leerness 1.36.96 → 1.36.98
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 +63 -0
- package/README.md +4 -4
- package/bin/leerness.js +646 -28
- package/lib/library.js +297 -0
- package/lib/session-close.js +30 -4
- package/lib/tech-profile.js +49 -3
- package/package.json +1 -1
- package/scripts/e2e.js +127 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.98 — 2026-08-04 — session close 가 사용자 문서를 지우고 있었다 + 재사용 인벤토리 (P-0013)
|
|
4
|
+
|
|
5
|
+
### 🚨 데이터 손실 수정 — `session close` 가 `current-state.md` 를 통째로 갈아치웠다
|
|
6
|
+
|
|
7
|
+
사용자 보고(다른 프로젝트의 AI 이력): *"session close가 또 current-state.md를 덮어썼습니다. 복원하겠습니다."* — **"또"** 가 핵심이었다.
|
|
8
|
+
|
|
9
|
+
재현했다. **440자 → 88자, 80% 파괴.** 사용자가 손으로 적어 둔 섹션 두 개(`## 결정 로그`, `## 인수인계 메모`)가 통째로 사라지고 Now/Next/Blockers 의 모든 상세 줄이 한 줄로 대체됐다. 원인은 세 개의 정규식 치환이었고, 마지막 하나가 결정적이었다 — 섹션이 아니라 **`## Blockers` 부터 파일 끝까지**를 지웠다. 그리고 이 명령은 제품이 AGENTS.md 로 *"매 세션 끝에 반드시 실행"* 하라고 지시하는 것이라, 손실이 상시로 일어났다.
|
|
10
|
+
|
|
11
|
+
고친 방식은 범위를 좁히는 게 아니라 **소유권을 표시하는 것**이다. 도구가 쓴 줄에 `<!-- leerness:auto -->` 표지를 달고 **표지 있는 줄만** 교체한다. 표지 없는 줄은 어떤 경우에도 지우지 않는다. 섹션 범위도 EOF 가 아니라 다음 `##` 제목까지로 닫았다. 실측: 손실 0 · 3회 반복 실행에도 296자로 안정(자동줄·빈줄 누적 없음) · 섹션이 없는 문서는 건드리지 않음 · 표지가 없던 구버전 파일의 마지막 생성 줄 하나는 남지만 누적되지 않는다(사용자 문서를 지우는 것보다 이쪽이 안전한 실패다).
|
|
12
|
+
|
|
13
|
+
변이 5종 중 4종 사살, 나머지 1종(섹션 끝을 EOF 로 되돌리기)은 **실행해서 등가임을 증명**했다 — 보존이 범위 계산이 아니라 "표지 없는 줄은 전부 유지"에서 나오므로 범위를 틀려도 삭제가 일어나지 않는다. 설계가 옳으면 가드가 한 겹 덜 필요하다.
|
|
14
|
+
|
|
15
|
+
### 재사용 인벤토리 (P-0013) — `leerness library`
|
|
16
|
+
|
|
17
|
+
`reuse-map` 이 중복 '함수' 를 막는다면 이쪽은 중복 '화면 조각' 을 막는다. 저장소에서 컴포넌트·디자인 토큰을 뽑아 사람용 오프라인 페이지(`library page`)와 에이전트용 압축 JSON(`library --ai`)으로 나눠 낸다. 나누는 게 핵심이다 — 인벤토리 전체를 컨텍스트에 밀어 넣으면 토큰을 아끼려던 기능이 토큰을 더 쓴다(실측: 페이지 23KB vs AI 페이로드 5.3KB).
|
|
18
|
+
|
|
19
|
+
설계는 실측이 세 번 뒤집었다.
|
|
20
|
+
- **순진한 추출은 2~3배 부풀린다.** 대문자 export 를 세면 auto-influencer 83 · view-work 116 · **플레인 HTML 프로젝트 62** 인데, 그 62개는 전부 가짜다. 없는 것을 있다고 말하는 인벤토리는 없느니만 못하다 → 선언 근처에 **실제 마크업**이 있는 것만 싣는다(같은 프로젝트 39 · 46 · **1**).
|
|
21
|
+
- **창을 고정 문자 수로 자르면 안 된다.** 실측한 선언→마크업 거리는 p50 684 · p90 3,248 · p99 20,639 자라 1,500 자 창은 69% 만 포착했고, 키우면 "파일에 JSX 있으면 전부"라는 부풀린 휴리스틱으로 되돌아간다. **다음 최상위 선언까지**로 잡아 상수를 없앴다(들여쓰기를 허용하면 컴포넌트 첫 줄 `const [s,setS]=useState()` 가 창을 즉시 닫아 반대로 붕괴한다 — 열 0 이 최상위라는 신호다).
|
|
22
|
+
- **토큰 출처가 스택마다 정반대다.** Tailwind 프로젝트(12개 중 7개)는 CSS 커스텀 프로퍼티가 0~5개인 대신 유틸 어휘가 74~121종이고(`text-ink-50` 417회 — 빈도가 곧 사실상의 토큰), 안 쓰는 프로젝트는 CSS 변수 58개에 유틸 0. 한쪽만 보면 절반이 '토큰 없음' 이 된다.
|
|
23
|
+
|
|
24
|
+
만들면서 오탐 세 종을 실측으로 잡았다: TypeScript 제네릭(`Promise<Response>`)이 JSX 태그와 글자가 같아 zod 스키마가 컴포넌트로 실렸고, 문자열·주석의 자리표시자(`<svc>`·`<uuid>`·`<browserPort>`)가 UI 를 그리지 않는 감시 클래스 4개를 컴포넌트로 만들었으며, `--이름:` 을 파일 전체에서 찾다가 주석 속 CLI 플래그(`audit --fix:`)가 '디자인 토큰' 으로 실렸다(이 저장소에서 57개 중 43개가 그것이었다). 각각 위치·속성·문맥으로 갈랐다. 아무것도 못 찾으면 빈 페이지를 그럴듯하게 내지 않고 이유를 말한다.
|
|
25
|
+
|
|
26
|
+
부수적으로 **UI 스택 감지**를 `tech-profile` 에 추가했다(react/next/vue/svelte/astro/tailwind/vite/django 등). 새 판정기를 만들지 않고 기존 한 곳을 확장했다 — 표면마다 따로 판정하면 반드시 어긋난다(1.36.97 에서 도메인 목록으로 같은 실수를 했다). 실측 12개 프로젝트 12/12 일치. `.html` 을 확장자 집계에 넣으면 **"html" 이 언어로 등재**되고, `changed` 판정에서 ui 를 빼면 기존 프로필을 가진 프로젝트에 ui 가 **영영 기록되지 않는다** — 둘 다 셀프테스트로 못박았다.
|
|
27
|
+
|
|
28
|
+
## 1.36.97 — 2026-08-04 — 가장 많이 물리는 축을 가장 얕게 묻고 있었다 (P-0011)
|
|
29
|
+
|
|
30
|
+
사용자 질문: *"leerness가 설치된 프로젝트에서 작업할 때 이 개념들(11개 공통 축 · 20개 분야)이 적용되는지 확인하고, 적용할 가치가 있는지 판단해줘."*
|
|
31
|
+
|
|
32
|
+
**측정부터 했다.** 렌즈는 7개 도메인이 있었지만 깊이가 극단적으로 치우쳐 있었다 — `database` 12문항 7,193자, `debug` 6문항 538자, 나머지 5종은 각 3문항 125~168자. 11축 중 4축(소유권·생명주기·성능확장성·관측가능성)은 아예 없었고, 사용자가 준 15개 설계 질문 중 실제로 묻는 건 4~5개였다.
|
|
33
|
+
|
|
34
|
+
결정적인 건 비율이 아니라 **어디서 물렸는가**였다. 직전 라운드(1.36.96)에서 외부 검수 8회가 막은 57건은 계약(378바이트 스텁) · 실패복구(없음) · 신뢰보안(3문항) 축에 몰렸고, **12문항짜리 동시성 축에서 나온 건 0건**이었다. 가장 깊은 렌즈가 실제 작업과 하나도 안 맞았다.
|
|
35
|
+
|
|
36
|
+
그래서 축 11개를 나열하지 않았다 — 이름만 늘리면 매 세션 읽히는 배경음이 되고, 이 저장소에서 낡은 경고가 무시되는 걸 이미 봤다. `database` 렌즈가 **작동한 이유**만 복제한다: (a) 이름이 아니라 구체적 반례, (b) 파일 내용 기반 자동 소환.
|
|
37
|
+
|
|
38
|
+
- **계약 · 실패·복구 · 관측 가능성** 3축을 각 12문항으로 신설(KO/EN 동수). 반례는 되도록 이 저장소에서 실제로 물린 사건으로 채웠다 — 상한을 UTF-16 코드 단위로 세어 한글 문서에서 512KB 주장이 실제 1,152KB 였던 일(계약: 단위를 안 적은 대가), 깊이 상한 너머를 원본 그대로 통과시켜 상한이 곧 우회 경로가 된 일(복구: 문제는 상한이 아니라 실패 방향), stderr 전문을 끝까지 읽고서야 "환경 탓"이 내 문법 오류였음을 안 일(관측: 첫 줄만 남기면 그 진단이 불가능하다).
|
|
39
|
+
- **`axes` 8축 경량 점검** — 나머지 축은 한 축당 한 문항. 이미 깊은 렌즈가 있는 축(순서·동시성 → database, 신뢰·보안 → security, 인간 요소 → design/docs)은 중복 서술 대신 그 렌즈로 넘긴다(같은 내용을 두 곳에 적으면 한쪽이 반드시 낡는다).
|
|
40
|
+
- **소환은 내용 기반** — 경로에 단서가 없는 파일도 켜진다. 신호는 낱말이 아니라 **"코드 모양으로 쓰였는가"** 다: 빈 catch · `.catch(() => {})` · `backoff:`/`maxRetries =` 같은 대입·옵션 문맥 · `new AbortController` · SIGTERM 핸들러(복구), `logger.error` · catch **블록** 안의 `console.error` · `traceId`/`x-request-id` · `metrics.inc(` · `@opentelemetry/`(관측), 라우트 정의 · `res.status(4xx)` · zod/Joi 스키마 · `@deprecated` · protobuf IDL(계약).
|
|
41
|
+
- **경량 축은 신호가 있을 때만 한 줄** 붙는다(반복문 안 `await` → 성능·확장성, `setInterval`/`addEventListener` → 생명주기). 신호가 없으면 아무것도 붙이지 않는다.
|
|
42
|
+
- **도메인 목록을 세 표면(help ko · help en · commands)에 손으로 적던 것을 카탈로그에서 파생**시켰다. 축을 추가할 때마다 세 곳을 동시에 고쳐야 했고, 한 곳만 놓치면 낡은 목록이 계속 광고된다.
|
|
43
|
+
|
|
44
|
+
**출하 직전 검수가 이 변경의 P1 두 건을 잡았다 — 둘 다 내가 만든 것이다.**
|
|
45
|
+
|
|
46
|
+
① **정규식 2차 폭발.** 줄머리 앵커 뒤에 쓴 `\s*` 가 개행까지 먹어, 빈 줄이 이어지면 O(n²) 가 됐다. 512KB 빈 줄 파일에 **227,760ms(3분 48초)**, 실제 CLI 로도 150KB 파일에 13.8초(대조군 1.2초). 상한 512KB 는 비용 가드였는데, 실제로는 `512KB × 512KB` 를 허용하고 있었다. 들여쓰기는 공백·탭이지 개행이 아니다 — `[ \t]*` 로 조이자 **227,760ms → 2.7ms**, 진짜 양성 인식은 5/5 그대로다. 내 사전 프로브가 이걸 놓친 이유는 분명하다: **정규식의 구조가 아니라 내가 상상한 공격 모양에서 입력을 만들었다**(`catch` 폭탄·CRLF 는 시험했고 순수 개행 연속은 안 했다).
|
|
47
|
+
|
|
48
|
+
② **낱말 신호가 산문·UI 문구에서 켜졌다.** i18n 카탈로그 한 줄(`'error.backoff': 'Too many attempts…'`)이 recovery+observability 를 띄우고 `code` 렌즈를 통째로 밀어냈다. 기능 탐지 배열의 `'AbortController'`, 개수 세는 `counter.set(`, 레이 트레이서의 `tracer`, `faker.company.catchPhrase()` 도 마찬가지였다. 반대로 `@opentelemetry`·`@sentry` 대안은 `\b` 를 `@` 앞에 둬서 **어떤 실제 코드에서도 매칭될 수 없었다**(죽은 대안을 넣고 커버리지가 있다고 믿고 있었다). 전부 대입·호출·블록 문맥 요구로 바꿨다.
|
|
49
|
+
|
|
50
|
+
**캡 설계도 틀렸다.** 구체 축을 앞세우고 전체를 2로 자르니 실제 커밋 226개 중 141개(62%)에서 출력이 바뀌고 일반 렌즈가 보이는 커밋이 99% → 57% 로 떨어졌다 — 구현+테스트 커밋이 `test` 질문을, code+README 커밋이 `docs` 질문을 아예 못 받았다. **새 축은 더하는 것이지 없애는 것이 아니다**: 일반 축 상한(2)을 1.36.96 그대로 두고 구체 축 2개를 그 위에 얹는다. 실측으로 **실제 커밋 188개에서 렌즈 제거 0건**(추가만 102건). 줄이 늘 것 같지만 반대다 — 인라인은 목차이지 본문이므로 질문의 첫 절만 찍게 바꿔 **advisory 블록이 15행 → 6행(ko) / 8행(en)** 이 됐다. 축은 2개에서 4개로 늘었는데 출력은 짧아졌다.
|
|
51
|
+
|
|
52
|
+
**렌즈 본문의 사실 오류 3건도 고쳤다.** ⓐ "Promise.all 은 처리되지 않은 거부를 남긴다"는 **틀렸다** — all 은 모든 프라미스를 구독하므로 늦은 거부도 처리된다(직접 실행해 확인: all 0건, 개별 await 방치 1건). 진짜 위험은 "취소되지 않고 계속 실행되며 결과가 버려진다"는 쪽이라 그렇게 다시 썼다. ⓑ 4xx 를 재시도 불가로 뭉뚱그렸는데 408·425·429 는 재시도 대상이다. ⓒ `contract verify` 가 명세↔구현 대조를 기계화한다고 적었지만 그 명령은 이름·필드 **존재**만 본다 — 과장을 걷어냈다(이건 1.36.5 에서 내가 고쳤던 바로 그 과장이다).
|
|
53
|
+
|
|
54
|
+
**측정 자체가 틀렸던 것도 바로잡는다.** 처음 적은 "recovery 1.4% · 어느 축이든 9.2%" 는 분모에 `.venv`·`site-packages`·생성 번들이 섞여 서드파티 코드로 희석된 값이었다. 손으로 쓴 소스만(벤더·빌드 산출물·미니파이·기계생성 제외) 다시 재면 **19개 프로젝트 3,463 파일에서 recovery 중앙값 6.5% · observability 8.3% · contract 4.0% · database 1.9%, 어느 축이든 31.3%** 다. 7배 낮게 적었던 셈이다. 다만 캡을 고친 지금은 이 비율이 '밀어냄' 이 아니라 '한 줄 더함' 이라 위험도가 다르다.
|
|
55
|
+
|
|
56
|
+
**신설한 렌즈가 자기 자신에게 먼저 물렸다.** 패키지의 테스트 제외 33개 파일 중 18개에 **주석 없는 `catch {}`** 가 있고 총 911회 나온다(`bin/leerness.js` 427 · `scripts/e2e.js` 378 · `lib/` 28개 중 16개 파일 106회). 상당수는 의도한 방어적 무시겠지만, recovery 5번 문항이 요구하는 "왜 안전한지 한 줄"이 없어 **의도와 사고를 코드만 보고 구분할 수 없다** — 별건(T-0086)으로 스윕한다.
|
|
57
|
+
|
|
58
|
+
**남의 커스텀 렌즈를 덮지 않는다.** 내장 도메인을 새로 만들면 그 이름을 먼저 쓰고 있던 프로젝트(`.harness/quality-lenses.json`)와 충돌한다. 실측해 보니 질문은 보존되는데 **사용자가 적어 둔 제목이 조용히 사라졌다**('우리팀 계약' → '계약(경계의 약속)') — 내용은 그대로인데 이름만 바뀌니 오히려 더 헷갈린다. 프로젝트가 명시한 title/persona 를 살리되 로케일별로 따로 받는다(ko 제목만 준 프로젝트의 `--language en` 출력에 한글이 새면 안 된다). 덤으로, 절단 경고가 하드코딩하던 "12+커스텀 상한"도 도메인별 실제 내장 문항 수로 파생시켰다(`axes` 는 8 이라 그 문구가 이미 거짓이었다).
|
|
59
|
+
|
|
60
|
+
**도그푸드가 잡은 것 하나 더.** `verify-claim` 을 이 변경 자체에 걸었더니 recovery 가 뜨지 않았다 — `bin/leerness.js`(1,799KB)와 `scripts/e2e.js`(692KB) 가 512KB 상한에 걸려 **이 저장소의 주력 파일 두 개가 영구히 스캔 밖**이었다. 파일당 4MB 로 올리되 호출당 총 32MB 예산을 함께 뒀다(비용은 파일 하나가 아니라 호출 하나에 묶어야 한다). 그리고 축마다 파일을 다시 열던 것을 **1회 읽기**로 바꿨다 — 같은 파일을 여섯 번 읽으면 IO 도 폭발 위험도 여섯 배다. 검수는 **디렉토리 정션으로 root 밖 파일이 읽히는 것**도 재현했다(문자열 접두만 봤고 최종 파일만 `lstat` 했다). 양쪽을 `realpath` 로 해석해 다시 봉쇄했다 — 1.36.4 부터 있던 구멍이다.
|
|
61
|
+
|
|
62
|
+
**가드.** selftest 2건 신설(343건). 양성만 재면 항상-참 술어도 통과하므로 **대조군을 함께** 넣었고, 검수가 재현한 오탐(i18n 문구·폴리필 배열·개수 세는 Map·레이 트레이서·`catchPhrase`)과 누락(`@opentelemetry` import)을 그대로 케이스로 박았다. **2차 폭발 회귀 가드**도 넣었다 — 256KB 빈 줄 입력에 시간 예산을 걸어, `\s*` 로 되돌리면 실패한다.
|
|
63
|
+
|
|
64
|
+
가드가 처음부터 튼튼했던 적은 없다. 변이 1차에서 5개가 살아남았는데 원인이 각각 달랐다: 3개는 **내 변이가 no-op**(문두만 바꿔 판별 문구가 그대로), 1개는 **픽스처가 크기·심볼릭 분기를 지나지 않음**, 1개는 **진짜 구멍 — 영문 표면에 단언이 없어 EN 문항을 한글로 바꿔도 통과**(1.36.89 에서 이미 겪은 구멍이라 `questionsEn` 에 한글 0 을 못박았다). e2e 절제에서도 1개가 살아남았는데 하필 **파생 목록의 존재 이유인 help/commands 세 표면을 아무도 안 보고 있었다**. 심볼릭 링크 단언은 Windows 에서 권한이 없어 조용히 건너뛰어 **주 플랫폼에서 죽어 있었다** — 권한 없이 만들 수 있는 **디렉토리 정션**으로 바꿔 되살렸고, 링크 갈래가 하나도 시험되지 않으면 실패하도록 했다(건너뜀을 통과로 세지 않는다).
|
|
65
|
+
|
|
3
66
|
## 1.36.96 — 2026-07-31 — 실행기를 만들지 않고 문서를 띄운다 (P-0010)
|
|
4
67
|
|
|
5
68
|
사용자 요청: *"leerness.html 대신 exe 같은 실행기로 로드맵·그래프·스킬·기술·핸드오프·플랜 마크다운을 그대로 띄울 수 있나? 성능에 영향을 준다면 leerness.html 로 유지하고 거기에 반영."*
|
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.98 하네스를 사용합니다. 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.98는 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.96는 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.98 릴리스 태그 이력** (수백 라운드) · _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.98: 2026-08-04
|
|
239
239
|
<!-- leerness:project-readme:end -->
|