leerness 1.36.10 → 1.36.12
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 +20 -0
- package/README.md +4 -4
- package/bin/leerness.js +53 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.12 — 2026-07-12 — UR-0009 완결: selftest 견고화 불변식 메타가드 + .gitattributes LF 강제 커밋
|
|
4
|
+
|
|
5
|
+
**저순위 백로그 UR-0009 (1.12.0 클린룸 exact-match 실패 후속) 3파트 완결.**
|
|
6
|
+
|
|
7
|
+
- **(1) 멀티라인 needle 전수 점검: CLEAN** — 실제 개행을 포함한 백틱 needle `.includes(` 0건(라인 히트 3건은 needle 텍스트에 이스케이프 `\n` 문자가 든 단일라인 — EOL 무관 견고). 1.12.1 정규식화 이후 재발 없음을 확인하고, **불변식을 메타가드 selftest 로 상시 강제**(실개행 needle 이 다시 생기면 selftest 실패; 스캔 정규식은 charCode 조립으로 self-ref 안전).
|
|
8
|
+
- **(2) .gitattributes LF 강제 커밋** — 파일은 working tree 에 존재했으나 **미추적**이라 다른 클론엔 무효였음(git 은 untracked .gitattributes 도 읽으므로 로컬은 이미 유효 — 과거 커밋의 "CRLF will be replaced by LF" 경고가 증거). 커밋으로 모든 체크아웃에서 bin/lib LF 일관성 보장.
|
|
9
|
+
- **(3) 1.12.0 실패 메커니즘** — 정확한 역사적 재현은 불가(당시 환경 소멸)를 정직 인정. 가설(체크아웃별 EOL/포맷 분산)은 (1) 불변식 강제 + (2) LF 정규화 커밋으로 **원인-계열 자체를 구조적으로 차단** — 증상(1.12.1)과 원인계열(1.36.12) 모두 닫힘.
|
|
10
|
+
- selftest 286 + e2e 통과.
|
|
11
|
+
|
|
12
|
+
## 1.36.11 — 2026-07-09 — 17th 클린룸 리뷰 4/4 채택: deps --json 누수 + UNC 우회 + DB렌즈 prose FP + en 렌즈 i18n
|
|
13
|
+
|
|
14
|
+
**게시본 1.36.10 을 codex 가 클린룸(fresh npm install)에서 행위로만 적대 검증(이 세션 7개 신규 표면) — P1×2, P2×2 전부 명령+출력 증거로 확정, 4/4 수정.**
|
|
15
|
+
|
|
16
|
+
- **P1-A `deps --json` plain-text 누수** — 워크스페이스-없음 에러 경로가 failJson 미배선 → **설치본 selftest 283/284 실패**의 원인. dev 트리는 `_apps` 가 있어 이 분기를 안 타 게이트가 못 잡는 **환경 의존 경로**였음(클린룸 리뷰의 존재 이유를 재확인). → `no_workspace_projects` 구조화.
|
|
17
|
+
- **P1-B UNC 접두 조용한 변환** — evidence `\\src\thing.mjs` 의 UNC 접두를 벗겨 무관한 로컬 `src/thing.mjs` 를 검증(존재검사 우회성). → 매치 직전이 경로 구분자면 "더 긴 경로의 꼬리"로 보고 추출 제외(드라이브 `C:\...`·URL 꼬리도 함께 차단). `파일:src/x.js` 정상 케이스는 유지.
|
|
18
|
+
- **P2-A DB 렌즈 prose FP** — `// select from the menu where …`(주석)·JSX 텍스트가 렌즈를 발화. → 완결형 SQL 문장은 **대문자 키워드(case-sensitive) 또는 따옴표/백틱 문맥**일 때만 인정 — 실제 코드 SQL 은 거의 항상 둘 중 하나라 recall 손실 없음(인용 소문자·대문자 SQL recall 행위 검증).
|
|
19
|
+
- **P2-B en 로케일 렌즈 한국어 누출** — verify-claim 렌즈 블록이 titleEn/questionsEn 미사용. → lensCmd 와 동일 규칙 적용.
|
|
20
|
+
- codex 검증 통과 표면(수정 불요): handoff 넛지(부패 JSON·미래 타임스탬프·10만 history·--json 무오염·opt-out 전부 견딤), optimism [DB] 수정(빈 .sql 경계 포함), 정직화 문구 4종 잔존 과장 없음.
|
|
21
|
+
- selftest 285 (신규: 4수정 행위+소스 가드) + e2e 통과.
|
|
22
|
+
|
|
3
23
|
## 1.36.10 — 2026-07-09 — FILE_RE Windows 백슬래시 경로 지원 (마지막 선재 백로그 소진)
|
|
4
24
|
|
|
5
25
|
**verify-claim 의 주장-파일 추출이 Windows 백슬래시 경로를 잘라먹던 선재 결함(전 확장자 공통, codex 6차 core-diff 리뷰에서 지적 후 backlog).**
|
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ MIT
|
|
|
125
125
|
<!-- leerness:project-readme:start -->
|
|
126
126
|
## Leerness Project Harness
|
|
127
127
|
|
|
128
|
-
이 프로젝트는 Leerness v1.36.
|
|
128
|
+
이 프로젝트는 Leerness v1.36.12 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
129
129
|
|
|
130
130
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
131
131
|
|
|
@@ -179,7 +179,7 @@ leerness memory restore decision <date|title>
|
|
|
179
179
|
|
|
180
180
|
### MCP server (외부 AI 통합)
|
|
181
181
|
|
|
182
|
-
Leerness v1.36.
|
|
182
|
+
Leerness v1.36.12는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
|
|
183
183
|
|
|
184
184
|
```jsonc
|
|
185
185
|
// 카테고리별
|
|
@@ -200,7 +200,7 @@ Leerness v1.36.10는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
200
200
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
201
201
|
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) 다음 라운드 예약.
|
|
202
202
|
|
|
203
|
-
현재 누적: **70 라운드 (1.9.40 → 1.36.
|
|
203
|
+
현재 누적: **70 라운드 (1.9.40 → 1.36.12)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
204
204
|
|
|
205
205
|
### 성능 가이드 (1.9.140 측정)
|
|
206
206
|
|
|
@@ -238,6 +238,6 @@ leerness release pack --close --auto-main-push
|
|
|
238
238
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
239
239
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
240
240
|
|
|
241
|
-
Last synced by Leerness v1.36.
|
|
241
|
+
Last synced by Leerness v1.36.12: 2026-07-12
|
|
242
242
|
<!-- leerness:project-readme:end -->
|
|
243
243
|
|
package/bin/leerness.js
CHANGED
|
@@ -33,7 +33,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
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
|
const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태 무결성 (audit/health/check 공유)
|
|
35
35
|
|
|
36
|
-
const VERSION = '1.36.
|
|
36
|
+
const VERSION = '1.36.12';
|
|
37
37
|
|
|
38
38
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
39
39
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -4032,6 +4032,37 @@ function _selfTestCases() {
|
|
|
4032
4032
|
return extractedOk;
|
|
4033
4033
|
} finally { try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ } }
|
|
4034
4034
|
} },
|
|
4035
|
+
{ name: '17th 클린룸 리뷰 (1.36.11): deps --json failJson + UNC 꼬리매치 스킵 + DB렌즈 prose FP 제거 + en 렌즈 i18n (행위+소스)', run: () => {
|
|
4036
|
+
const self = read(__filename);
|
|
4037
|
+
// P1-A: deps 워크스페이스-없음 경로 failJson 배선 (소스 가드)
|
|
4038
|
+
const depsJsonOk = self.includes("failJson(has('--json'), 'no_workspace_projects'");
|
|
4039
|
+
// P1-B: UNC/꼬리 매치 스킵 — \\src\thing.mjs 는 추출 안 됨(무관 로컬 경로 검증 차단), 정상 백슬래시는 유지 (행위)
|
|
4040
|
+
const ev = String.fromCharCode(92) + String.fromCharCode(92) + 'src' + String.fromCharCode(92) + 'thing.mjs 그리고 src' + String.fromCharCode(92) + 'other.mjs 수정';
|
|
4041
|
+
const FE = 'mjs|js';
|
|
4042
|
+
const FR = new RegExp(`(?:[A-Za-z][A-Za-z0-9_-]*[\\/\\\\])?[A-Za-z][\\w./\\\\-]*\\.(?:${FE})\\b`, 'g');
|
|
4043
|
+
const got = [];
|
|
4044
|
+
for (const m of ev.matchAll(FR)) { const p = m.index > 0 ? ev[m.index - 1] : ''; if (p === '\\' || p === '/') continue; got.push(m[0].replace(/\\/g, '/')); }
|
|
4045
|
+
const uncOk = JSON.stringify(got) === JSON.stringify(['src/other.mjs']);
|
|
4046
|
+
// P2-A: prose FP 제거 + recall 유지 (행위)
|
|
4047
|
+
const proseOk = !_isDbContentText('// select from the menu where appropriate')
|
|
4048
|
+
&& !_isDbContentText('<div>Select from the menu where needed</div>')
|
|
4049
|
+
&& _isDbContentText("db.prepare('select * from t where id = ?')") // 인용 소문자 SQL recall 유지
|
|
4050
|
+
&& _isDbContentText('SELECT id FROM users WHERE active = 1'); // 대문자 SQL recall 유지
|
|
4051
|
+
// P2-B: en 렌즈 i18n (소스 가드)
|
|
4052
|
+
const enLensOk = self.includes('(_en && l.questionsEn && l.questionsEn[0]) || l.questions[0]');
|
|
4053
|
+
return depsJsonOk && uncOk && proseOk && enLensOk;
|
|
4054
|
+
} },
|
|
4055
|
+
{ name: 'selftest 견고화 불변식 (UR-0009, 1.36.12): 실개행 멀티라인 needle includes 금지(전수 0) — 1.12.0 클린룸 exact-match 실패 재발 차단 (메타가드)', run: () => {
|
|
4056
|
+
// 1.12.0 사고: 멀티라인 exact-string includes 가드가 환경별 EOL/포맷 차이로 클린룸에서만 실패(1.12.1 정규식화로 해소).
|
|
4057
|
+
// 이 메타가드는 그 불변식을 상시 강제: 백틱 needle 안에 실제 개행이 든 .includes( 가 다시 생기면 selftest 가 실패한다.
|
|
4058
|
+
// self-ref 안전: 스캔 정규식은 backtick 을 charCode 로 조립 — 이 가드 자신은 실개행 needle 을 포함하지 않는다.
|
|
4059
|
+
const self = read(__filename);
|
|
4060
|
+
const BT = String.fromCharCode(96), NL = String.fromCharCode(10);
|
|
4061
|
+
const re = new RegExp('\\.includes\\(' + BT + '([^' + BT + ']*)' + BT + '\\)', 'g');
|
|
4062
|
+
let m, offenders = 0;
|
|
4063
|
+
while ((m = re.exec(self)) !== null) { if (m[1].includes(NL)) offenders++; }
|
|
4064
|
+
return offenders === 0;
|
|
4065
|
+
} },
|
|
4035
4066
|
{ name: 'CLI 영어화 Phase 1 (1.20.2, UR-0010): _uiLang 해석(flag>env>manifest>ko) + 첫화면 _t 적용 (행위+소스)', run: () => {
|
|
4036
4067
|
const save = process.argv; const saveEnv = process.env.LEERNESS_LANG;
|
|
4037
4068
|
try {
|
|
@@ -4951,9 +4982,14 @@ function _lensDomainsForFiles(files) {
|
|
|
4951
4982
|
const _DB_IMPORT_RE = /(?:require\s*\(|\bfrom|\bimport)\s*['"](?:node:sqlite|better-sqlite3|sqlite3|pg|postgres|mysql2?|knex|typeorm|sequelize|drizzle-orm|@prisma\/client|mongodb|mongoose|ioredis|redis|oracledb|tedious|@libsql\/client)['"]/i;
|
|
4952
4983
|
// 단일 SQL 키워드는 산문 FP 위험(select/update/from/where 는 영어 단어) → 산문에 거의 없는 조합만: DDL/트랜잭션 토큰 또는
|
|
4953
4984
|
// 완결형 문장 패턴(SELECT…FROM…WHERE / UPDATE…SET…WHERE / DELETE FROM…WHERE). 근접 조합이라 산문 FP 가 급감한다.
|
|
4954
|
-
|
|
4985
|
+
// 1.36.11 (클린룸 P2-A): 완결형 문장은 산문("// select from the menu where …", JSX 텍스트)에도 나올 수 있어
|
|
4986
|
+
// **대문자 키워드(case-sensitive)** 또는 **따옴표/백틱 문맥(인용 시작 40자 내)** 일 때만 인정 — 실제 코드의 SQL 은
|
|
4987
|
+
// 거의 항상 둘 중 하나(관례적 대문자 또는 문자열 리터럴)라 recall 손실 없이 prose FP 를 제거.
|
|
4988
|
+
const _DB_SQL_RE = /\b(?:CREATE\s+TABLE|INSERT\s+INTO|ON\s+CONFLICT|BEGIN\s+(?:IMMEDIATE|TRANSACTION|DEFERRED|EXCLUSIVE)|FOR\s+UPDATE)\b/i;
|
|
4989
|
+
const _DB_STMT_UPPER_RE = /\bSELECT\b[\s\S]{0,120}?\bFROM\b[\s\S]{0,120}?\bWHERE\b|\bUPDATE\b[\s\S]{0,80}?\bSET\b[\s\S]{0,200}?\bWHERE\b|\bDELETE\s+FROM\b[\s\S]{0,120}?\bWHERE\b/; // case-sensitive
|
|
4990
|
+
const _DB_STMT_QUOTED_RE = /['"`][^'"`\n]{0,40}?\bselect\b[\s\S]{0,120}?\bfrom\b[\s\S]{0,120}?\bwhere\b|['"`][^'"`\n]{0,40}?\bupdate\b[\s\S]{0,80}?\bset\b[\s\S]{0,200}?\bwhere\b|['"`][^'"`\n]{0,40}?\bdelete\s+from\b[\s\S]{0,120}?\bwhere\b/i;
|
|
4955
4991
|
function _isDbContentText(txt) {
|
|
4956
|
-
return typeof txt === 'string' && (_DB_IMPORT_RE.test(txt) || _DB_SQL_RE.test(txt));
|
|
4992
|
+
return typeof txt === 'string' && (_DB_IMPORT_RE.test(txt) || _DB_SQL_RE.test(txt) || _DB_STMT_UPPER_RE.test(txt) || _DB_STMT_QUOTED_RE.test(txt));
|
|
4957
4993
|
}
|
|
4958
4994
|
// claimed 파일 중 코드 파일을 읽어 DB 내용 신호가 있으면 true. 미존재/큰 파일(>512KB)은 건너뜀(안전·비용).
|
|
4959
4995
|
// 보안(codex P2): 경로는 root 안으로 제한 — 절대경로/../ 트래버설로 워크스페이스 밖 파일을 읽지 않는다. 심볼릭 링크는 스킵.
|
|
@@ -10907,7 +10943,14 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
10907
10943
|
// 'db\\migrations\\001_create.sql' 이 'create.sql' 로 잘려 추출돼 존재 검사가 false-fail 하던 결함.
|
|
10908
10944
|
// 구분자에 \\ 허용 + 추출 후 / 로 정규화(하류의 git diff 비교·렌즈 매핑은 / 기준).
|
|
10909
10945
|
const FILE_RE = new RegExp(`(?:[A-Za-z][A-Za-z0-9_-]*[\\/\\\\])?[A-Za-z][\\w./\\\\-]*\\.(?:${FILE_EXTS})\\b`, 'g');
|
|
10910
|
-
|
|
10946
|
+
// 1.36.11 (클린룸 P1-B): 매치 직전 문자가 경로 구분자면 더 긴 경로의 "꼬리"만 잡은 것(UNC \\\\server\\..., 드라이브 X:\\...,
|
|
10947
|
+
// 숫자 시작 선두 세그먼트 등) — 잘린 꼬리를 루트-상대 경로로 검증하면 무관한 로컬 파일이 통과(우회성). 그런 매치는 버린다.
|
|
10948
|
+
const filePatterns = [];
|
|
10949
|
+
for (const m of evidence.matchAll(FILE_RE)) {
|
|
10950
|
+
const prev = m.index > 0 ? evidence[m.index - 1] : '';
|
|
10951
|
+
if (prev === '\\' || prev === '/') continue; // suffix-of-longer-path(UNC \\srv\..., C:\..., URL) → skip. ':' 는 "파일:src/x.js" 정상 케이스라 스킵 안 함
|
|
10952
|
+
filePatterns.push(m[0].replace(/\\/g, '/'));
|
|
10953
|
+
}
|
|
10911
10954
|
// 중복 제거 + "tests/test.js" 같은 결과를 유지 (이미 `..` 없으니 그대로)
|
|
10912
10955
|
const files = Array.from(new Set(filePatterns));
|
|
10913
10956
|
// 1.9.20: 테스트 수 파싱 확장 — 한국어 + jest/mocha/tap/vitest
|
|
@@ -11258,7 +11301,9 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
11258
11301
|
log(t(` 🧭 품질 렌즈 (완료 선언 전 자문 — advisory, 게이트 아님):`, ` 🧭 Quality lens (self-ask before declaring done — advisory, not a gate):`));
|
|
11259
11302
|
for (const d of _lensDoms) {
|
|
11260
11303
|
const l = _lensCat[d];
|
|
11261
|
-
|
|
11304
|
+
// 1.36.11 (클린룸 P2-B): en 로케일에서 렌즈 제목/질문이 한국어로 새던 것 — titleEn/questionsEn 사용(lensCmd 와 동일 규칙).
|
|
11305
|
+
const _en = _uiLang(root) === 'en';
|
|
11306
|
+
if (l) log(` · ${d}(${(_en && l.titleEn) || l.title}): ${(_en && l.questionsEn && l.questionsEn[0]) || l.questions[0]}`);
|
|
11262
11307
|
}
|
|
11263
11308
|
log(` ${t('→ 전체 질문', '→ full questions')}: leerness lens ${_lensDoms[0]}`);
|
|
11264
11309
|
}
|
|
@@ -11655,7 +11700,9 @@ function depsImpactCmd(root, targetCapability) {
|
|
|
11655
11700
|
process.argv.push('--all-apps');
|
|
11656
11701
|
}
|
|
11657
11702
|
const allPaths = _collectWorkspacePaths(root);
|
|
11658
|
-
|
|
11703
|
+
// 1.36.11 (클린룸 P1-A): --json 인데 plain-text 로 새던 에러 경로 — failJson 배선 (설치본 selftest 283/284 의 원인:
|
|
11704
|
+
// dev 트리는 _apps 가 있어 이 분기를 안 타 게이트가 못 잡았음; 클린룸(워크스페이스 없음)에서만 노출되는 환경 의존 경로).
|
|
11705
|
+
if (!allPaths.length) return failJson(has('--json'), 'no_workspace_projects', '워크스페이스 프로젝트 없음. _apps/* 또는 --include 사용.');
|
|
11659
11706
|
|
|
11660
11707
|
// 1) 모든 reuse-map에서 entries + depends-on 엣지 수집
|
|
11661
11708
|
const allEntries = []; // { project, entry }
|