leerness 1.36.3 → 1.36.4
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 +11 -0
- package/README.md +4 -4
- package/bin/leerness.js +78 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.4 — 2026-07-09 — database 렌즈 recall: 내용 기반 DB 감지 (dogfood FN) — 평범한 이름의 DB 모듈도 렌즈 노출
|
|
4
|
+
|
|
5
|
+
**도그푸드에서 발견한 recall gap(FN) 소진.** 실사용(재고 서비스) 도중, `_lensDomainsForFiles` 가 **경로 휴리스틱**만 써서 `models/`·`.repository.`·`.sql` 등 관습적 이름의 파일만 `database` 도메인에 매핑함을 경험적으로 확인했다 — `inventory.safe.mjs`·`orders.occ.mjs` 처럼 실제로 SQLite 를 다루지만 이름에 단서가 없는 모듈은 `database` 렌즈가 **전혀 노출되지 않았다**(`["code"]` 로만 분류).
|
|
6
|
+
|
|
7
|
+
- 신규: `_isDbContentText` / `_anyDbContentInFiles` / `_withDbDomain` — verify-claim 이 주장 파일의 **내용**에 실제 DB 드라이버 import(`node:sqlite`/`pg`/`mysql2`/`better-sqlite3`/`typeorm`/`prisma` 등) 또는 산문엔 거의 없는 완결형 SQL 문장(`SELECT…FROM…WHERE`/`UPDATE…SET…WHERE`/`CREATE TABLE`/`INSERT INTO`/`ON CONFLICT`/`BEGIN IMMEDIATE`)이 있으면 `database` 렌즈를 보강 노출.
|
|
8
|
+
- FP 가드: 단일 키워드(`database`/`select`/`from`/`where` = 영어 단어)만으로는 안 켬 — 드라이버 import 또는 근접 다중 SQL 키워드 조합만. 산문/비-DB 코드 회귀 0(selftest 행위 검증).
|
|
9
|
+
- 안전: 코드 파일(.js/.mjs/.ts/.py 등)만, 512KB 이하만 읽음. path-기반 매핑은 그대로 두고 **추가 보강**만(비파괴). advisory 렌즈라 게이트 아님.
|
|
10
|
+
- codex FN검수 채택: **root 제한**(evidence 경로의 `../`·절대경로로 워크스페이스 밖 파일 읽기 차단, 심볼릭 링크 스킵 — P2) + **테스트 파일 제외**(SQL 담은 test 파일이 `test` 렌즈를 축출하지 않도록 — P3).
|
|
11
|
+
- 알려진 한계(정직 고지): 드라이버 import 가 타 파일에 있는 ORM 메서드체인 파일은 단일파일 내용분석으론 여전히 미탐(P3); `SELECT…FROM…WHERE` 형태의 드문 산문은 advisory FP 가능(저해, 게이트 아님).
|
|
12
|
+
- 검증: 실제 dogfood 4개 DB 모듈 전부 감지(전 false→true), 산문/README FP 0, ReDoS SAFE(<25ms @320KB), root 트래버설·테스트 제외 행위 검증. selftest 278 + full e2e 통과.
|
|
13
|
+
|
|
3
14
|
## 1.36.3 — 2026-07-08 — DB 안전성 렌즈 코어 승격 (race condition + ACID) — 사용자 명시 + codex 검수 채택
|
|
4
15
|
|
|
5
16
|
- 신규 내장 품질 렌즈 도메인 `database` (8 자기질문: lost update / check-then-act TOCTOU / atomicity / DB 제약 / isolation+retry / durability / distribution). `leerness lens database` 로 호출, verify-claim 이 DB 파일 변경 시 인라인 노출.
|
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.4 하네스를 사용합니다. 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.4는 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.3는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
|
|
|
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.4)** · 매 라운드 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.4: 2026-07-09
|
|
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.4';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -3914,6 +3914,40 @@ function _selfTestCases() {
|
|
|
3914
3914
|
&& eq(F(['README.md']), ['docs']);
|
|
3915
3915
|
return catOk && mapOk;
|
|
3916
3916
|
} },
|
|
3917
|
+
{ name: 'DB 렌즈 recall (dogfood FN, 1.36.4): 내용기반 감지 — 평범한 이름의 DB 모듈 잡고 산문 FP 0 (행위)', run: () => {
|
|
3918
|
+
const T = _isDbContentText, W = _withDbDomain;
|
|
3919
|
+
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
3920
|
+
// 잡아야: 드라이버 import + 완결형 SQL 문장(SELECT…FROM…WHERE / UPDATE…SET…WHERE) + DDL/트랜잭션 토큰
|
|
3921
|
+
const hit = T("import { DatabaseSync } from 'node:sqlite'")
|
|
3922
|
+
&& T("const pg = require('pg')")
|
|
3923
|
+
&& T("db.prepare('SELECT * FROM product WHERE id = ?')")
|
|
3924
|
+
&& T("db.prepare('UPDATE product SET stock = stock - 1 WHERE id = ?')")
|
|
3925
|
+
&& T("db.exec('BEGIN IMMEDIATE')") && T("INSERT INTO orders(x) VALUES(?)") && T("ON CONFLICT(idem_key) DO NOTHING");
|
|
3926
|
+
// FP 0: database/select/from/where 가 산문에 흩어져 있어도 단일 키워드·부분조합은 안 켠다
|
|
3927
|
+
const noFp = !T('// connect to the database and select from the menu')
|
|
3928
|
+
&& !T('const total = items.reduce((a,b)=>a+b,0)')
|
|
3929
|
+
&& !T('return items.filter(x => x.from && x.where)')
|
|
3930
|
+
&& !T('updateProfile(user) // app update, no SQL');
|
|
3931
|
+
// 병합: database 우선 노출 + 최대 2 캡
|
|
3932
|
+
const mergeOk = eq(W(['code']), ['database', 'code']) && eq(W(['code', 'design']), ['database', 'code']) && eq(W([]), ['database']);
|
|
3933
|
+
return hit && noFp && mergeOk;
|
|
3934
|
+
} },
|
|
3935
|
+
{ name: 'DB 렌즈 recall 보안·특례 (codex FN검수 P2/P3, 1.36.4): _anyDbContentInFiles root 제한 + 테스트파일 제외 (행위)', run: () => {
|
|
3936
|
+
const os = require('os');
|
|
3937
|
+
const parent = fs.mkdtempSync(path.join(os.tmpdir(), 'lz-dbc-'));
|
|
3938
|
+
const dir = path.join(parent, 'proj'); fs.mkdirSync(dir);
|
|
3939
|
+
try {
|
|
3940
|
+
const sql = "import { DatabaseSync } from 'node:sqlite'\ndb.prepare('SELECT * FROM t WHERE id=?')\n";
|
|
3941
|
+
fs.writeFileSync(path.join(dir, 'svc.mjs'), sql);
|
|
3942
|
+
fs.writeFileSync(path.join(dir, 'svc.test.mjs'), sql); // 테스트 파일(동일 DB 내용)
|
|
3943
|
+
fs.writeFileSync(path.join(parent, 'outside.mjs'), sql); // root 밖(존재+DB내용)
|
|
3944
|
+
const plain = _anyDbContentInFiles(['svc.mjs'], dir) === true;
|
|
3945
|
+
const testExcluded = _anyDbContentInFiles(['svc.test.mjs'], dir) === false; // codex P3: 테스트 제외
|
|
3946
|
+
const traversalBlocked = _anyDbContentInFiles(['../outside.mjs'], dir) === false // codex P2: ../ 거부
|
|
3947
|
+
&& _anyDbContentInFiles([path.join(parent, 'outside.mjs')], dir) === false; // 절대경로 거부
|
|
3948
|
+
return plain && testExcluded && traversalBlocked;
|
|
3949
|
+
} finally { try { fs.rmSync(parent, { recursive: true, force: true }); } catch { /* ignore */ } }
|
|
3950
|
+
} },
|
|
3917
3951
|
{ name: 'GPT-5.5 평가 #5 (1.19.1, UR-0009) + 정직성 calibration (1.35.12): 클린룸 문서 공개 + 한계 명시 + self-administered 라벨 (행위)', run: () => {
|
|
3918
3952
|
const dp = path.join(path.dirname(__filename), '..', 'docs', 'clean-room-evaluations.md');
|
|
3919
3953
|
if (!exists(dp)) return true; // 패키지에 없으면 스킵(설치본 안전)
|
|
@@ -4833,6 +4867,44 @@ function _lensDomainsForFiles(files) {
|
|
|
4833
4867
|
const ordered = ['database', 'code', 'design', 'docs', 'test'].filter(d => out.includes(d));
|
|
4834
4868
|
return ordered.slice(0, 2);
|
|
4835
4869
|
}
|
|
4870
|
+
// 1.36.x (dogfood FN): path 휴리스틱은 관습적 이름(models/·.repository.·.sql 등)만 잡아, 평범한 이름의 DB 모듈
|
|
4871
|
+
// (inventory.safe.mjs · orders.occ.mjs 처럼 DB 를 다루지만 이름에 단서 없음)을 놓친다. 내용 기반으로 보강한다:
|
|
4872
|
+
// claimed 파일이 실제 DB 드라이버를 import 하거나 산문엔 안 나오는 명백한 SQL DDL/트랜잭션 토큰을 담으면 database 렌즈를 켠다.
|
|
4873
|
+
// FP 가드: "database" 라는 단어만으로는 안 켠다 — 드라이버 import 또는 CREATE TABLE/INSERT INTO/ON CONFLICT/BEGIN IMMEDIATE/FOR UPDATE 급 토큰이 있어야 함.
|
|
4874
|
+
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;
|
|
4875
|
+
// 단일 SQL 키워드는 산문 FP 위험(select/update/from/where 는 영어 단어) → 산문에 거의 없는 조합만: DDL/트랜잭션 토큰 또는
|
|
4876
|
+
// 완결형 문장 패턴(SELECT…FROM…WHERE / UPDATE…SET…WHERE / DELETE FROM…WHERE). 근접 조합이라 산문 FP 가 급감한다.
|
|
4877
|
+
const _DB_SQL_RE = /\b(?:CREATE\s+TABLE|INSERT\s+INTO|ON\s+CONFLICT|BEGIN\s+(?:IMMEDIATE|TRANSACTION|DEFERRED|EXCLUSIVE)|FOR\s+UPDATE)\b|\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/i;
|
|
4878
|
+
function _isDbContentText(txt) {
|
|
4879
|
+
return typeof txt === 'string' && (_DB_IMPORT_RE.test(txt) || _DB_SQL_RE.test(txt));
|
|
4880
|
+
}
|
|
4881
|
+
// claimed 파일 중 코드 파일을 읽어 DB 내용 신호가 있으면 true. 미존재/큰 파일(>512KB)은 건너뜀(안전·비용).
|
|
4882
|
+
// 보안(codex P2): 경로는 root 안으로 제한 — 절대경로/../ 트래버설로 워크스페이스 밖 파일을 읽지 않는다. 심볼릭 링크는 스킵.
|
|
4883
|
+
// codex P3: 테스트 파일은 DB 증강에서 제외 — test 렌즈를 축출하지 않도록(_lensDomainsForFiles 의 test 특례와 일관).
|
|
4884
|
+
function _anyDbContentInFiles(files, root) {
|
|
4885
|
+
const arr = Array.isArray(files) ? files : [];
|
|
4886
|
+
const base = path.resolve(root || process.cwd());
|
|
4887
|
+
const _testRe = /(^|[\\/])(test_[^\\/]+\.[a-z]+|[^\\/]+[._-]test\.[a-z]+|[^\\/]+\.spec\.[a-z]+)$|(^|[\\/])tests?[\\/]/i;
|
|
4888
|
+
for (const f of arr) {
|
|
4889
|
+
if (typeof f !== 'string') continue;
|
|
4890
|
+
if (!/\.(js|mjs|cjs|ts|tsx|py|rb|go|rs|java|cs|php)$/i.test(f)) continue;
|
|
4891
|
+
if (_testRe.test(f)) continue; // 테스트 파일 → test 렌즈 유지(DB 증강 제외)
|
|
4892
|
+
try {
|
|
4893
|
+
const p = path.resolve(base, f);
|
|
4894
|
+
if (p !== base && !p.startsWith(base + path.sep)) continue; // root 밖(../·절대경로) 거부
|
|
4895
|
+
const st = fs.lstatSync(p); // 심볼릭 링크는 따라가지 않음(isFile()=false → 스킵)
|
|
4896
|
+
if (!st.isFile() || st.size > 512 * 1024) continue;
|
|
4897
|
+
if (_isDbContentText(fs.readFileSync(p, 'utf8'))) return true;
|
|
4898
|
+
} catch { /* 미존재/권한 → 무시 */ }
|
|
4899
|
+
}
|
|
4900
|
+
return false;
|
|
4901
|
+
}
|
|
4902
|
+
// database 를 도메인 목록에 병합(우선 노출 + 최대 2 캡). database 가 code/design 등보다 앞선다.
|
|
4903
|
+
function _withDbDomain(domains) {
|
|
4904
|
+
const merged = Array.isArray(domains) ? domains.slice() : [];
|
|
4905
|
+
if (!merged.includes('database')) merged.unshift('database');
|
|
4906
|
+
return ['database', 'code', 'design', 'docs', 'test'].filter(d => merged.includes(d)).slice(0, 2);
|
|
4907
|
+
}
|
|
4836
4908
|
function lensCmd(domain, opts = {}) {
|
|
4837
4909
|
const jsonMode = !!opts.json || has('--json');
|
|
4838
4910
|
// 1.19.1 (19th 버그헌트): 도메인 인자 정규화 — `lens Code` / `lens CODE ` 도 인식(대소문자·공백 무관).
|
|
@@ -11072,7 +11144,9 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
11072
11144
|
log(t(` ℹ 한계: 테스트 통과는 "의미적 구현 정확성"을 보장하지 않음 — evidence 가 해당 주장(수정 파일/테스트)을 직접 링크해야 신뢰도↑.`, ` ℹ Limit: passing tests do not guarantee "semantic correctness" — evidence should directly link the claimed files/tests for higher confidence.`));
|
|
11073
11145
|
// 1.19.2 (UR-0003 렌즈 완전판 v2): 완료-검증 순간에 분야별 자기질문 advisory — 주장 파일 확장자 기반(결정적).
|
|
11074
11146
|
// 기계검증(파일/테스트/스텁)을 통과해도 "사람이 보기에 좋은가"는 별개 → AI 가 스스로 답하도록 권장(advisory, 게이트 아님).
|
|
11075
|
-
|
|
11147
|
+
let _lensDoms = _lensDomainsForFiles(files);
|
|
11148
|
+
// 1.36.x (dogfood FN): path 가 놓친 평범한 이름의 DB 모듈 — claimed 파일 내용에 DB 신호가 있으면 database 렌즈 보강.
|
|
11149
|
+
if (!_lensDoms.includes('database') && _anyDbContentInFiles(files, root)) _lensDoms = _withDbDomain(_lensDoms);
|
|
11076
11150
|
if (_lensDoms.length) {
|
|
11077
11151
|
const _lensCat = _effectiveLensCatalog(root); // 1.19.3: 프로젝트 커스텀 질문도 포함
|
|
11078
11152
|
log('');
|
|
@@ -21087,5 +21161,6 @@ module.exports = {
|
|
|
21087
21161
|
// 1.18.2: verify-claim 위장 스텁(빈 export 껍데기) 판정 — 단위 테스트
|
|
21088
21162
|
_vcImplIsEmpty, _VC_EMPTY_SHELL_RE,
|
|
21089
21163
|
// 1.18.3 (UR-0003): 분야별 자기질문 품질 렌즈 — 단위 테스트. 1.19.2: 파일→도메인 매핑(완료-검증 advisory)
|
|
21090
|
-
LENS_CATALOG, lensCmd, _lensDomainsForFiles, _mergeLensCatalog, _loadProjectLenses, _effectiveLensCatalog
|
|
21164
|
+
LENS_CATALOG, lensCmd, _lensDomainsForFiles, _mergeLensCatalog, _loadProjectLenses, _effectiveLensCatalog,
|
|
21165
|
+
_isDbContentText, _anyDbContentInFiles, _withDbDomain
|
|
21091
21166
|
};
|