leerness 1.36.81 → 1.36.83
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 +42 -0
- package/README.ko.md +1 -1
- package/README.md +4 -4
- package/bin/leerness.js +449 -41
- package/docs/interoperability.md +2 -2
- package/lib/analyzers.js +22 -2
- package/lib/referee.js +8 -2
- package/package.json +1 -1
- package/scripts/e2e.js +117 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.83 — 2026-07-28 — 자기참조 가드 부채 상환 (탐지 가능 범위 12 → 0) · 소스 문자열 검사를 행위 검사로
|
|
4
|
+
|
|
5
|
+
1.36.82 가 `baseline 12` 로 **유예**했던 자기참조 소스가드를 갚고 baseline 을 **0 으로 조였다**(유예 수치를 남기면 그 안에서 조용히 썩는다).
|
|
6
|
+
|
|
7
|
+
**주장의 정확한 범위** — 검수(25회전)가 "잔여 0" 이 과장임을 반례로 보였고, 그 지적이 맞았다. 정확히는 **"직접 선언된 `read(__filename)` 수신자의 codeish 리터럴 `.includes` 탐지 결과 0"** 이다. 메타가드가 아직 못 보는 형태가 남아 있다: `read(__filename).includes(...)` 직접 호출 · `fs.readFileSync(__filename)` · `indexOf`/정규식 `.test` · alias/재할당/구조분해 · 표식 복사. 이번 라운드에 실측으로 드러난 세 사각지대(UI 문자열처럼 codeish 가 아닌 리터럴 · `.test(정규식)` 형태 · 읽는 파일엔 있지만 **무관한 위치**에서 만족되는 리터럴)는 해당 가드를 개별 수리했으나, 메타가드의 일반 탐지로는 아직 잡히지 않는다. 다음 라운드 과제로 남긴다.
|
|
8
|
+
|
|
9
|
+
- **12건을 현재 코드 기준으로 재작성 — 대부분 소스 문자열 검사를 행위 검사로 교체.** 실제 junction 을 만들어 migrate 복사가 심링크를 따라가지 않는지, 70KB `.env.production` 의 시크릿이 탐지되는지, `plan add --done-when` 이 저장·파싱되는지를 **직접 실행해** 확인한다. 문자열은 리팩터로 사라지지만 동작은 사라지지 않는다. 각 가드는 "지키던 동작을 실제로 깨뜨렸을 때 실패한다"를 변이로 증명했고, **대조군**(같은 변이에서 원본 가드는 통과)까지 붙였다 — 예: 심링크 미추종은 `lstatSync`→`statSync` 한 글자로 기능이 완전히 깨지는데 종전 가드는 337/337 초록이었다. 보고서에 적힌 것과 **다른 방식의 변이**(skip 기록 제거 / env 정규식 축소)로도 정확히 실패함을 확인해 과적합이 아님을 검증했다.
|
|
10
|
+
- **메타가드를 파일-인지로 강화 — 3건이 더 드러났다.** 리터럴이 "제품 어딘가"에 있으면 통과시키던 판정을, **가드가 실제로 읽는 파일** 기준으로 바꿨다. `read(__filename)`(=bin)을 검사하는데 구현이 `lib/` 로 옮겨갔다면 그 가드는 여전히 자기 줄만 매칭한다: drift 의 "최신 Last generated"(→`lib/drift.js`) · decision 필드 파싱(→`lib/pure-utils.js`) · constraints 호출부(인자가 하나 늘어 정확 리터럴이 어긋남). 셋 다 수리.
|
|
11
|
+
- **파일을 넘나든 자기참조** — `scripts/e2e.js` 의 constraints 단언이 bin 에서 리터럴을 찾는데, 그 문자열의 **유일한 출처가 bin 안의 selftest 가드 줄**이었다. 위 수정으로 그 줄이 사라지자 게이트가 실패해 공허함이 드러났다(부작용이 아니라 의도한 효과). 불변식으로 교체하고, 같은 클래스가 `e2e.js`/`e2e-core.js` 에 더 있는지 전수 확인(1건이 유일).
|
|
12
|
+
- **도구가 세 번 틀렸고 세 번 다 개별 확인으로 잡았다**: ① `$'` 가 `String.replace` 치환문자열에서 "매치 이후 문자열"로 해석돼 패치 하나가 잘렸다(문법 검사가 잡아 자동 원복 → 나머지 12건도 원문 그대로인지 전수 확인 후 함수 치환자로 재적용) ② 탐지기의 **단어 경계 누락**으로 `s.includes` 가 `tps.includes` 에 걸려 정상 가드를 결함으로 지목(일괄 수정했다면 멀쩡한 가드를 망가뜨렸다) ③ 템플릿 보간 리터럴(`` `_requireInit(root, '${l}')` ``)은 런타임 치환이라 원문 비교가 불가능한데 "제품에 없음"으로 오탐 — 이 클래스를 판정에서 제외.
|
|
13
|
+
**검수 25회전이 잡은 것** (전건 직접 재현):
|
|
14
|
+
|
|
15
|
+
- **High — 내가 이 라운드에 만든 회귀**: 템플릿 보간을 판정에서 제외하면서 `${` 가 든 리터럴을 **따옴표까지 싸잡아** 제외해 16개 가드가 무방비가 됐다. 보간은 **백틱에서만** 일어난다 — 따옴표 안의 `'${...}'` 는 그냥 문자열이라 검사해야 한다. 검수가 시연한 우회(skill 덮어쓰기 보호를 `if (false)` 로 무력화)가 수정 후 정확히 차단됨을 실측.
|
|
16
|
+
- **High — health 보안 연결이 무방비**: 가드가 bin 을 읽는데 검증 대상은 `lib/health.js` 에 있었고, `_collectSecretFindings(root)`/`committedSecrets` 는 bin 의 **무관한 다른 호출**이, 한국어 문구는 가드 자신의 줄이 만족시켰다. 실제로 스캐너 연결을 끊어 **커밋된 시크릿이 있는데도 `healthy: true`** 가 나오게 만들어도 337/337 초록이었다(실측 확정). → 실제 시크릿을 심은 워크스페이스로 `health --json` 을 돌려 `committedSecrets≥1 · critical · healthy:false` 를 행위 검증.
|
|
17
|
+
- **Medium — "잔여 0" 반례 3건**: `## File check`/`## Test count`(실제 출력은 이모지가 들어가 정확 리터럴이 어긋남) · `/# leerness doctor/.test(src)`(정규식이 자기 줄을 매칭, 실제 헤더는 `lib/diagnostics.js`). 셋 다 행위 검사/올바른 파일로 교체.
|
|
18
|
+
- 검수가 지적한 나머지(신규 행위검사의 커버리지 부족 — `.env.local` 미포함 · 선행 eager read 미탐 · lesson 파서 미검증 · `--done-when` 기본값 경로 · e2e exit 무시 · README 관리블록 버전)는 **다음 라운드 과제로 명시**한다. 이번 라운드에 고친 것만 고쳤다고 적는다.
|
|
19
|
+
|
|
20
|
+
- 검증: selftest 337 · e2e 406/406 · 가드별 변이 증명(대조군 포함) · 교차 변이로 과적합 아님 확인 · 메타가드 4방향(따옴표/백틱 자기참조 차단 · 정상 가드 무오탐) · baseline 경계 시험(12 통과 / 11 실패 → 수치가 추정이 아니라 측정값임을 확인한 뒤 0 으로 전환) · 검수 시연 우회 2건 차단 실측.
|
|
21
|
+
|
|
22
|
+
## 1.36.82 — 2026-07-28 — 공허한 가드 클래스 스윕: 날조된 완료 주장이 통과하던 fail-open · 안내만 되고 없던 플래그 · 자기참조 소스가드
|
|
23
|
+
|
|
24
|
+
1.36.81 의 교훈("셋업이 조용히 실패해도 부정 단언은 통과한다")을 **클래스로 확장**해, "통과하지만 아무것도 증명하지 않는 가드"를 전수 스윕했다(5개 렌즈 · 변이 테스트로 실행 확정). 보고 8건 중 직접 재현한 것만 반영했고, **수치가 과장된 보고는 정정**했다(자기참조 가드: 보고 42건 → 실측 12건. 내 1차 탐지기도 `lib/` 를 빼먹어 39건으로 과대보고했다가 교정).
|
|
25
|
+
|
|
26
|
+
- **High — 플래그십 게이트가 가장 위험한 케이스에서 반대로 작동했다.** `verify-claim` 은 evidence 에 파일 경로가 없으면 `fileChecks.every([])`=true(공허참) · `stubFiles`=[] 이라 구현실체 true · git 교차검증은 `files.length>0` 을 요구해 skip — **모든 판정이 공허참**이 되어 "코드도 테스트도 없이 done" 이 `ok:true`/exit 0 이었다. **단조성이 뒤집혀 있었다**: 파일명을 적어 검증 가능하게 쓸수록 FAIL, 아무것도 안 적고 날조할수록 PASS. 코드 주석은 "공허참은 evidenceQualityOk 가 차단한다"고 적혀 있었지만 기본 모드의 그 값은 `hasFile||hasTest||hasLog` 라 `"tests: 32/32 passed"` 문자열 하나로 통과했다 — **주석의 전제가 거짓**이었다. 새 사유 `unverifiable-claim` 을 세 경로(collect/`--json`/human)가 공유하는 한 곳에 배선. 탈출구는 두 가지뿐(evidence 에 수정 파일 경로를 적거나 `--run-tests` 로 실행 결과를 붙이거나), 기존 `--lenient` opt-out 은 그대로.
|
|
27
|
+
- **한계(과장하지 않음)**: 이 수정은 "대조된 것이 하나도 없는" 구멍을 닫을 뿐, 존재하는 아무 파일명(예: `README.md`)을 적으면 통과한다. verify-claim 은 증거 대조이지 구현 증명이 아니다.
|
|
28
|
+
- **추출기 사각지대 동반 수정**: "대조 0건 = FAIL" 로 바꾸면 추출기가 못 보는 파일이 곧 정직한 주장의 거짓 차단이 된다. 확장자 없는 표준 파일명(`Dockerfile`/`Makefile`/`LICENSE` 등)을 추출 대상에 추가하고, `_evidenceQuality` 의 "파일 언급" 판정도 같이 맞췄다(주석이 "bin FILE_RE 와 정합"을 명시하는데 한쪽만 고치면 같은 명령 안에서 정의가 둘로 갈린다).
|
|
29
|
+
- **High — 툴이 안내하는 플래그가 구현되지 않았다.** `referee verify` 성공 시 leerness 는 "이제 `gate --require-referee <id>` 가 이 검증기를 신뢰합니다"라고 출력하는데, `gate` 는 그 플래그를 **읽지도 않았다**(패키지 전체에서 그 문자열은 안내문 한 줄뿐). 사용자는 안내받은 명령을 CI 에 넣고 검증기가 머지를 막는다고 믿지만 아무 일도 일어나지 않았고, 부재/무효 검증기에서도 exit 0 이라 오배선을 알 신호조차 없었다 — 1.36.80 에서 내가 만든 약속이 곧 거짓말이었다. 이제 실제로 게이팅한다: 미존재/미증명/무효 검증기는 실행 전 거부, 검증기가 현재 상태를 거부하면 게이트 실패, 반복 지정(`--require-referee a --require-referee b`) 지원, 플래그가 없으면 기존 5체크 그대로.
|
|
30
|
+
- **High — 자기참조 소스가드**: `read(__filename)` 로 자기 파일을 읽는 selftest 가, 검사하려던 코드 리터럴이 리팩터로 사라지면 그 문자열이 **가드 자신의 줄에만** 남아 영원히 통과한다(기능을 통째로 지워도 초록). 실제로 **verify-claim 의 `--json` exit 게이트**와 **개인키 파일 스캔 강제포함**이 그 상태였다. 고가치 4건을 현재 구현 기준으로 교체하고, **확산 금지 메타가드**를 추가했다 — 제품 코드(bin 나머지 + `lib/` + `scripts/`)에 없는 **코드형** 리터럴을 붙잡은 케이스가 늘어나면 실패한다. 잔여 부채 9건은 숨기지 않고 baseline 으로 명시했다(줄이는 방향으로만 갱신).
|
|
31
|
+
- **문서 수치 메타가드**: `docs/interoperability.md` 가 "86 도구"(3릴리스 낡음)를, `README.ko.md` 가 "selftest 210 케이스"(실제 336)를 **현재 상태**로 주장한 채 npm 에 실려 나갔다. `README.md` 는 `readmeSync` 관리구간이라 동적으로 맞았지만 관리구간 밖 문서는 아무도 맞춰주지 않는다. 낡을 수치를 살아있는 출처 안내로 대체하고, README/docs 의 MCP 도구·selftest 수가 실측과 어긋나면 selftest 가 실패하도록 했다(과거 라운드 로그인 AGENTS/CLAUDE/CHANGELOG 는 과거 수치가 정당하므로 제외 — 포함하면 false-BLOCK).
|
|
32
|
+
- **가드를 만들며 두 번 틀렸고 둘 다 실행으로 잡았다**: (1) 사이트 수치 가드 1차판이 5/5 오탐(주석·파생표현·다른 개념의 "0 의존성")이라 대상을 실제로 낡은 이력이 있는 수치로 좁혔다. (2) 그 가드가 하드코딩 재삽입을 **차단하지 못했다** — JS 의 `\b` 는 ASCII 기준이라 `(?:도구|tools?)\b` 가 한글 뒤에서 성립하지 않았다. 차별적 통제를 돌리지 않았다면 아무것도 지키지 않는 가드를 출하할 뻔했다. 같은 버그로 내 수동 grep 도 낡은 주장 2건 중 1건만 보고 있었다.
|
|
33
|
+
**검수 24회전이 BLOCK 판정 + High 6건** — 전건 직접 재현 후 수정. 이번 라운드는 **내 자체 검증이 여섯 개를 다 놓쳤다**:
|
|
34
|
+
|
|
35
|
+
- **내가 고치려던 역전을 다시 만들었다.** `_runProven` 을 `runResult.parsed` 로 판정한 탓에, `--test-cmd 'node -e "process.exit(0)"'` 처럼 **실제로 실행해 exit 0 을 받아도** 출력이 파싱 불가면 차단되고, evidence 에 `"1/1 passed"` 문자열만 적은 날조는 통과했다. 커스텀 리포터·`go test` 를 쓰는 정직한 실행이 벌을 받는 구조 — 진단 신호로 삼았던 단조성 역전 그 자체다. 판정 기준을 **실행 확증**(`allPassed`)으로 교체.
|
|
36
|
+
- **추출기 사각지대 4형태가 곧 거짓 차단이었다**: `ops/containers/Dockerfile`(다단계 경로의 bare 파일) · `.github/workflows/ci.yml`(선두 점이 잘려 `github/…` 라는 없는 파일로 판정) · `.gitignore` · `Dockerfile.dev`(내가 추가한 bare 규칙이 `Dockerfile` 로 잘라 "없는 파일"로 만듦). bin 추출기와 `_evidenceQuality` 로 갈려 있던 판정을 **단일 출처**(`lib/analyzers._EVIDENCE_FILE_RE`)로 통합.
|
|
37
|
+
- **툴이 안내하는 문법 그대로 쓰면 깨졌다.** `--require-referee` 를 value-flag 목록에 등록하지 않아 값이 positional 경로로 흡수돼 root 가 `<cwd>/<id>` 가 됐다(소스 주석이 "신규 value-flag 는 반드시 여기 등록" 이라고 명시하는데 어겼다). **내 e2e 는 항상 `--path` 와 `.` 을 줘서 이 결함을 가리고 있었다** — 픽스처가 버그를 숨기는 전형이라, 이제 안내된 문법 그대로(cwd + 플래그만) 검증한다. 1.36.80/81 의 다른 신규 value-flag 도 소급 등록.
|
|
38
|
+
- **값 없는 `--require-referee` 가 조용히 사라졌다** — CI 의 `--require-referee=$EMPTY_ENV` 가 아무 게이팅 없이 exit 0(fail-open). 명시적 실패 단계(`referee_missing_value`)로 전환.
|
|
39
|
+
- **메타가드 자신이 반쯤 공허했다**: 케이스 파싱이 337 중 170 만 잡았고, 자기 제외 표식을 `'selfref' + '-guard-own'` 로 조립해 **소스에 결합된 문자열이 없어 한 번도 동작하지 않았다**(자기를 세지 않은 건 우연). 백틱 리터럴로 우회도 가능했다. 시작 경계 분할로 전수 파싱 + 백틱 포함 + 표식 교정, baseline 은 과소집계였던 9 → **실측 12** 로 정정.
|
|
40
|
+
- **"탐지력 증명됨" 은 실제보다 강한 주장이었다** — 캘리브레이션이 확인하는 것은 등록된 known-good/known-bad 쌍이 기대 사유로 갈린다는 것과 `check` 의 기동 가능성뿐이고, good/bad 가 `check` 와 같은 절차인지는 **도구가 강제할 수 없다**. 문구를 사실에 맞추고 그 조건을 명시.
|
|
41
|
+
- Medium 동반 수정: gate 의 referee 실패가 `--json` 에서 `{name, ok:false}` 로만 축약되던 것 → `code`/`exit`/`timedOut`/`spawnError`/`signal` 노출 · 캘리브레이션 timeout 정책을 gate 와 공유(느린 검증기 거짓 차단 방지) · 같은 id 반복은 순서 보존 dedupe · 문서 수치 가드의 어순 양방향·core 문맥·`80+` 하한 표현·`CONTRIBUTING/SECURITY` 스캔 보강.
|
|
42
|
+
|
|
43
|
+
- 검증: selftest 337 · e2e **406/406**(신규 2블록) · 신규 단언의 **변이 테스트**(수정을 되돌리면 정확히 실패) · 메타가드 4방향(따옴표/백틱 자기참조 차단 · 정상 가드 무오탐) · false-BLOCK 시나리오(문서만 수정 / 추출기 사각지대 5형태 / 비-done / `--run-tests` 실행확증 / `--lenient`) 실측.
|
|
44
|
+
|
|
3
45
|
## 1.36.81 — 2026-07-28 — 게시본 클린룸 실측이 잡은 2건 (referee 상태 신호 정확화 · 출하 소스 생 NUL 제거)
|
|
4
46
|
|
|
5
47
|
1.36.80 을 게시한 뒤 **게시된 tarball 로** 신규 기능을 실행해 본 결과 2건이 나왔다. 둘 다 게이트(selftest 334 · e2e 404)는 초록이었다 — 단언이 없던 자리다.
|
package/README.ko.md
CHANGED
|
@@ -154,7 +154,7 @@ leerness mcp serve # JSON-RPC over stdio, 80+ 도구
|
|
|
154
154
|
- **원자적 UTF-8 쓰기** — temp + rename 으로 부분쓰기 손상 방지, BOM 자동 strip.
|
|
155
155
|
- **shell 미경유 MCP** — `mcp serve` 의 도구 호출은 셸을 거치지 않고 인자를 직접 전달(명령 주입 차단).
|
|
156
156
|
- **순수 `--json` / 일관 exit code** — 성공·실패·미존재 경로 모두 파싱 가능한 JSON(`{ok,error,code}`) + 실패 시 exit 1. 자동화·CI 친화.
|
|
157
|
-
- **모듈 분리(DI)** + **내장 자가검증** — `lib/` 순수 유틸/IO/카탈로그 분리, `selftest`
|
|
157
|
+
- **모듈 분리(DI)** + **내장 자가검증** — `lib/` 순수 유틸/IO/카탈로그 분리, `selftest` 가 코어 함수 무결성을 자가 검증(`doctor` 가 함께 실행하며 케이스 수를 실측 출력). 문서(README) 부재에도 코어 무결성 진단은 통과.
|
|
158
158
|
|
|
159
159
|
---
|
|
160
160
|
|
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.
|
|
125
|
+
이 프로젝트는 Leerness v1.36.83 하네스를 사용합니다. 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.
|
|
179
|
+
Leerness v1.36.83는 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.81는 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.
|
|
200
|
+
현재 누적: **v1.9.x → 1.36.83 릴리스 태그 이력** (수백 라운드) · _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.
|
|
238
|
+
Last synced by Leerness v1.36.83: 2026-07-28
|
|
239
239
|
<!-- leerness:project-readme:end -->
|