leerness 1.19.0 → 1.21.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 +111 -0
- package/README.ko.md +1 -1
- package/README.md +11 -9
- package/bin/leerness.js +208 -47
- package/docs/clean-room-evaluations.md +67 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,116 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.21.0 — 2026-06-15 — 🛡️ [안정화/Stable] 영어 온보딩 첫걸음 + 검증 강화 안정 minor
|
|
4
|
+
|
|
5
|
+
**🛡️ 안정화(Stable) minor — 비한국어 사용자에게 영어 온보딩을 실제로 전달.** 직전 minor(1.20.0) 이후 누적된 패치 2건(1.20.1~1.20.2)을 검증·통합해 npm 공개. R-0011 정책의 12번째 stable minor. 핵심: **영어 첫 화면(opt-in)** 이 npm 사용자에게 닿고, 검증 플래그십의 정적 우회가 닫힘.
|
|
6
|
+
|
|
7
|
+
### 이번 minor 통합 (1.20.1~1.20.2)
|
|
8
|
+
- **🌐 CLI 영어화 Phase 1 (UR-0010, 사용자 지정 방향)**: 영어 README 정문 ↔ 한국어 CLI 불일치(외부평가 #1 병목) 해소 시작. `--language en` / `LEERNESS_LANG=en` / en 으로 init 된 프로젝트에서 **init 시작하기 배너 전체 + handoff 헤드라인 라벨**이 영어로. 한국어 우선 정체성은 **기본값 유지**(영어는 명시 opt-in, system locale 미사용). 순수 `_uiLang`/`_tx`.
|
|
9
|
+
- **🛡️ 장식 no-op 정적 우회 차단 (검증)**: `"use strict"; module.exports={}` · `{}; //c` · `void 0;` · `;;;;` · `0;` 같은 "실로직 0" 파일이 정적 verify-claim 을 통과하던 우회를 차감식 residue 검사로 폐쇄(FP 0).
|
|
10
|
+
- **📄 README 플래그십 데모 정합**: task-id(T-0002) + "실파일 쓰면 같은 명령 통과" 정정 — 신규 유저 복붙이 실제로 동작. lens 헤더 동적 버전.
|
|
11
|
+
- **🔬 신뢰 투명성/렌즈 완전판** (1.19.x 누적 — 1.20.0 에 포함됨, 본 minor 는 1.20.x 패치 묶음).
|
|
12
|
+
|
|
13
|
+
### 잔여 (UR-0010 Phase 2+, 백로그)
|
|
14
|
+
- handoff 헤드라인 **항목 라벨**(drift·보안·skills·health·mem 등 ~20종) + session close·verify-claim·help·status 출력 영어화 — 단계적 확대.
|
|
15
|
+
|
|
16
|
+
### 검증 (회귀 0)
|
|
17
|
+
- **selftest 235→236** · **E2E 365/365** · 영어 배너/헤드라인 행위 + no-op 우회 차단 + 게시본 재실증.
|
|
18
|
+
|
|
19
|
+
### 안정화 표시 (R-0006)
|
|
20
|
+
CHANGELOG [안정화/Stable] · git tag (Stable) · GitHub release (`--latest`) · npm dist-tag `stable` 시도.
|
|
21
|
+
|
|
22
|
+
## 1.20.2 — 2026-06-15 — CLI 영어화 Phase 1: 첫 화면(init 배너·handoff 헤드라인) 영어 opt-in (UR-0010)
|
|
23
|
+
|
|
24
|
+
**🌐 "어떤 언어, 어떤 AI 에이전트로 작업하든"의 실질 첫걸음.** 외부평가가 꼽은 단일 최대 병목 — 영어 README 정문 ↔ 한국어 CLI 불일치 — 를 단계적으로 해소 시작. 한국어 우선 정체성은 기본값으로 유지하고, **영어는 명시 opt-in**.
|
|
25
|
+
|
|
26
|
+
### 변경 (UR-0010 Phase 1)
|
|
27
|
+
- **UI 언어 해석 `_uiLang(root)`**: 우선순위 `--language` 플래그 > `LEERNESS_LANG` env > `.harness/manifest.json` 의 language(init 선택) > **`ko`(기본)**. system locale 은 의도적으로 미사용(영어 OS 한국 사용자 놀람 방지) — 영어는 항상 명시 opt-in.
|
|
28
|
+
- **init 시작하기 배너 영어화**: `--language en`(또는 LEERNESS_LANG=en, 또는 en 으로 init 된 프로젝트)에서 "Get started (3 steps)" + 메모리/인과/보안/MCP/release 섹션 전체 영어로. 비한국어 신규 유저의 첫 화면이 더 이상 한국어 벽이 아님.
|
|
29
|
+
- **handoff 헤드라인 라벨 영어화**: `📊 헤드라인 (버전태그…)` → 영어 시 `📊 Headline`(버전태그 노이즈 제거).
|
|
30
|
+
- 순수 함수 `_uiLang`/`_tx` 분리(단위 테스트). **한국어 기본 회귀 0**(플래그/env/manifest 없으면 그대로 한국어).
|
|
31
|
+
|
|
32
|
+
### 잔여 (UR-0010 Phase 2+, 백로그)
|
|
33
|
+
- handoff 헤드라인 **항목 라벨**(보안 OK·skills·health…) 영어화, 그 외 명령(session close·verify-claim 출력·help·status 등) 영어화 — 단계적 확대. Phase 1 은 첫인상 표면(배너+헤드라인 프리픽스)에 한정.
|
|
34
|
+
|
|
35
|
+
### 검증 (회귀 0)
|
|
36
|
+
- **selftest 235→236** (`_uiLang` 해석 4종 + `_tx` + 첫화면 분기 소스가드) · 행위: `--language en` → 영어 배너/헤드라인, 플래그 없음 → 한국어 유지 · **E2E 365/365**.
|
|
37
|
+
- patch(1.20.2) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
38
|
+
|
|
39
|
+
## 1.20.1 — 2026-06-15 — 1.20.0 외부평가 채택: 장식 no-op 우회 차단 + README 데모 정합
|
|
40
|
+
|
|
41
|
+
**🔎 게시본 1.20.0 신규 멀티모델 클린룸 평가(5축) 채택 — 맹신 X로 재현된 것만.** 검증(8)·정직성/보안(9)·품질렌즈(8)는 GPT-5.5 대비 뚜렷이 상승했고, 평가가 찾은 재현 가능한 P2 두 가지(검증 정적모드 우회 + README 데모 부정확)를 닫음.
|
|
42
|
+
|
|
43
|
+
### 변경 (1.20.0 외부평가 재현 채택)
|
|
44
|
+
- **🛡️ 장식된 no-op 정적 우회 차단 (검증, P2)**: 빈껍데기 검출이 순수 빈-export 형태만 잡던 것 — `"use strict"; module.exports={}`·`{}; // comment`·`module.exports={}; void 0;`·`;;;;`·`0;` 같은 "실로직 0" 파일이 정적 `verify-claim`을 exit 0 통과하던 우회 폐쇄. `_vcImplIsEmpty` 에 **차감식(residue) 검사** 추가 — 디렉티브 프롤로그 + 빈 export + no-op 리터럴/문을 제거하고 의미 토큰이 하나도 안 남으면 스텁. FP 0(실코드·`require` 재노출·이름붙은 export·`module.exports=0` 같은 의도적 값은 통과). `let x=1` 류 무의미 선언은 식별자가 남아 통과 — AST 토큰화 필요(백로그).
|
|
45
|
+
- **📄 README 플래그십 데모 정합 (온보딩, P2)**: ① task-id 수정 — init 이 T-0001(계획 task)을 차지하므로 `task add` 는 T-0002 생성, 데모를 실제 흐름(출력된 id 사용)으로. ② "실파일+실테스트 쓰면 같은 명령 통과" 정정 — evidence 의 테스트 개수 주장은 실측과 대조되므로(거짓이면 거부) 정직한 evidence + `--run-tests --test-cmd` 안내로 교체. 신규 유저 복붙이 실제로 동작.
|
|
46
|
+
- **🔖 lens 헤더 버전 정합 (P3)**: 하드코딩 `(1.18.3)` → 동적 `(v${VERSION})`.
|
|
47
|
+
|
|
48
|
+
### 백로그 등록 (평가 추천 우선순위, 이번 라운드 미포함)
|
|
49
|
+
- **UR-0010 (최우선)**: CLI UX 영어화 — `--language en` 이 템플릿 파일만 바꾸고 CLI 자체 출력(init 배너·handoff 헤드라인·help)은 한국어 유지. 영어 README 정문 ↔ 한국어 CLI 불일치가 비한국어 시장의 단일 최대 병목(거대 작업, UR-0042 잔여 통합).
|
|
50
|
+
- **UR-0011**: 표면 core-vs-extended 티어링 — 84커맨드+85툴 평면 노출, banner/about/commands/help 의 "start here" 4종 불일치 → 핵심8 표준화 + `--help` 정리.
|
|
51
|
+
- **UR-0012**: secret scan AWS AKIA 키 ID + 추가 prefix 커버리지.
|
|
52
|
+
|
|
53
|
+
### 검증 (회귀 0)
|
|
54
|
+
- **selftest 235** (no-op 우회 6종 차단 + FP 가드 3종 행위) · 5종 우회 재현 차단·FP 0 확인 · README 데모 재현(task-id·count 정합) · **E2E 365/365**.
|
|
55
|
+
- patch(1.20.1) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
56
|
+
|
|
57
|
+
## 1.20.0 — 2026-06-15 — 🛡️ [안정화/Stable] 품질 렌즈 완전판 + 신뢰 투명성 안정 minor
|
|
58
|
+
|
|
59
|
+
**🛡️ 안정화(Stable) minor — "AI가 스스로 질문하고 행동하도록" 완성.** 직전 minor(1.19.0) 이후 누적된 패치 3건(1.19.1~1.19.3)을 검증·통합해 npm 공개. R-0011 정책의 11번째 stable minor. 핵심 테마: **사용자 자기질문 품질 렌즈를 정적 명령에서 완전한 워크플로 기능으로** + 신뢰 투명성(클린룸 평가 공개).
|
|
60
|
+
|
|
61
|
+
### 이번 minor 통합 (1.19.1~1.19.3)
|
|
62
|
+
- **🧭 품질 렌즈 완전판 (UR-0003, 사용자 핵심 철학)**: 1.18.3 의 정적 `lens` 명령을 3단계로 완성 —
|
|
63
|
+
- **v2 (1.19.2)**: `verify-claim` 이 완료를 검증하는 바로 그 순간, 주장된 파일 확장자로 관련 렌즈 질문을 advisory 로 노출(결정적 매핑, 게이트 아님). 코드: "선임 개발자가 복잡하다 느끼지 않을까?" / 디자인: "이쁘고 직관적인가?".
|
|
64
|
+
- **v3 (1.19.3)**: 프로젝트별 커스텀 렌즈 `.harness/quality-lenses.json` — 게임은 "60fps 끊김?", 웹은 접근성을 자기 기준에 추가. 내장+커스텀 불변 병합(읽기만, 쓰기 명령 없음).
|
|
65
|
+
- lens 도메인 대소문자/공백 정규화(1.19.1).
|
|
66
|
+
- **🔬 신뢰 투명성 (GPT-5.5 #5, UR-0009, 1.19.1)**: `docs/clean-room-evaluations.md` 공개 — README "독립 클린룸 평가" 주장을 확인 가능하게(방법론·4개 평가·**정직한 한계**). 과대였던 "5개" 수치를 검증 가능한 서술로 교체.
|
|
67
|
+
- **🛡️ 19th 버그헌트 (1.19.1)**: 1.18.x 신규 표면(권한 술어·빈껍데기 정규식·lens·audit) 엣지 14종 + 크래시 벡터 — 견고 확인(버그 0, lens 정규화만 수정).
|
|
68
|
+
|
|
69
|
+
### 검증 (회귀 0)
|
|
70
|
+
- **selftest 231→235** · **E2E 365/365** · 렌즈 v2/v3 행위 재현 + 클린룸 문서 + 게시본 재실증.
|
|
71
|
+
|
|
72
|
+
### 안정화 표시 (R-0006)
|
|
73
|
+
CHANGELOG [안정화/Stable] · git tag (Stable) · GitHub release (`--latest`) · npm dist-tag `stable` 시도.
|
|
74
|
+
|
|
75
|
+
## 1.19.3 — 2026-06-14 — 품질 렌즈 완전판 v3: 프로젝트별 커스텀 렌즈 (UR-0003)
|
|
76
|
+
|
|
77
|
+
**🧭 렌즈를 "프로젝트 인지"로.** 내장 5종(code/design/docs/test/security)에 더해, 프로젝트가 자기 품질 기준을 추가할 수 있습니다 — 게임 프로젝트는 "60fps에서 끊기지 않나?", 웹 프로젝트는 접근성("키보드만으로 모든 기능 가능?")을 자기질문에 넣습니다. 사용자의 "각 분야 고려" 철학을 프로젝트 맥락까지 확장.
|
|
78
|
+
|
|
79
|
+
### 변경 (UR-0003 렌즈 완전판 v3)
|
|
80
|
+
- **프로젝트 커스텀 렌즈**: `.harness/quality-lenses.json` 을 `lens` 와 verify-claim advisory 가 내장 catalog 와 **병합**. 포맷 `{ "domains": { "code": { "questions": ["..."] }, "a11y": { "title":"접근성", "persona":"...", "questions":[...] } } }`. 기존 도메인 → 질문 추가(dedup, 최대 8), 신규 도메인 → 추가. 표시에 `[프로젝트]`/`[+프로젝트 질문]` 라벨.
|
|
81
|
+
- **읽기-병합만, 쓰기 명령 없음 (R-0006 보수)**: leerness 는 JSON 을 읽어 병합·표시만 함(쓰기 경로 위험 0). JSON 은 AI/사용자가 편집 — 커스텀 없으면 편집 방법을 `lens` 가 안내. 순수 함수 `_mergeLensCatalog`(불변, 내장 catalog 미오염) + `_loadProjectLenses`.
|
|
82
|
+
- 잔여(UR-0003 완전판): handoff 힌트, MCP 노출, 인과관계 자동 추론 — 백로그.
|
|
83
|
+
|
|
84
|
+
### 검증 (회귀 0)
|
|
85
|
+
- **selftest 234→235** (병합 행위: 기존 도메인 질문 추가·신규 도메인·잘못된 입력 무시·내장 불변성) · 프로젝트 커스텀 렌즈 행위 재현(게임 fps + a11y 도메인 병합 표시) · **E2E 365/365**.
|
|
86
|
+
- patch(1.19.3) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
87
|
+
|
|
88
|
+
## 1.19.2 — 2026-06-14 — 품질 렌즈 완전판 v2: verify-claim 완료-검증 순간에 분야별 자기질문 (UR-0003)
|
|
89
|
+
|
|
90
|
+
**🧭 렌즈를 "수동 참조 명령"에서 "완료 검증 흐름의 일부"로.** 1.18.3 의 `leerness lens` 는 AI 가 직접 호출해야만 보였습니다. 이제 `verify-claim` 이 **완료를 검증하는 바로 그 순간**(사용자 표현: "완료 선언 전 자기질문") 주장된 파일의 종류에 맞는 렌즈 질문을 advisory 로 노출 — AI 의 자기질문이 수동 습관이 아니라 워크플로의 일부가 됩니다.
|
|
91
|
+
|
|
92
|
+
### 변경 (UR-0003 렌즈 완전판 v2)
|
|
93
|
+
- **verify-claim 렌즈 advisory**: done 주장 검증 시, 주장된 파일 확장자로 관련 렌즈 도메인을 **결정적으로** 판정(`_lensDomainsForFiles` — 퍼지 키워드 추론 아님)해 해당 분야 질문을 노출. 예: `pay.js + app.css` → `code`(선임 개발자가 복잡하다 느끼지 않을까?) + `design`(이쁘고 직관적인가?). 매핑: UI/스타일(css/scss/tsx/jsx/vue/html)→design, 문서(md/mdx/rst)→docs, 테스트파일→test, 실코드(js/ts/py/…)→code. 최대 2개(클러터 방지), 고정 순서(code 먼저).
|
|
94
|
+
- **advisory 전용** — 게이트가 아님(exit code 불변). 기계검증(파일/테스트/스텁)을 통과해도 "사람이 보기에 좋은가"는 별개라는 점을 완료 순간에 상기. `--json` 경로엔 영향 없음(human 출력 한정).
|
|
95
|
+
- 잔여(UR-0003 완전판): 프로젝트별 커스텀 렌즈(`.harness/quality-lenses.json`), handoff 힌트, MCP 노출, 인과관계 자동 추론 — 백로그.
|
|
96
|
+
|
|
97
|
+
### 검증 (회귀 0)
|
|
98
|
+
- **selftest 233→234** (`_lensDomainsForFiles` 행위: 단일/혼합/문서/테스트/빈/최대2 매핑) · verify-claim advisory 행위 재현(혼합 파일 → code+design) · **E2E 365/365**.
|
|
99
|
+
- patch(1.19.2) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
100
|
+
|
|
101
|
+
## 1.19.1 — 2026-06-14 — 19th 버그헌트(신규 표면 견고 확인) + 클린룸 평가 공개 (GPT-5.5 #5)
|
|
102
|
+
|
|
103
|
+
**🔬 갓 배포한 1.19.0의 신규 표면을 적대적으로 헌트 — 표면은 견고, 사소 1건만 수정 + 신뢰증거 공개.** 1.18.x 추가분(권한 술어·빈껍데기 정규식·lens·audit)을 엣지케이스로 직접 탐침: 순수 술어 14종 + CLI 크래시/주입 벡터 — **크래시·스택트레이스·오판 0**. 유일 발견은 `lens` 도메인 대소문자 미정규화.
|
|
104
|
+
|
|
105
|
+
### 변경
|
|
106
|
+
- **🧭 lens 도메인 정규화 (19th 헌트)**: `leerness lens Code` / `lens CODE ` 도 인식(trim + toLowerCase). 이전엔 정확한 소문자만 허용해 사소한 오류.
|
|
107
|
+
- **🔬 클린룸 평가 공개 (GPT-5.5 #5, UR-0009)**: `docs/clean-room-evaluations.md` 신설 — README 의 "독립 클린룸 평가로 검증" 주장을 **확인 가능하게**. 방법론(게시본 npm 설치·행위 검증·적대 시도), 수행한 4개 평가(5축 현장조사·1.18.0/1.19.0 게시본 재실증·1.18.2 적대 스텁 워크플로), 그리고 **정직한 한계**(휴리스틱이지 의미검증 아님 / 커버리지·mutation·제3자 재현은 미해결)를 명시. README(영/한)에서 링크. 과대홍보였던 "5개" 수치를 검증 가능한 서술로 교체.
|
|
108
|
+
- 잔여(UR-0009): 코드 커버리지/mutation testing 은 spawn 기반 e2e 특성상 별도 작업으로 백로그.
|
|
109
|
+
|
|
110
|
+
### 검증 (회귀 0)
|
|
111
|
+
- **selftest 231→233** (lens 정규화 + 클린룸 문서 행위 가드) · 순수 술어 14종 엣지 통과 · 크래시 벡터 0 · **E2E 365/365**.
|
|
112
|
+
- patch(1.19.1) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
113
|
+
|
|
3
114
|
## 1.19.0 — 2026-06-14 — 🛡️ [안정화/Stable] 검증 하네스 강화 + 정직한 프레이밍 안정 minor
|
|
4
115
|
|
|
5
116
|
**🛡️ 안정화(Stable) minor — "거짓 완료를 못 하게" 만드는 검증 계층을 한층 더 단단하게.** 직전 minor(1.18.0) 이후 누적된 패치 4건(1.18.1~1.18.4)을 검증·통합해 npm 공개. R-0011 정책의 10번째 stable minor. 핵심 테마: **검증 계층 강화(비-JS 거짓FAIL 해소 + 위장 스텁 차단) + AI 자기질문 품질 렌즈 + 정직한 프레이밍(협조 vs 강제, 영문 우선)**.
|
package/README.ko.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
> **어떤 언어, 어떤 AI 에이전트로 작업하든 — "증거 없이는 끝났다고 말할 수 없게" 만드는 AI 코딩 운영 레이어.** 코드를 대신 쓰는 도구가 아니라, AI 에이전트의 **기억·인수인계·검증·감사·보안 가드**를 프로젝트에 영속화하는 CLI + MCP 서버입니다. (이 포지셔닝은
|
|
14
|
+
> **어떤 언어, 어떤 AI 에이전트로 작업하든 — "증거 없이는 끝났다고 말할 수 없게" 만드는 AI 코딩 운영 레이어.** 코드를 대신 쓰는 도구가 아니라, AI 에이전트의 **기억·인수인계·검증·감사·보안 가드**를 프로젝트에 영속화하는 CLI + MCP 서버입니다. (이 포지셔닝은 독립 클린룸 평가 — Python/Node/Rust 실개발·에이전트 교대·검증기 적대 공격 — 로 확인됐습니다. 방법론·결과·정직한 한계: [docs/clean-room-evaluations.md](./docs/clean-room-evaluations.md))
|
|
15
15
|
|
|
16
16
|
[](https://www.npmjs.com/package/leerness) ·  · **런타임 의존성 0** · **install-script 0** · offline-first · Node ≥ 18 · MIT
|
|
17
17
|
|
package/README.md
CHANGED
|
@@ -28,12 +28,14 @@ npx leerness handoff . # everything your AI should know right now, in on
|
|
|
28
28
|
Your project now has agent-independent memory. To see the flagship feature — catching a false "done" claim:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npx leerness task add "Implement payment API"
|
|
32
|
-
npx leerness task update T-
|
|
33
|
-
npx leerness verify-claim T-
|
|
31
|
+
npx leerness task add "Implement payment API" # prints the new id, e.g. T-0002 — use it below
|
|
32
|
+
npx leerness task update T-0002 --status done --evidence "payment.js implemented + tested"
|
|
33
|
+
npx leerness verify-claim T-0002 # exit 1 — payment.js does not exist. Claim rejected.
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Now actually write `payment.js`, then run the **same** `verify-claim T-0002` → it exits 0. That is the whole idea: **"done" must match reality.**
|
|
37
|
+
|
|
38
|
+
> Tip: if your evidence claims a specific test count (e.g. "5 tests passed"), leerness measures the real count and rejects a mismatch — so claim only what's true, or add `--run-tests --test-cmd "<your test cmd>"` to verify by running them.
|
|
37
39
|
|
|
38
40
|
> Want a smaller footprint? `leerness init . --minimal` installs only the core memory + verification files instead of the full set.
|
|
39
41
|
|
|
@@ -63,7 +65,7 @@ Built-in harnesses remember what the AI **said**. leerness verifies what the AI
|
|
|
63
65
|
| Secrets · encoding · drift guards | none | `scan secrets` · `encoding check` · `drift check --auto-fix` — CI-ready |
|
|
64
66
|
| Lock-in | one vendor | any agent, any language, 0 runtime dependencies |
|
|
65
67
|
|
|
66
|
-
This positioning was verified by **
|
|
68
|
+
This positioning was verified by **independent clean-room evaluations** — fresh `npm install` into temp dirs, driven by behavior only, including adversarial attacks against the verifier itself (fake tests, comment-only stubs, inflated test counts — all rejected). Methodology, results, and honest limitations: **[docs/clean-room-evaluations.md](./docs/clean-room-evaluations.md)**.
|
|
67
69
|
|
|
68
70
|
---
|
|
69
71
|
|
|
@@ -102,7 +104,7 @@ MIT
|
|
|
102
104
|
<!-- leerness:project-readme:start -->
|
|
103
105
|
## Leerness Project Harness
|
|
104
106
|
|
|
105
|
-
이 프로젝트는 Leerness v1.
|
|
107
|
+
이 프로젝트는 Leerness v1.21.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
106
108
|
|
|
107
109
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
108
110
|
|
|
@@ -156,7 +158,7 @@ leerness memory restore decision <date|title>
|
|
|
156
158
|
|
|
157
159
|
### MCP server (외부 AI 통합)
|
|
158
160
|
|
|
159
|
-
Leerness v1.
|
|
161
|
+
Leerness v1.21.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
|
|
160
162
|
|
|
161
163
|
```jsonc
|
|
162
164
|
// 카테고리별
|
|
@@ -177,7 +179,7 @@ Leerness v1.19.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
|
|
|
177
179
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
178
180
|
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) 다음 라운드 예약.
|
|
179
181
|
|
|
180
|
-
현재 누적: **70 라운드 (1.9.40 → 1.
|
|
182
|
+
현재 누적: **70 라운드 (1.9.40 → 1.21.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
181
183
|
|
|
182
184
|
### 성능 가이드 (1.9.140 측정)
|
|
183
185
|
|
|
@@ -215,6 +217,6 @@ leerness release pack --close --auto-main-push
|
|
|
215
217
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
216
218
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
217
219
|
|
|
218
|
-
Last synced by Leerness v1.
|
|
220
|
+
Last synced by Leerness v1.21.0: 2026-06-15
|
|
219
221
|
<!-- leerness:project-readme:end -->
|
|
220
222
|
|
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.
|
|
35
|
+
const VERSION = '1.21.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') 시 호스트 프로세스 오염.
|
|
@@ -252,6 +252,22 @@ function detectLanguageValue(root, value = 'auto') {
|
|
|
252
252
|
// ③ en 폴백
|
|
253
253
|
return 'en';
|
|
254
254
|
}
|
|
255
|
+
// 1.20.2 (UR-0010 CLI 영어화 Phase 1): UI 출력 언어 해석 — 한국어 우선 기본, 영어 opt-in.
|
|
256
|
+
// 우선순위: --language 플래그 > LEERNESS_LANG env > .harness/manifest.json 의 language(init 선택) > 'ko'.
|
|
257
|
+
// (system locale 은 의도적으로 미사용 — 영어 OS 한국 사용자 놀람 방지. 영어는 명시 opt-in.)
|
|
258
|
+
function _uiLang(root) {
|
|
259
|
+
try {
|
|
260
|
+
const flag = String(arg('--language', '') || '').toLowerCase();
|
|
261
|
+
if (flag === 'en' || flag === 'ko') return flag;
|
|
262
|
+
const env = String(process.env.LEERNESS_LANG || '').toLowerCase();
|
|
263
|
+
if (env === 'en' || env === 'ko') return env;
|
|
264
|
+
const mf = path.join(absRoot(root || process.cwd()), '.harness', 'manifest.json');
|
|
265
|
+
if (exists(mf)) { const l = String((JSON.parse(read(mf)) || {}).language || '').toLowerCase(); if (l === 'en' || l === 'ko') return l; }
|
|
266
|
+
} catch {}
|
|
267
|
+
return 'ko';
|
|
268
|
+
}
|
|
269
|
+
// ko/en 쌍에서 해석된 UI 언어로 선택 (Phase 1: 첫 화면 한정 사용).
|
|
270
|
+
function _tx(lang, ko, en) { return lang === 'en' ? en : ko; }
|
|
255
271
|
function fm(role, readWhen, updateWhen, body) {
|
|
256
272
|
return `---\nleernessRole: ${role}\nreadWhen:\n${readWhen.map(x => ' - ' + x).join('\n')}\nupdateWhen:\n${updateWhen.map(x => ' - ' + x).join('\n')}\ndoNotStore:\n - 실제 토큰\n - 비밀번호\n - 운영 쿠키\n - 민감한 개인정보 원문\n---\n${MARK}\n${body}`;
|
|
257
273
|
}
|
|
@@ -3624,7 +3640,18 @@ function _selfTestCases() {
|
|
|
3624
3640
|
&& E('module.exports = Object.freeze({ a: 1 });\n') === false
|
|
3625
3641
|
&& E('module.exports = class { run(){ return 1; } };\n') === false
|
|
3626
3642
|
&& E('module.exports = (a,b) => a+b;\n') === false;
|
|
3627
|
-
|
|
3643
|
+
// 1.20.1 (1.20.0 외부평가 재현): 장식된 no-op 우회(true 여야 함) — 디렉티브 프롤로그 + no-op 문
|
|
3644
|
+
const noop = E('"use strict";\nmodule.exports = {};\n') === true
|
|
3645
|
+
&& E('{}; // c\n') === true
|
|
3646
|
+
&& E('module.exports = {};\nvoid 0;\n') === true
|
|
3647
|
+
&& E(';;;;\n') === true
|
|
3648
|
+
&& E('0;\n') === true
|
|
3649
|
+
&& E("'use strict';\nexports.default = {};\n") === true;
|
|
3650
|
+
// 1.20.1 FP 가드(false 여야 함): 단일 의미 토큰만 있어도 통과
|
|
3651
|
+
const noopFP = E('"use strict";\nfunction f(){ return 1; }\nmodule.exports = { f };\n') === false
|
|
3652
|
+
&& E('module.exports = 0;\n') === false // 0 을 export 하는 의도적 값 — 스텁 아님
|
|
3653
|
+
&& E('module.exports = require("./x"); void 0;\n') === false;
|
|
3654
|
+
return base && bypass && real && noop && noopFP;
|
|
3628
3655
|
} },
|
|
3629
3656
|
{ name: '위장 스텁 차단 (1.18.2): stub 루프 _vcImplIsEmpty 사용 + 메시지 + FILE_EXTS java/php 정합 (소스 가드)', run: () => {
|
|
3630
3657
|
const src = read(__filename);
|
|
@@ -3657,6 +3684,59 @@ function _selfTestCases() {
|
|
|
3657
3684
|
const a = read(path.join(path.dirname(__filename), '..', 'lib', 'audit.js'));
|
|
3658
3685
|
return a.includes('Last synced by Leerness v') && a.includes('readme_synced_version_stale');
|
|
3659
3686
|
} },
|
|
3687
|
+
{ name: '19th 헌트 (1.19.1): lens 도메인 대소문자/공백 정규화 (소스 가드)', run: () => {
|
|
3688
|
+
const src = read(__filename);
|
|
3689
|
+
return src.includes("if (domain != null) domain = String(domain).trim().toLowerCase();");
|
|
3690
|
+
} },
|
|
3691
|
+
{ name: '렌즈 완전판 v3 (1.19.3, UR-0003): 프로젝트 커스텀 렌즈 병합 (행위)', run: () => {
|
|
3692
|
+
const M = _mergeLensCatalog;
|
|
3693
|
+
const a = M({ code: { title: '코드', persona: 'p', questions: ['Q1'], affects: [] } }, { code: { questions: ['Q1', 'Q2'] } });
|
|
3694
|
+
const appendOk = a.code.questions.length === 2 && a.code.questions[1] === 'Q2' && a.code._customAdded === true;
|
|
3695
|
+
const b = M({ code: { title: '코드', persona: 'p', questions: ['Q'], affects: [] } }, { a11y: { title: '접근성', persona: '스크린리더', questions: ['키보드로 가능?'] } });
|
|
3696
|
+
const newOk = b.a11y && b.a11y.title === '접근성' && b.a11y._custom === true && b.code.questions.length === 1;
|
|
3697
|
+
const c = M({ code: { questions: ['Q'], affects: [] } }, { bad: { title: 'x' }, nope: null });
|
|
3698
|
+
const ignoreOk = !c.bad && !c.nope && c.code.questions.length === 1;
|
|
3699
|
+
const before = LENS_CATALOG.code.questions.length;
|
|
3700
|
+
M(LENS_CATALOG, { code: { questions: ['임시'] } });
|
|
3701
|
+
const immutableOk = LENS_CATALOG.code.questions.length === before;
|
|
3702
|
+
return appendOk && newOk && ignoreOk && immutableOk;
|
|
3703
|
+
} },
|
|
3704
|
+
{ name: '렌즈 완전판 v2 (1.19.2, UR-0003): 파일 확장자 → 렌즈 도메인 매핑 (행위)', run: () => {
|
|
3705
|
+
const F = _lensDomainsForFiles;
|
|
3706
|
+
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
3707
|
+
return eq(F(['src/api.js']), ['code'])
|
|
3708
|
+
&& eq(F(['ui/Button.tsx', 'styles/app.css']), ['design']) // UI/스타일 → design (코드 ext 아닌 tsx 는 design 우선)
|
|
3709
|
+
&& eq(F(['src/pay.js', 'src/Button.tsx']), ['code', 'design']) // 혼합 → code 먼저, design (최대 2)
|
|
3710
|
+
&& eq(F(['README.md']), ['docs'])
|
|
3711
|
+
&& eq(F(['tests/foo.test.js']), ['code', 'test']) // 테스트파일(.js) → code+test (고정 순서: code 먼저)
|
|
3712
|
+
&& eq(F([]), [])
|
|
3713
|
+
&& eq(F(['a.css', 'b.md', 'c.js', 'd.test.js']).length, 2); // 최대 2개 클러터 방지
|
|
3714
|
+
} },
|
|
3715
|
+
{ name: 'GPT-5.5 평가 #5 (1.19.1, UR-0009): 클린룸 평가 문서 공개 + 한계 명시 (행위)', run: () => {
|
|
3716
|
+
const dp = path.join(path.dirname(__filename), '..', 'docs', 'clean-room-evaluations.md');
|
|
3717
|
+
if (!exists(dp)) return true; // 패키지에 없으면 스킵(설치본 안전)
|
|
3718
|
+
const d = read(dp);
|
|
3719
|
+
return /AI clean-room evaluations/i.test(d) && /heuristic, not semantic/i.test(d) && /npm i leerness@/.test(d);
|
|
3720
|
+
} },
|
|
3721
|
+
{ name: 'CLI 영어화 Phase 1 (1.20.2, UR-0010): _uiLang 해석(flag>env>manifest>ko) + 첫화면 _t 적용 (행위+소스)', run: () => {
|
|
3722
|
+
const save = process.argv; const saveEnv = process.env.LEERNESS_LANG;
|
|
3723
|
+
try {
|
|
3724
|
+
// 기본 ko (한국어 우선 정체성 보존)
|
|
3725
|
+
process.argv = ['node', 'h', 'handoff']; delete process.env.LEERNESS_LANG;
|
|
3726
|
+
if (_uiLang('/no/such/dir') !== 'ko') return false;
|
|
3727
|
+
// --language en 플래그 → en
|
|
3728
|
+
process.argv = ['node', 'h', 'handoff', '--language', 'en'];
|
|
3729
|
+
if (_uiLang('/no/such/dir') !== 'en') return false;
|
|
3730
|
+
// LEERNESS_LANG env → en (플래그 없을 때)
|
|
3731
|
+
process.argv = ['node', 'h', 'handoff']; process.env.LEERNESS_LANG = 'en';
|
|
3732
|
+
if (_uiLang('/no/such/dir') !== 'en') return false;
|
|
3733
|
+
// _tx 선택
|
|
3734
|
+
if (_tx('en', '가', 'A') !== 'A' || _tx('ko', '가', 'A') !== '가') return false;
|
|
3735
|
+
} finally { process.argv = save; if (saveEnv === undefined) delete process.env.LEERNESS_LANG; else process.env.LEERNESS_LANG = saveEnv; }
|
|
3736
|
+
// 첫화면(배너/헤드라인)이 언어 분기 사용
|
|
3737
|
+
const src = read(__filename);
|
|
3738
|
+
return src.includes("const L = _uiLang(arg('--path', process.cwd()));") && src.includes("_uiLang(root) === 'en' ? '📊 Headline'");
|
|
3739
|
+
} },
|
|
3660
3740
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3661
3741
|
];
|
|
3662
3742
|
}
|
|
@@ -4235,23 +4315,74 @@ const LENS_CATALOG = {
|
|
|
4235
4315
|
affects: ['code', 'test'], affectsNote: '보안 가드를 넣었다면 우회/오탐 테스트가 따라와야 함'
|
|
4236
4316
|
}
|
|
4237
4317
|
};
|
|
4318
|
+
// 1.19.3 (UR-0003 렌즈 완전판 v3): 프로젝트별 커스텀 렌즈 — .harness/quality-lenses.json 읽기-병합(쓰기 명령 없음, AI/사용자가 편집).
|
|
4319
|
+
// 포맷: { "domains": { "code": { "questions": ["추가 질문"] }, "a11y": { "title":"접근성", "persona":"스크린리더 사용자", "questions":[...], "affects":["design"] } } }
|
|
4320
|
+
function _loadProjectLenses(root) {
|
|
4321
|
+
try {
|
|
4322
|
+
const p = path.join(absRoot(root || process.cwd()), '.harness', 'quality-lenses.json');
|
|
4323
|
+
if (!exists(p)) return {};
|
|
4324
|
+
const j = JSON.parse(read(p));
|
|
4325
|
+
return (j && typeof j === 'object' && j.domains && typeof j.domains === 'object') ? j.domains : {};
|
|
4326
|
+
} catch { return {}; }
|
|
4327
|
+
}
|
|
4328
|
+
// 내장 + 프로젝트 커스텀 병합. 기존 도메인 → 질문 추가(dedup, 최대 8); 신규 도메인 → 기본값으로 추가. _custom 플래그로 표시.
|
|
4329
|
+
function _mergeLensCatalog(builtin, custom) {
|
|
4330
|
+
const out = {};
|
|
4331
|
+
for (const [k, v] of Object.entries(builtin || {})) out[k] = Object.assign({}, v, { questions: (v.questions || []).slice(), affects: (v.affects || []).slice() });
|
|
4332
|
+
for (const [k, c] of Object.entries(custom || {})) {
|
|
4333
|
+
if (!c || typeof c !== 'object') continue;
|
|
4334
|
+
const cq = Array.isArray(c.questions) ? c.questions.filter(q => typeof q === 'string' && q.trim()) : [];
|
|
4335
|
+
if (out[k]) {
|
|
4336
|
+
const seen = new Set(out[k].questions);
|
|
4337
|
+
for (const q of cq) if (!seen.has(q)) { out[k].questions.push(q); seen.add(q); }
|
|
4338
|
+
out[k].questions = out[k].questions.slice(0, 8);
|
|
4339
|
+
out[k]._customAdded = cq.length > 0;
|
|
4340
|
+
} else if (cq.length) {
|
|
4341
|
+
out[k] = { title: (c.title || k), persona: (c.persona || '검토자'), questions: cq.slice(0, 8), affects: Array.isArray(c.affects) ? c.affects : [], affectsNote: c.affectsNote || '(프로젝트 정의)', _custom: true };
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
return out;
|
|
4345
|
+
}
|
|
4346
|
+
function _effectiveLensCatalog(root) { return _mergeLensCatalog(LENS_CATALOG, _loadProjectLenses(root)); }
|
|
4347
|
+
|
|
4348
|
+
// 1.19.2 (UR-0003 렌즈 완전판 v2): 주장된 파일 확장자 → 관련 품질 렌즈 도메인 (결정적, 키워드 추론 아님).
|
|
4349
|
+
// verify-claim 이 완료-검증 순간(사용자: "완료 선언 전 자기질문")에 해당 분야 질문을 advisory 로 노출하는 데 사용.
|
|
4350
|
+
function _lensDomainsForFiles(files) {
|
|
4351
|
+
const out = [];
|
|
4352
|
+
const arr = Array.isArray(files) ? files : [];
|
|
4353
|
+
const has = (re) => arr.some(f => typeof f === 'string' && re.test(f));
|
|
4354
|
+
if (has(/\.(css|scss|sass|less|styl|tsx|jsx|vue|svelte|html?|astro)$/i)) out.push('design'); // UI/스타일
|
|
4355
|
+
if (has(/\.(md|mdx|rst|adoc|txt)$/i)) out.push('docs'); // 문서
|
|
4356
|
+
if (has(/(^|[\\/])(test_[^\\/]+\.[a-z]+|[^\\/]+[._-]test\.[a-z]+|[^\\/]+\.spec\.[a-z]+)$|(^|[\\/])tests?[\\/]/i)) out.push('test'); // 테스트
|
|
4357
|
+
if (has(/\.(js|mjs|cjs|ts|py|rb|go|rs|java|cs|php)$/i)) out.push('code'); // 실코드
|
|
4358
|
+
// 순서: code 먼저(가장 일반), 그다음 design/docs/test. 중복 제거 + 최대 2개(클러터 방지).
|
|
4359
|
+
const ordered = ['code', 'design', 'docs', 'test'].filter(d => out.includes(d));
|
|
4360
|
+
return ordered.slice(0, 2);
|
|
4361
|
+
}
|
|
4238
4362
|
function lensCmd(domain, opts = {}) {
|
|
4239
4363
|
const jsonMode = !!opts.json || has('--json');
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
const
|
|
4364
|
+
// 1.19.1 (19th 버그헌트): 도메인 인자 정규화 — `lens Code` / `lens CODE ` 도 인식(대소문자·공백 무관).
|
|
4365
|
+
if (domain != null) domain = String(domain).trim().toLowerCase();
|
|
4366
|
+
// 1.19.3: 내장 + 프로젝트 커스텀(.harness/quality-lenses.json) 병합 catalog.
|
|
4367
|
+
const root = opts.root || arg('--path', process.cwd());
|
|
4368
|
+
const catalog = _effectiveLensCatalog(root);
|
|
4369
|
+
if (domain && !catalog[domain]) {
|
|
4370
|
+
return fail(`알 수 없는 렌즈: ${domain} — 유효값: ${Object.keys(catalog).join(', ')}`);
|
|
4371
|
+
}
|
|
4372
|
+
const picked = domain ? { [domain]: catalog[domain] } : catalog;
|
|
4244
4373
|
if (jsonMode) { log(JSON.stringify({ ok: true, lenses: picked }, null, 2)); return; }
|
|
4245
|
-
|
|
4374
|
+
const hasCustom = Object.values(catalog).some(l => l && (l._custom || l._customAdded));
|
|
4375
|
+
log(`# leerness lens — 분야별 자기질문 품질 렌즈 (v${VERSION})${hasCustom ? ' + 프로젝트 커스텀(.harness/quality-lenses.json)' : ''}`);
|
|
4246
4376
|
log(`완료 선언 전 해당 분야 질문에 스스로 답해보세요. "그렇다(통과)"라고 답할 수 없으면 아직 완료가 아닙니다.`);
|
|
4247
4377
|
for (const [key, l] of Object.entries(picked)) {
|
|
4248
4378
|
log('');
|
|
4249
|
-
log(`## ${key} (${l.title}) — 페르소나: ${l.persona}`);
|
|
4379
|
+
log(`## ${key} (${l.title}) — 페르소나: ${l.persona}${l._custom ? ' [프로젝트]' : (l._customAdded ? ' [+프로젝트 질문]' : '')}`);
|
|
4250
4380
|
l.questions.forEach((q, i) => log(` ${i + 1}. ${q}`));
|
|
4251
|
-
log(` ↔ 인과: ${key} 를 바꾸면 → ${l.affects.join(', ')} 질문도 다시 — ${l.affectsNote}`);
|
|
4381
|
+
log(` ↔ 인과: ${key} 를 바꾸면 → ${(l.affects || []).join(', ') || '(없음)'} 질문도 다시 — ${l.affectsNote}`);
|
|
4252
4382
|
}
|
|
4253
4383
|
log('');
|
|
4254
|
-
log(`사용: leerness lens <${Object.keys(
|
|
4384
|
+
log(`사용: leerness lens <${Object.keys(catalog).join('|')}> · 완료 검증과 함께: leerness verify-claim T-XXXX`);
|
|
4385
|
+
if (!hasCustom) log(`프로젝트 커스텀 렌즈: .harness/quality-lenses.json 에 { "domains": { "code": { "questions": ["..."] } } } 추가`);
|
|
4255
4386
|
}
|
|
4256
4387
|
|
|
4257
4388
|
function commandsCmd(root) {
|
|
@@ -8382,7 +8513,9 @@ function handoff(root) {
|
|
|
8382
8513
|
if (parts.length) {
|
|
8383
8514
|
const isTty = process.stdout && process.stdout.isTTY;
|
|
8384
8515
|
const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
8385
|
-
|
|
8516
|
+
// 1.20.2 (UR-0010 Phase 1): 헤드라인 라벨 UI 언어 적용 (영어 시 버전태그 노이즈 제거; 항목 라벨 영어화는 Phase 2).
|
|
8517
|
+
const _hl = _uiLang(root) === 'en' ? '📊 Headline' : '📊 헤드라인 (1.9.81/93/113/152/162/192/197/204/207/209/215/220/223/226)';
|
|
8518
|
+
log(cy(`${_hl}: ${parts.join(' · ')}`));
|
|
8386
8519
|
}
|
|
8387
8520
|
} catch {}
|
|
8388
8521
|
}
|
|
@@ -9807,7 +9940,19 @@ function _vcImplIsEmpty(body) {
|
|
|
9807
9940
|
}).filter(Boolean);
|
|
9808
9941
|
if (codeLines.length === 0) return true; // ① 코드 0줄
|
|
9809
9942
|
const joined = codeLines.join(' ').replace(/\s+/g, ' ').trim();
|
|
9810
|
-
|
|
9943
|
+
if (_VC_EMPTY_SHELL_RE.test(joined)) return true; // ② 빈 export 껍데기뿐
|
|
9944
|
+
// ③ 1.20.1 (1.20.0 외부평가 재현): 장식된 no-op 우회 — 디렉티브 프롤로그 + 빈 export + no-op 문만 남으면 스텁.
|
|
9945
|
+
// "use strict"; module.exports={} · {};//c · module.exports={};void 0; · ;;;; · 0; (정적모드 우회) 폐쇄. FP 0: 실코드는 식별자/키워드가 남음.
|
|
9946
|
+
// (let x=1 같은 무의미 선언은 식별자가 남아 통과 — AST 토큰 카운트 필요, 백로그.)
|
|
9947
|
+
const residue = joined
|
|
9948
|
+
.replace(/^\s*(['"])use strict\1\s*;?/i, '') // 디렉티브 프롤로그
|
|
9949
|
+
.replace(/(?:module\.)?exports(?:\.[A-Za-z0-9_$]+)?\s*=\s*(?:\{\s*\}|\[\s*\])\s*;?/g, '') // 빈 export
|
|
9950
|
+
.replace(/export\s+default\s*(?:\{\s*\}|\[\s*\])\s*;?/g, '')
|
|
9951
|
+
.replace(/export\s*\{\s*\}\s*;?/g, '')
|
|
9952
|
+
.replace(/\bvoid\s+0\b/g, '') // void 0
|
|
9953
|
+
.replace(/\b(?:null|undefined|false|true|0)\b/g, '') // 단독 no-op 리터럴
|
|
9954
|
+
.replace(/[\s;{}()[\],.]/g, ''); // 공백/구두점
|
|
9955
|
+
return residue === ''; // 의미 토큰이 하나도 안 남으면 스텁
|
|
9811
9956
|
}
|
|
9812
9957
|
|
|
9813
9958
|
function verifyClaimCmd(root, taskId) {
|
|
@@ -10141,6 +10286,19 @@ function verifyClaimCmd(root, taskId) {
|
|
|
10141
10286
|
if (claimsChecked || mustHaveEvidence) {
|
|
10142
10287
|
log('');
|
|
10143
10288
|
log(` ℹ 한계: 테스트 통과는 "의미적 구현 정확성"을 보장하지 않음 — evidence 가 해당 주장(수정 파일/테스트)을 직접 링크해야 신뢰도↑.`);
|
|
10289
|
+
// 1.19.2 (UR-0003 렌즈 완전판 v2): 완료-검증 순간에 분야별 자기질문 advisory — 주장 파일 확장자 기반(결정적).
|
|
10290
|
+
// 기계검증(파일/테스트/스텁)을 통과해도 "사람이 보기에 좋은가"는 별개 → AI 가 스스로 답하도록 권장(advisory, 게이트 아님).
|
|
10291
|
+
const _lensDoms = _lensDomainsForFiles(files);
|
|
10292
|
+
if (_lensDoms.length) {
|
|
10293
|
+
const _lensCat = _effectiveLensCatalog(root); // 1.19.3: 프로젝트 커스텀 질문도 포함
|
|
10294
|
+
log('');
|
|
10295
|
+
log(` 🧭 품질 렌즈 (완료 선언 전 자문 — advisory, 게이트 아님):`);
|
|
10296
|
+
for (const d of _lensDoms) {
|
|
10297
|
+
const l = _lensCat[d];
|
|
10298
|
+
if (l) log(` · ${d}(${l.title}): ${l.questions[0]}`);
|
|
10299
|
+
}
|
|
10300
|
+
log(` → 전체 질문: leerness lens ${_lensDoms[0]}`);
|
|
10301
|
+
}
|
|
10144
10302
|
}
|
|
10145
10303
|
if (overallFail) {
|
|
10146
10304
|
log('');
|
|
@@ -11513,43 +11671,46 @@ function _banner(opts = {}) {
|
|
|
11513
11671
|
cprint(' ' + C.green(padded) + C.dim('# ' + desc));
|
|
11514
11672
|
};
|
|
11515
11673
|
|
|
11674
|
+
// 1.20.2 (UR-0010 Phase 1): 첫 화면 배너 — UI 언어(한국어 우선, 영어 opt-in) 적용.
|
|
11675
|
+
const L = _uiLang(arg('--path', process.cwd()));
|
|
11676
|
+
const t = (ko, en) => (L === 'en' ? en : ko);
|
|
11516
11677
|
cprint('');
|
|
11517
|
-
cprint(C.bold(C.cyan(' ✨ 시작하기 (3단계면 끝)')));
|
|
11518
|
-
cmd('npx leerness init .', '1️⃣ 하네스 설치 + AI 도구 자동 연결');
|
|
11519
|
-
cmd('npx leerness handoff .', '2️⃣ 세션 시작 — 컨텍스트·기억·feature impact 자동 회수');
|
|
11520
|
-
cmd('npx leerness session close .', '3️⃣ 세션 종료 — 마감 통계 + 다음 라운드 추천');
|
|
11521
|
-
|
|
11522
|
-
section('🧠 메모리 5종 CRUD (1.9.142 — cascade 방지)');
|
|
11523
|
-
cmd('leerness task add "
|
|
11524
|
-
cmd('leerness decision add "
|
|
11525
|
-
cmd('leerness lesson save "
|
|
11526
|
-
cmd('leerness plan add "<milestone>"', '계획 단계 등록');
|
|
11527
|
-
cmd('leerness rule add "
|
|
11528
|
-
cmd('leerness feature add "
|
|
11529
|
-
|
|
11530
|
-
section('🔗 인과관계 + 영향 추적 (1.9.141~143)');
|
|
11531
|
-
cmd('leerness feature impact <F-XXXX>', '코드 변경 전 영향받는 feature 자동 회수');
|
|
11532
|
-
cmd('leerness feature list --json', '전체 그래프 + 엣지');
|
|
11533
|
-
cmd('leerness audit . --json', 'orphan/cycle 무결성 검증');
|
|
11534
|
-
|
|
11535
|
-
section('🛡 보안·드리프트·게으름 가드');
|
|
11536
|
-
cmd('leerness drift check . --auto-fix', 'drift + 보안 자동 회복');
|
|
11537
|
-
cmd('leerness lazy detect . --json', '거짓 완료/no test run 감지');
|
|
11538
|
-
cmd('leerness env sync .', '.env ↔ .env.example 동기화');
|
|
11539
|
-
cmd('leerness health . --json', '종합 헬스 (drift+보안+skill+feature)');
|
|
11540
|
-
|
|
11541
|
-
section(`🤖 외부 AI 통합 (MCP ${_mcpToolCount()} 도구)`); // 1.9.315 (UR-0054): 하드코딩 46 → 동적
|
|
11678
|
+
cprint(C.bold(C.cyan(t(' ✨ 시작하기 (3단계면 끝)', ' ✨ Get started (3 steps)'))));
|
|
11679
|
+
cmd('npx leerness init .', t('1️⃣ 하네스 설치 + AI 도구 자동 연결', '1️⃣ install the harness + auto-wire AI tools'));
|
|
11680
|
+
cmd('npx leerness handoff .', t('2️⃣ 세션 시작 — 컨텍스트·기억·feature impact 자동 회수', '2️⃣ start a session — context, memory, feature impact in one call'));
|
|
11681
|
+
cmd('npx leerness session close .', t('3️⃣ 세션 종료 — 마감 통계 + 다음 라운드 추천', '3️⃣ end a session — closing stats + next-round suggestion'));
|
|
11682
|
+
|
|
11683
|
+
section(t('🧠 메모리 5종 CRUD (1.9.142 — cascade 방지)', '🧠 Memory (5 surfaces)'));
|
|
11684
|
+
cmd('leerness task add "<title>"', t('progress-tracker 등록', 'add a task to progress-tracker'));
|
|
11685
|
+
cmd('leerness decision add "<title>" --reason "..."', t('되돌리기 어려운 결정 영구화', 'persist a hard-to-reverse decision'));
|
|
11686
|
+
cmd('leerness lesson save "<lesson>" --tag "..."', t('재발견 가능한 통찰 저장', 'save a rediscoverable insight'));
|
|
11687
|
+
cmd('leerness plan add "<milestone>"', t('계획 단계 등록', 'add a plan milestone'));
|
|
11688
|
+
cmd('leerness rule add "<rule>" --trigger every-X', t('자연어 영구 룰', 'natural-language standing rule'));
|
|
11689
|
+
cmd('leerness feature add "<feature>" --files "..."', t('Feature Graph 노드 (1.9.141)', 'Feature Graph node'));
|
|
11690
|
+
|
|
11691
|
+
section(t('🔗 인과관계 + 영향 추적 (1.9.141~143)', '🔗 Causality + impact tracking'));
|
|
11692
|
+
cmd('leerness feature impact <F-XXXX>', t('코드 변경 전 영향받는 feature 자동 회수', 'which features a code change affects'));
|
|
11693
|
+
cmd('leerness feature list --json', t('전체 그래프 + 엣지', 'full graph + edges'));
|
|
11694
|
+
cmd('leerness audit . --json', t('orphan/cycle 무결성 검증', 'orphan/cycle integrity check'));
|
|
11695
|
+
|
|
11696
|
+
section(t('🛡 보안·드리프트·게으름 가드', '🛡 Security · drift · laziness guards'));
|
|
11697
|
+
cmd('leerness drift check . --auto-fix', t('drift + 보안 자동 회복', 'drift + security auto-heal'));
|
|
11698
|
+
cmd('leerness lazy detect . --json', t('거짓 완료/no test run 감지', 'detect false-done / no-test-run'));
|
|
11699
|
+
cmd('leerness env sync .', t('.env ↔ .env.example 동기화', 'sync .env <-> .env.example'));
|
|
11700
|
+
cmd('leerness health . --json', t('종합 헬스 (drift+보안+skill+feature)', 'overall health (drift+security+skill+feature)'));
|
|
11701
|
+
|
|
11702
|
+
section(t(`🤖 외부 AI 통합 (MCP ${_mcpToolCount()} 도구)`, `🤖 External AI integration (MCP, ${_mcpToolCount()} tools)`)); // 1.9.315 (UR-0054): 하드코딩 46 → 동적
|
|
11542
11703
|
cmd('npx leerness mcp serve', 'stdio JSON-RPC server');
|
|
11543
|
-
cmd('leerness memory status . --json', '5 surface + featureGraph 한 호출');
|
|
11544
|
-
cmd('leerness memory archive list --query "kw"', 'DELETE 5종 archive 검색');
|
|
11545
|
-
cmd('leerness memory restore <surface> <target>', 'archive → active 복원');
|
|
11704
|
+
cmd('leerness memory status . --json', t('5 surface + featureGraph 한 호출', '5 surfaces + featureGraph in one call'));
|
|
11705
|
+
cmd('leerness memory archive list --query "kw"', t('DELETE 5종 archive 검색', 'search the archive of deleted items'));
|
|
11706
|
+
cmd('leerness memory restore <surface> <target>', t('archive → active 복원', 'restore archive -> active'));
|
|
11546
11707
|
|
|
11547
|
-
section('🚀 Release 자동화');
|
|
11548
|
-
cmd('leerness release pack --close --auto-main-push', '한 줄 release (1.9.140 main push 통합)');
|
|
11549
|
-
cmd('leerness release sync-main .', 'release branch → main 자동 fast-forward');
|
|
11708
|
+
section(t('🚀 Release 자동화', '🚀 Release automation'));
|
|
11709
|
+
cmd('leerness release pack --close --auto-main-push', t('한 줄 release (1.9.140 main push 통합)', 'one-line release (with main push)'));
|
|
11710
|
+
cmd('leerness release sync-main .', t('release branch → main 자동 fast-forward', 'release branch -> main fast-forward'));
|
|
11550
11711
|
|
|
11551
11712
|
cprint('');
|
|
11552
|
-
cprint(C.dim(' 📚 자세히: `leerness --help` · 자율 모드: `<<autonomous-loop-dynamic>>` 신호로 진행'));
|
|
11713
|
+
cprint(C.dim(t(' 📚 자세히: `leerness --help` · 자율 모드: `<<autonomous-loop-dynamic>>` 신호로 진행', ' 📚 More: `leerness --help`')));
|
|
11553
11714
|
cprint('');
|
|
11554
11715
|
}
|
|
11555
11716
|
}
|
|
@@ -19924,9 +20085,9 @@ module.exports = {
|
|
|
19924
20085
|
// 1.9.289: shell-safe 인용 (Codex #3) — 단위 테스트
|
|
19925
20086
|
_shellQuoteArg,
|
|
19926
20087
|
// 1.18.1: 명령 실행 권한 결정 (재실증 신규 P1: --test-cmd 비-JS 인터프리터 거짓차단) — 단위 테스트
|
|
19927
|
-
_isCommandPermitted, RUN_CORE_ALLOW,
|
|
20088
|
+
_isCommandPermitted, RUN_CORE_ALLOW, _uiLang, _tx,
|
|
19928
20089
|
// 1.18.2: verify-claim 위장 스텁(빈 export 껍데기) 판정 — 단위 테스트
|
|
19929
20090
|
_vcImplIsEmpty, _VC_EMPTY_SHELL_RE,
|
|
19930
|
-
// 1.18.3 (UR-0003): 분야별 자기질문 품질 렌즈 — 단위
|
|
19931
|
-
LENS_CATALOG, lensCmd
|
|
20091
|
+
// 1.18.3 (UR-0003): 분야별 자기질문 품질 렌즈 — 단위 테스트. 1.19.2: 파일→도메인 매핑(완료-검증 advisory)
|
|
20092
|
+
LENS_CATALOG, lensCmd, _lensDomainsForFiles, _mergeLensCatalog, _loadProjectLenses, _effectiveLensCatalog
|
|
19932
20093
|
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Clean-room evaluations / 클린룸 평가 기록
|
|
2
|
+
|
|
3
|
+
> **What this is, honestly.** These are **AI clean-room evaluations** — independent agents installing the
|
|
4
|
+
> published npm package into fresh temp directories and exercising it from behavior only (no access to the
|
|
5
|
+
> source tree), including adversarial attempts to defeat leerness's own verifier. They are **not**
|
|
6
|
+
> third-party human security audits or peer-reviewed benchmarks. We publish them so the README claim
|
|
7
|
+
> "verified by independent clean-room evaluations" is checkable rather than a marketing line.
|
|
8
|
+
>
|
|
9
|
+
> 정직하게 말하면: 아래는 **AI 클린룸 평가**입니다 — 독립 에이전트가 게시된 npm 패키지를 빈 임시 폴더에
|
|
10
|
+
> 설치해 **소스 접근 없이 행위만으로** 검증하고, leerness 검증기 자체를 무력화하려는 적대 시도까지 포함합니다.
|
|
11
|
+
> 제3자 인간 보안 감사나 동료심사 벤치마크가 **아닙니다**. README 의 "독립 클린룸 평가로 검증" 주장을
|
|
12
|
+
> 확인 가능하게 만들기 위해 공개합니다.
|
|
13
|
+
|
|
14
|
+
## Methodology / 방법론
|
|
15
|
+
|
|
16
|
+
Every evaluation follows the same shape:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
W=$(mktemp -d); cd "$W"; npm init -y
|
|
20
|
+
npm i leerness@<version> # the PUBLISHED package, not the working tree
|
|
21
|
+
LB="node node_modules/leerness/bin/leerness.js"
|
|
22
|
+
# ... drive the CLI by behavior, assert on exit codes + output, clean up ...
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- **Published-artifact only** — installs from npm, never reads the repo source. This is what an external user gets.
|
|
26
|
+
- **Behavior-asserted** — checks process exit codes and stdout/stderr, not internal state.
|
|
27
|
+
- **Adversarial** — a portion of every pass actively tries to make a *false* "done" claim pass verification
|
|
28
|
+
(comment-only stubs, empty-export shells, `assert(true)` fake tests, inflated test counts, language tricks).
|
|
29
|
+
- **Both directions (맹신 X / "trust nothing")** — confirmed real defects AND rejected false alarms; a finding
|
|
30
|
+
counts only if reproduced on the published package.
|
|
31
|
+
|
|
32
|
+
## Evaluations run / 수행한 평가
|
|
33
|
+
|
|
34
|
+
| # | Round | Scope | Outcome |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| 1 | Universal-harness field study (5-axis) | Real Python / Node / Rust development, agent-swap handoff, adversarial attack on the verifier | Verdict "conditionally yes"; found 5 gaps (2×P1, 3×P2) — all closed in 1.17.2–1.17.6, shipped as the 1.18.0 stable minor |
|
|
37
|
+
| 2 | 1.18.0 re-verification (published) | Independently re-ran the 5 gaps against the **published** 1.18.0 | 4 closed; surfaced a new P1 (Windows `--test-cmd python` blocked → false FAIL) → fixed in 1.18.1 |
|
|
38
|
+
| 3 | 1.18.2 adversarial stub workflow | Bypass-hunters + false-positive-hunters vs the empty-shell stub detector | 9 one-keyword bypasses found (Object.freeze, async fn, inline comment, …) and closed; **0 false positives** across ~45 legitimate patterns |
|
|
39
|
+
| 4 | 1.19.0 published-artifact re-verification | Installed `leerness@1.19.0` from npm; smoke-tested headline fixes | `--test-cmd python`, empty-shell rejection, `lens`, selftest 231 — all pass on the published package |
|
|
40
|
+
|
|
41
|
+
## What these evaluations do NOT establish / 한계 (정직)
|
|
42
|
+
|
|
43
|
+
leerness's verification is **heuristic, not semantic**. These evaluations show it reliably catches *obvious
|
|
44
|
+
false-done claims* (missing files, empty shells, fake/unlinked tests, inflated counts) without false-failing
|
|
45
|
+
honest work. They do **not** prove:
|
|
46
|
+
|
|
47
|
+
- the implementation satisfies the actual requirement, or business logic is correct;
|
|
48
|
+
- tests are sufficient or well-designed;
|
|
49
|
+
- absence of security vulnerabilities or production-runtime correctness.
|
|
50
|
+
|
|
51
|
+
Known heuristic gaps tracked for an AST/token-based redesign: multi-arg call-expression empty objects
|
|
52
|
+
(`Object.assign({}, {})`), Python `def …: pass` / `...` / `raise NotImplementedError`, multi-language empty
|
|
53
|
+
bodies. **Code coverage / mutation testing and third-party reproduction remain open** (the e2e is a
|
|
54
|
+
spawn-based runner, which standard coverage tools do not instrument cleanly).
|
|
55
|
+
|
|
56
|
+
## Reproduce it yourself / 직접 재현
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
W=$(mktemp -d); cd "$W"; npm init -y && npm i leerness@latest
|
|
60
|
+
LB="node node_modules/leerness/bin/leerness.js"
|
|
61
|
+
$LB init "$W" --yes
|
|
62
|
+
# fake "done": claim an implementation that does not exist
|
|
63
|
+
$LB task add "Implement X"
|
|
64
|
+
$LB task update T-0001 --status done --evidence "x.js done, 5 tests passed"
|
|
65
|
+
$LB verify-claim T-0001 # exit 1 — claim rejected (x.js missing, no tests ran)
|
|
66
|
+
$LB selftest # core-function integrity self-check
|
|
67
|
+
```
|