leerness 1.36.86 → 1.36.89

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,98 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.89 — 2026-07-29 — 작업 난이도별 모델 라우팅 (P-0007, 옵트인 · 제안 후 확인 실행)
4
+
5
+ 사용자 요청: "codex/claude 등 CLI 설치·사용 가능 여부에 따라 작업 난이도별 AI 모델을 라우팅, 켜고 끌 수 있게." 함께 받은 제3자 제안(GPT-5.6=설계·판정 / Claude=지휘 / Codex-Spark=고속 작업자 고정 배치)은 codex 와 함께 검토해 **오래 가는 정책과 곧 낡을 정보를 갈라서** 전자만 반영했다.
6
+
7
+ - **`leerness agents route "<작업>"`** — 난이도(tiny/normal/high-risk)를 결정적 규칙으로 판정하고, 등급별 추상 역할 구성을 제안한다. 최상위 `route` 는 이미 컨텍스트 라우팅 명령이라(실행해보고 알았다) `agents recommend|dispatch` 와 같은 집에 두었다.
8
+ - **모델 이름은 한 줄도 하드코딩하지 않는다.** 정책은 추상 역할(`architect`/`commander`/`coder`/`reviewer`)로만 쓰고, 역할→provider+model 매핑은 기존 `leerness roles set` 사용자 설정이 소유한다. 벤더 모델명·성능 순위·컨텍스트 크기는 몇 달이면 낡아 제품 정책이 될 수 없다.
9
+ - **4중 fail-closed** — 토글 OFF · 역할 미설정 · 고위험인데 사람 승인(`--approved-by`) 없음 · 검수자와 구현자가 같은 provider. 마지막 항목은 같은 벤더가 구현과 검수를 겸하면 같은 맹점을 공유하므로 "검수됨"이라고 부를 수 없기 때문이다.
10
+ - **토글 `difficulty-routing` 기본 OFF.** 켜도 자동 실행은 없다 — `--dispatch` 를 **허용**할 뿐이고, 제안·열람은 토글과 무관하게 동작한다. 토글을 켜는 것이 무인 과금을 승인하는 뜻이 되면 안 된다.
11
+ - **감사 기록** `.harness/routing-log.json` (200건 cap, 원자 쓰기 + 락) — 입력·매칭된 신호·후보·선택·승인자·거부 사유·실행 여부. 거부된 시도도 남는다. 남기지 않으면 사후에 라우팅을 검증할 수 없다.
12
+
13
+ **정직성**: 판정하는 것은 **텍스트에 대한 규칙 매칭**뿐이고 난이도를 측정하지 않는다. 출력이 그대로 말한다 — "난이도는 텍스트 규칙 매칭 결과이며 측정값이 아닙니다" · "설치·활성 여부만 확인했습니다 — 인증·모델 사용권한·쿼터·과금 권한은 검증하지 않았습니다." `--version` 이 도는 것은 그중 무엇도 증명하지 않는다.
14
+
15
+ **규칙이 못 가르는 경우를 숨기지 않는다.** 검토에서 나온 함정 케이스 — "인증 문서 오타 수정"(고위험 키워드 + 초소형 키워드 동시) — 은 규칙만으로 가릴 수 없다. 보수적으로 high-risk 를 유지하되 `conflict: true` · `confidence: low` 를 노출하고 "사소한 작업이면 `--tier tiny` 로 낮추세요"를 안내한다. 근거가 없을 때도 tiny 로 내리지 않는다: 신호 0 + 짧은 설명은 `normal`(confidence low)이다 — **과소평가는 사고, 과대평가는 비용**이라 비용 쪽을 택했다.
16
+
17
+ 적대 검증 실측: 혼합 입력 5종("CSS 색상 조정하면서 결제 버튼 동작도 바꿈" 등) 전부 high-risk 로 상승 · 37만 자 입력 분류 1ms(ReDoS 없음) · 소스 주석의 "한글에는 `\b` 가 동작하지 않는다"는 주장도 실행으로 확인했다.
18
+
19
+ **출하 전 codex 검수(28차)가 BLOCK 2건 포함 10건을 냈고, 8건이 재현돼 전부 고쳤다** (1건은 반증 — `--approved-by --json` 은 실제로 `human_approval_required` 로 정확히 거부된다):
20
+
21
+ - **`dispatched: true` 라고 보고하면서 아무것도 실행하지 않았다.** 이 제품이 존재하는 이유가 "하지 않은 일을 했다고 말하지 않게 하는 것"인데 정확히 그 거짓말을 했다. 실제로 하는 일은 계획 확정이므로 `--confirm` / `confirmed` 로 이름을 바로잡고 `executed: false` 를 명시하며, "leerness 는 여기서 외부 CLI 를 실행하지 않습니다"를 사람용·JSON·`--log` 세 경로 모두에 넣었다.
22
+ - **영문 어간이 정규식을 통째로 빠져나갔다.** `\bauth\b` 는 `authentication` 에 매치되지 않아 **"fix typo in authentication middleware" 가 tiny 로 떨어졌다**(실측). `authorization`·`permissions`·`concurrency`·`idempotency` 도 같은 이유로 전부 누락. 어간 + `\w*` 로 바꾸고, `author`(글쓴이)·`tokenizer`(파서 용어)가 걸리지 않도록 오탐 대조군을 테스트에 함께 넣었다. **내 적대 테스트가 한글 입력만 써서 이 구멍을 통째로 놓쳤다.**
23
+ - **초소형 키워드 하나로 거대 작업이 tiny 가 됐다** — "Rewrite the entire compiler … and also fix one typo" 가 tiny 였다. tiny 는 "범위가 좁다"는 주장이므로 광범위 신호(entire/전체/갈아엎…)나 긴 설명이면 그 주장을 인정하지 않는다.
24
+ - **`--tier` 만 주면 빈 작업이 통과했다** — 빈 작업 검사가 declared 분기 뒤에 있었다.
25
+ - **빈 provider 가 "해석됨"이었다** — `resolved: !!r` 이라 공백 provider 로 확정이 통과했다.
26
+ - **손상된 감사 기록을 조용히 덮어썼다** — 감사 기록을 파괴하는 코드가 감사 기록 기능 안에 있었다. 손상 시 쓰지 않고 그 사실을 알린다(스토어 손상 = 덮어쓰기 거부, 기존 규율). 파싱 전에 파일 크기(4MB)도 본다.
27
+ - **하지 않은 확인을 했다고 말했다** — 고지 문구가 "설치·활성 여부만 확인했습니다"였는데 이 모듈은 설치 확인을 **한 번도 하지 않는다**. 실제로 하는 일(역할 매핑 읽기)만 말하도록 고치고, 고지가 없던 `--log` 경로에도 넣었다.
28
+
29
+ **신설 e2e 가드는 변이 14종으로 탐지력을 증명했다**(토글 무시 · 역할 미설정 무시 · 사람 승인 생략 · 독립성 검사 제거 · 충돌시 tiny 로 내림 · 근거부족을 tiny 로 · `--tier` 검증 제거 · 감사기록 미기록 · 고지를 옛 거짓 문구로 회귀 · 실행안함 고지 삭제 · 손상로그 덮어쓰기 복귀 · 빈 provider 를 resolved 로 · 빈 작업 검사 제거 · tiny 범위 가드 제거 → **14/14 검출**).
30
+
31
+ 세 번 고쳐서 14/14 가 됐고, 살아남은 변이의 원인이 매번 달랐다:
32
+ - `--tier` 검증을 없애도 exit 1 이 나왔다 — **검증이 아니라 크래시** 때문이었다(사유 `invalid_tier` 까지 단언 + 알 수 없는 등급은 크래시 대신 가장 엄격한 구성으로 fail-closed).
33
+ - probe 검사와 영수증 검사처럼 **두 거부 조건이 서로를 가렸다**(판별 케이스 추가).
34
+ - 마지막 하나는 코드가 아니라 **내 픽스처가 잘못된 스키마(`{roles:{…}}` 대신 최상위)로 저장돼 셋업이 조용히 실패한 것**이었다 — 빈 provider 를 검증한 게 아니라 "역할 없음"을 통과로 읽고 있었다. 셋업 성공 여부까지 함께 단언하도록 고쳤다.
35
+
36
+ 게이트: exit 0 · E2E 409/409 · selftest 341/341 · ✗ 0건
37
+
38
+ ## 1.36.88 — 2026-07-29 — `selftest --json` 잡음 3종 제거 · "우연히 지켜지던" 단일 JSON 계약을 원인 단계에서 고정
39
+
40
+ `selftest --json` 이 JSON 앞에 잡음 5줄(`✗ selftest probe` · `✗ probe` · graph 사람용 3줄)을 낸다는 제보로 시작했다. **먼저 계약이 실제로 깨졌는지 측정했고, 제보의 전제 두 가지는 사실이 아니었다.**
41
+
42
+ - **stdout 은 이미 순수 JSON 이었다** — `JSON.parse(stdout)` 성공 · `stdout.trimStart().startsWith('{')` 참 · exit 0. 잡음 5줄은 **전부 stderr** 였다(`2>&1` 로 합쳐 보면 오염처럼 보인다). 소비자 계약은 깨져 있지 않았다.
43
+ - **cwd 의존도 아니었다** — 리포 루트와 임시 cwd에서 잡음이 **동일하게** 나온다. e2e 가 통과한 이유는 "임시 cwd 라 잡음이 안 보여서"가 아니라 **stdout 이 실제로 순수해서**다.
44
+
45
+ 그럼에도 잡음 자체는 실재했고 원인 3개는 모두 진짜 결함이라 고쳤다:
46
+
47
+ - **프로브가 stderr 를 안 가로챘다** — `fail()` 은 `--json` 모드에서 `✗` 를 **stderr** 로 보내는데(io.js 설계), 두 케이스(`fail()` exitCode 행위 · `lib/io` 프리미티브)는 `process.stdout.write` 만 스왑했다. 양쪽 스트림을 스왑하도록 수정 — 이미 있던 패턴(4558행 케이스는 `_w`/`_e` 둘 다 스왑)을 따랐다.
48
+ - **프로브가 io 전역 래치를 오염시킨 채 빠져나갔다** — `fail()` 은 `_failLatched`/`_firstFail`/exit 훅을 세우는데 `finally` 가 `exitCode` 만 복구했다. 남은 300여 케이스 내내 래치가 살아 있어 사람용 `log()` 가 전부 stderr 로 강등됐다. `setQuiet(false)`(기존 "래치 초기화" 관용구, 5838행)로 복구.
49
+ - **graph 케이스에 `quiet: true` 누락** — `graph --html` 온톨로지 케이스 4개 중 **하나만** deps 에 `quiet` 를 안 넘겨 사람용 3줄이 샜다.
50
+
51
+ **핵심: stdout 순수성만 단언하는 가드였다면 공허했다.** 세 결함을 하나씩 되돌린 절제 실험에서 세 변종 모두 `pureStdout=true` · `payloadOk=true` · `exit=0` 으로 **통과**한다 — 잡히는 축은 오직 stderr 잡음뿐이었다. 위 두 번째 항목이 이유다: 래치가 사람용 출력을 stderr 로 강등해 주는 **우연** 덕분에 stdout 이 겨우 지켜지고 있었고, 래치 동작이 바뀌는 순간 그대로 stdout 오염이 된다. 그래서 신설 가드는 stdout 순수성 + 페이로드 + **stderr 잡음 0** 을 함께 단언한다.
52
+
53
+ > **가드가 덮는 범위 정정** (출하 전 적대 검수에서 실측): 신설 가드는 세 결함 중 **둘**(stderr 미스왑 · graph quiet 누락)을 잡고, **래치 오염은 잡지 못한다** — 래치만 되돌린 변종은 `stderr.len=0` 으로 가드를 그대로 통과한다. 래치 복구는 잡음을 만들지 않고 *없애는* 방향이라 "잡음 0" 축에 걸리지 않기 때문이다. 따라서 "원인 단계에서 막는다"는 세 결함 전부가 아니라 둘에 대해서만 성립한다. 또한 기존 5049 블록이 "e2e 기본 임시 cwd 로만 돌았다"는 서술도 사실이 아니었다 — 그 블록은 `cwd` 옵션이 없어 e2e 프로세스의 cwd(= `npm test` 실행 위치 = 리포 루트)를 그대로 상속한다(`process.chdir` 사용 0회, 실측 확인). 신설 가드의 실제 추가 가치는 cwd 차이가 아니라 **stderr 축을 단언한다는 점**이다.
54
+
55
+ - **신설 e2e 가드** — `selftest --json` 을 **리포 루트 cwd** 로 spawn 해 ① `stdout.trimStart().startsWith('{')` ② `JSON.parse(stdout)` 성공 + `ok/pass===total/fail===0` ③ stderr 에 `✗ ` · `leerness.html →` · `nodes · ` 부재를 단언. 기존 5049 블록은 e2e 기본 임시 cwd 로만 돌았다.
56
+ - **가드의 보장 범위**: `selftest --json` 한 명령의 stdout 단일 JSON 계약과 **알려진 유출원 3종**의 부재만 보장한다. 다른 `--json` 명령의 계약이나, 여기 열거되지 않은 새 유출 문자열은 보장하지 않는다.
57
+
58
+ ### 같은 릴리스에 함께 실린 것 — bugfix 완료 영수증 (P-0005, 옵트인)
59
+
60
+ 사용자 제기: "버그를 고쳐달라고 하면 증상만 고치고 고쳤다고 판정한다." 실사용 저장소를 **읽기 전용**으로 스캔해 근거를 먼저 세웠다 — 주문 상태 오판정이 플랫폼(SSG·11번가…)을 갈아가며 8회 반복, 한 파일이 22회 수정, 그 프로젝트 lessons 11건은 전부 개별 API 특이사항이고 **클래스 교훈은 0건**.
61
+
62
+ - **개입 지점은 `--status done` 전이 하나**. leerness 는 이미 debug 렌즈·review-request 로 올바른 절차를 안내하지만 그건 조언 텍스트일 뿐 **완료 시점에 소비되지 않는다**. 조언을 더 만드는 것이야말로 증상 처방이라 판단했다.
63
+ - **`leerness bugfix start <ID> --repro "<cmd>" --expect-bad "<신호>"`** — 등록은 probe 가 **지금, 2회 연속** 실패하고 기대 신호가 출력에 있을 때만 허용한다. 통과해버리는 probe 는 나중에 아무것도 증명하지 못한다.
64
+ - **`bugfix receipt`** 로 근본원인·형제범위를 남기고, `done` 시 probe 통과 + 영수증 존재를 요구한다. `bugfix drop` 으로 등록 취소.
65
+ - **기본 OFF(옵트인)** — `toggle set bugfix-receipt on` 으로만 켜진다. 이를 위해 토글 레지스트리에 `defaultOff` 를 신설했다. 기존 동작을 바꾸는 신규 토글이 기본 ON 이면 업그레이드만으로 모든 사용자의 `task update` 가 깨진다.
66
+ - **정직성 계약**: 기계적으로 검증되는 것은 **probe 뿐**이다. 근본원인·형제범위는 선언이며 출력이 그렇게 명시한다(`ⓘ 아래는 선언이며 내용의 진위는 검증하지 않았습니다`). 신뢰 경계도 모듈 헤더에 적었다 — 저장소 쓰기 권한자는 우회할 수 있으므로 이 게이트는 "성실한 작업자의 성급한 완료"를 막지, 적대적 우회를 막지 않는다.
67
+ - **기각한 것**: 재오픈 ID 경고(실측 정밀도 19% — 후속 커밋 37건 중 문서 14·확장 4·되돌림 1) · 파일 hotspot 경고(원래 자주 바뀌는 파일과 구분 불가) · 형제 분기 자동 제시(**구문 분기 ≠ 도메인 형제**, 기계가 발명할 수 없다).
68
+
69
+ **검수에서 잡힌 결함 8건은 전부 실행으로 재현한 뒤 고쳤다** (자체 헌트 3 + codex 5):
70
+
71
+ - **완료 렌더가 `task update` 를 죽였다** — baseline 없는 엔트리가 유효로 통과해 `e.baseline.exit` 에서 TypeError. 스토어 계약을 조여(`baseline.failed === true` 필수) 형상 무효로 보류하도록.
72
+ - **공허한 영수증** — `--siblings ","` 가 `checked: []` 를 만들며 "기록됨"으로 통과. 빈 목록은 기록이 아니다.
73
+ - **이 기능을 만들게 한 도메인에서의 오차단** — 출력에 `SyntaxError:` 가 있으면 런처 오류로 단정해, 11번가가 JSON 대신 HTML 을 주는 바로 그 케이스의 probe 가 **수정을 마치고 exit 0 이어도 완료가 막혔다**. 런처 판정은 하드 신호(spawn 실패·127·9009)만 보고, 출력 문자열은 등록 시점의 **비차단 경고**로 강등.
74
+ - **1회성 probe 가 게이트를 무력화** — `if(!exists(marker)){write(marker); exit 1}` 은 등록 때만 실패하고 이후 항상 통과해, 제품 코드를 한 줄도 안 고치고 done 을 통과시킨다. 등록 시 2회 연속 재현 요구로 차단(실측: 거부 · 결정적 probe 대조군은 정상 등록).
75
+ - **동시 등록이 서로를 지웠다** — 읽은 목록 전체를 덮어써서 4개 동시 등록 중 **2개가 사라졌고**, 사라진 task 는 "미등록"이 되어 게이트가 조용히 꺼졌다(fail-open). `_withLock` 주입 + 락 안 재로드 + 임시파일 rename → 실측 4/4 보존.
76
+ - **지키지 못할 약속 문구** — 게이트가 꺼진 프로젝트에서도 "done 시 probe 가 통과해야 합니다"라고 안내했다. 실제 토글 상태를 읽어 꺼져 있으면 그렇게 고지한다.
77
+ - **이미 done 인 task 재편집이 막혔다** — 조건이 `status === 'done'` 뿐이라 전이가 아닌 호출도 게이트를 탔고, probe 스크립트가 사라진 뒤엔 완료된 task 를 영영 손댈 수 없었다. 현재 상태와 비교해 **전이일 때만** 검사.
78
+ - **exit 0 인데 실패 신호 잔존** — 경고로 노출하되 **막지 않는다**. 여기서 잘못 막으면 이미 고쳐진 probe 는 재등록할 방법이 없어(등록이 실패를 요구) 사용자가 빠져나올 수 없다.
79
+
80
+ **출하 직전 적대 헌트(6렌즈·36에이전트)가 21건을 확인하고 9건을 반증했다.** 반증 9건은 설계상 명시된 한계(신뢰 경계·형상 무효 시 fail-closed 등)와 맞아 기각했다. 확인된 것 중 셋은 이 기능의 **뼈대**를 바꿨다:
81
+
82
+ - **게이트가 한 호출부에만 걸려 있었다** — `task sync --from`(TodoWrite 왕복, 하네스가 `session-workflow.md` 에서 직접 지시하는 정규 경로)이 경고 한 줄 없이 exit 0 으로 done 을 기록했다. 즉 게이트가 막으려는 주체가 **정상 명령 하나로** 무력화할 수 있었고, 사후에 알려주는 표면도 없었다. 판정을 `taskUpdate` 에서 progress 행을 쓰는 **단일 병목(`upsertProgress`)** 으로 옮기고, 막힐 때는 조용히 무시할 수 없도록 **던지게** 했다(반환값 무시가 이 클래스의 재발 양식이다). 이제 새 호출부가 생겨도 자동으로 게이트를 탄다.
83
+ - **"이미 done 이면 스킵"이 동기 시나리오를 정확히 무력화했다** — 1.36.87 에서 codex 지적(완료된 task 를 영영 못 고친다)을 받아 넣은 조건인데, **재오픈 버그**(1라운드 done → 회귀 → probe 등록 → 다시 done)가 그대로 통과했다. 이 기능이 존재하는 이유가 바로 그 시나리오다. 스킵을 없애고, 갇히는 문제는 `leerness bugfix drop <ID>` 라는 **명시적 탈출구**로 푼다.
84
+ - **가드가 배선을 한 줄도 실행하지 않았다** — selftest 는 `checkDoneTransition` 을 in-process 로만 불러서, 게이트를 통째로 죽여도 341/341 그린이었다. 실제 CLI 경로로 도는 e2e 가드를 신설하고 **변이 8종으로 탐지력을 증명**했다(게이트 제거·차단결과 무시·sync 예외 삼킴·재오픈 스킵 복귀·없는id 확인 제거·probe 검사 제거·영수증 검사 제거·토글 무시 → 8/8 검출). 처음 만든 가드는 6/8 이었는데, 살아남은 둘은 **서로를 가려주고 있었다**(probe 검사와 영수증 검사 중 하나를 죽여도 다른 하나가 막아 exit code 만 보는 단언에는 안 잡힘) — 사유(`code`)까지 단언하고 "수정했지만 영수증 없음" 판별 케이스를 넣어 해소했다.
85
+
86
+ 나머지 확인건도 함께 정리했다: 존재하지 않는 task id 로도 등록에 성공해 **오타 하나면 "보호받는 줄 아는" 무보호**가 되던 구멍(등록 시 tracker 확인) · `ENOBUFS`(출력 8MB 초과)를 "명령이 실행되지 못했습니다"로 오분류해 통과하는 probe 를 **사실과 다른 사유로** 차단하던 것(판정 불가는 판정 불가라고 말하고 별도 code 로 분리) · `bugfix list` 가 위치 경로를 무시하고 cwd 를 읽어 등록된 probe 를 "없음"으로 보고하던 것 · 목록과 게이트가 형제범위를 **서로 다른 술어**로 판정하던 불일치(단일 술어로 통합) · 토글 설명이 적용 범위(probe 를 등록한 task 에 한함)를 말하지 않던 것 · `leerness commands` 와 설치되는 `session-workflow.md` 에 이 기능이 아예 없어 사용자가 발견할 수 없던 것.
87
+
88
+ 곁가지로 토글 값 해석도 고쳤다 — 종전 `v !== false` 는 `null`/`0`/`"off"` 를 **ON 으로** 읽었다. 기본 ON 토글에서는 무해했지만 차단 게이트가 생긴 순간 "알 수 없는 값이 차단을 켜는" 동작이 된다. 인식 가능한 표현만 받고 나머지는 레지스트리 기본값으로 되돌린다(변이 테스트로 가드 유효성 확인: 종전 규칙 복귀 시 케이스 FAIL).
89
+
90
+ e2e 의 `doctor` 제한도 손봤다 — `doctor` 는 내부에서 selftest 를 돌려 실측 19~24s 인데 제한이 20s 였다(**게시본 1.36.86 기준 여유 0.9초**). 120s 로 올리면서 **종료코드 단언을 함께 넣었다**: 제한만 올리고 status 를 안 보면 타임아웃(status=null)이 출력 매칭만으로 통과한다.
91
+ - 래치 초기화가 새 stdout 오염을 만들지 않는지 확인했다(래치 해제 후 사람용 `log()` 은 stdout 으로 간다) — 수정 후 실측 stderr 길이 **0**, stdout 순수 JSON. 즉 잔여 유출원은 문자열 매칭이 아니라 **전량 부재**로 확인했다.
92
+ - **stderr 를 "빈 문자열" 로 단언하지 않은 이유**: Node 가 환경에 따라 stderr 로 `DeprecationWarning` 등을 내보낸다(이번 e2e 실행에서도 관측). 빈 문자열 단언은 런타임/노드 버전에 따라 깨지므로 **알려진 유출 문자열 3종의 부재**로 좁혔다.
93
+ - 검증: selftest **341/341**(`--json` · 사람용 양쪽) · e2e **407/407**(1796s) · 리포 루트/임시 cwd 양쪽 stdout 순수성 실측 · 신설 가드 **4방향 절제 실험**(fix 3종 개별 되돌림 + 전체 되돌림 → 모두 FAIL, 각 되돌림이 정확히 해당 잡음만 되살림). `graph --html` 실명령의 사람용 3줄 출력은 무변경 확인.
94
+ - 참고(이번 수정과 무관): 1차 e2e 실행에서 `doc/surface` 1건이 실패했다가 재실행에서 통과했다 — `doctor --json` 은 격리 실측 **21.1s** 인데 해당 블록 제한이 **30s**(여유 1.4배)라 전체 부하에서 간헐 초과한다. 1.36.87 이 같은 이유로 다른 타임아웃을 올렸던 것과 동일 패턴이며, 이 블록은 아직 안 올라가 있다.
95
+
3
96
  ## 1.36.86 — 2026-07-29 — brainstorm 두 경로 수집 단일화(중복 149줄 제거) · 총계 계약 정정 · 조용한 절단 제거
4
97
 
5
98
  1.36.85 가 다음 라운드 과제로 남긴 "사람용 vs `--json` 이 갈린다"를 근본 수정했다. 원인은 **세 겹**이었다: ① 사람용이 자체 수집 코드를 갖고 있어 `code` 를 아예 모으지 않았고 ② 렌더러에 code 섹션이 없었으며 ③ 총계를 각자 인라인 계산해 서로 다른 표면을 누락했다.
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.86 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
+ 이 프로젝트는 Leerness v1.36.89 하네스를 사용합니다. 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.86는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **89개 도구**를 노출:
179
+ Leerness v1.36.89는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **89개 도구**를 노출:
180
180
 
181
181
  ```jsonc
182
182
  // 카테고리별
@@ -197,7 +197,7 @@ Leerness v1.36.86는 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.86 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
+ 현재 누적: **v1.9.x → 1.36.89 릴리스 태그 이력** (수백 라운드) · _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.86: 2026-07-29
238
+ Last synced by Leerness v1.36.89: 2026-07-29
239
239
  <!-- leerness:project-readme:end -->