leerness 1.36.51 → 1.36.52
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 +53 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.52 — 2026-07-21 — 외부 GPT 감사(1.36.47) P0 채택 (UR-0063)
|
|
4
|
+
|
|
5
|
+
웹 구독 GPT 의 독립 감사(격리 설치·오류 주입·패치 첨부, 10건) 중 재현 확정 2건 + 채택 판단 2건을 반영. 전 건 현행 소스에서 독립 재현/검증 후 수정.
|
|
6
|
+
|
|
7
|
+
- **F-01 (재현확정) — 재-init 언어 뒤집힘**: en 프로젝트 init 2회차에 1회차가 생성한 한국어 관리 블록을 콘텐츠 감지가 오인해 ko 로 전환되던 멱등성 버그. 저장된 manifest/LANGUAGE 언어 최우선, 콘텐츠 감지는 신규만 (감사 첨부 패치 검증 후 채택). 실측: en→en 유지, 명시 --language ko 는 전환.
|
|
8
|
+
- **F-07 (재현확정) — memory restore 단수형 실패**: 생성 문서 예시(decision)와 CLI(decisions) 불일치 — 단수형 별칭 수용.
|
|
9
|
+
- **F-02 — 비율형 증거 우회**: "5/5 passed"(비율형 완전-통과)가 명시 카운트("5 tests passed")와 달리 정적 선언-수 대조를 우회하던 것 — 동일 정규화(1.35.9 원칙 유지: N/N 만 승격). verify-claim --json 에 신뢰 경계 명시: `semanticVerified:false` + `evidenceLevel: static|executed`.
|
|
10
|
+
- **F-08 — .env 권한**: Unix 에서 생성/병합 시 0600 (gitignore 는 커밋만 막고 로컬 노출은 못 막음).
|
|
11
|
+
- 감사 나머지: F-03(enforce=사용 게이트, status 한계 명시 기존) / F-06(전용 스캐너 병행 — 1.35.8 문서 기존) 은 방향 일치 확인, F-04(managed 무결성 복구)/F-05(en 완전 번역)/F-09(MCP 프로필)/E2E 아티팩트 이식성은 백로그 편입.
|
|
12
|
+
- 검증: selftest 326(+1 통합 행위), e2e.
|
|
13
|
+
|
|
3
14
|
## 1.36.51 — 2026-07-21 — 모호성 질문 + 미리보기 승인 워크플로 (사용자 요청 UR-0061)
|
|
4
15
|
|
|
5
16
|
사용자 요청: "자연어에 판단 모호한 부분이 있으면 사용자에게 질문하고, 신규 기능은 코드 작성 전 디자인/기능 미리보기를 제시해 승인/수정을 받도록".
|
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.52 하네스를 사용합니다. 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.52는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
|
|
180
180
|
|
|
181
181
|
```jsonc
|
|
182
182
|
// 카테고리별
|
|
@@ -197,7 +197,7 @@ Leerness v1.36.51는 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.52 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
|
|
201
201
|
|
|
202
202
|
### 성능 가이드
|
|
203
203
|
|
|
@@ -235,6 +235,6 @@ 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.52: 2026-07-21
|
|
239
239
|
<!-- leerness:project-readme:end -->
|
|
240
240
|
|
package/bin/leerness.js
CHANGED
|
@@ -34,7 +34,7 @@ const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE
|
|
|
34
34
|
const { tokenizeForRank: _tokenizeForRank, expandQuery: _expandQuery, scoreHits: _scoreHits, suggestTerms: _suggestTerms } = require('../lib/search-core'); // 1.36.23: memory search 랭킹 코어(순수·0-deps)
|
|
35
35
|
const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태 무결성 (audit/health/check 공유)
|
|
36
36
|
|
|
37
|
-
const VERSION = '1.36.
|
|
37
|
+
const VERSION = '1.36.52';
|
|
38
38
|
|
|
39
39
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
40
40
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -240,6 +240,21 @@ function detectProjectName(root) { try { const pkg = JSON.parse(read(path.join(r
|
|
|
240
240
|
function detectLanguageValue(root, value = 'auto') {
|
|
241
241
|
const v = String(value || 'auto').toLowerCase();
|
|
242
242
|
if (v === 'ko' || v === 'en') return v;
|
|
243
|
+
// 1.36.52 (외부 GPT 감사 F-01, 재현확정): 이미 초기화된 프로젝트는 저장된 명시 언어를 최우선 —
|
|
244
|
+
// 1회차 init 이 생성한 한국어 관리 블록을 2회차 콘텐츠 감지가 오인해 en→ko 로 뒤집던 멱등성 버그.
|
|
245
|
+
// 콘텐츠 자동 감지는 신규 프로젝트에만. (감사 첨부 패치 검증 후 채택)
|
|
246
|
+
try {
|
|
247
|
+
const mf = path.join(root, '.harness', 'manifest.json');
|
|
248
|
+
if (exists(mf)) {
|
|
249
|
+
const saved = String((JSON.parse(read(mf)) || {}).language || '').toLowerCase();
|
|
250
|
+
if (saved === 'ko' || saved === 'en') return saved;
|
|
251
|
+
}
|
|
252
|
+
const lf = path.join(root, '.harness', 'LANGUAGE');
|
|
253
|
+
if (exists(lf)) {
|
|
254
|
+
const saved = String(read(lf) || '').trim().toLowerCase();
|
|
255
|
+
if (saved === 'ko' || saved === 'en') return saved;
|
|
256
|
+
}
|
|
257
|
+
} catch {}
|
|
243
258
|
// ① 프로젝트 콘텐츠 한글 우선 — 콘텐츠가 있으면 그 의도를 존중 (기존 동작 보존).
|
|
244
259
|
const candidates = ['README.md', 'docs/guideline.md', '.harness/project-brief.md', '.harness/plan.md'];
|
|
245
260
|
let text = '';
|
|
@@ -744,6 +759,8 @@ function mergeLinesFile(p, lines) {
|
|
|
744
759
|
function mergeEnvFile(p, lines) {
|
|
745
760
|
const current = exists(p) ? read(p) : '';
|
|
746
761
|
writeUtf8(p, _mergeEnvLines(current, lines));
|
|
762
|
+
// 1.36.52 (외부 GPT 감사 F-08): Unix 에서 .env 는 소유자 전용(0600) — gitignore 는 커밋만 막지 로컬 노출은 못 막는다.
|
|
763
|
+
if (process.platform !== 'win32') { try { fs.chmodSync(p, 0o600); } catch {} }
|
|
747
764
|
}
|
|
748
765
|
|
|
749
766
|
function writeMigrationReport(root, backup, actions, opts = {}) {
|
|
@@ -5019,6 +5036,26 @@ function _selfTestCases() {
|
|
|
5019
5036
|
const preserved = fs.readFileSync(c._previewsPath(tmp), 'utf8') === '[{"id":"P-0001",';
|
|
5020
5037
|
return added && pending1 && revised && approved && preserved;
|
|
5021
5038
|
} finally { process.stdout.write = _w; console.error = _ce; try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
|
|
5039
|
+
} },
|
|
5040
|
+
{ name: '외부 GPT 감사 P0 (1.36.52, UR-0063): 재init 언어보존(F-01) + restore 별칭(F-07) + ratio 증거 정규화/evidenceLevel(F-02) + .env 0600(F-08) — 행위+소스가드', run: () => {
|
|
5041
|
+
// F-01: manifest 언어가 콘텐츠 감지보다 우선 (행위)
|
|
5042
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_lang_'));
|
|
5043
|
+
let langOk = false;
|
|
5044
|
+
try {
|
|
5045
|
+
fs.mkdirSync(path.join(tmp, '.harness'), { recursive: true });
|
|
5046
|
+
fs.writeFileSync(path.join(tmp, '.harness', 'manifest.json'), '{"language":"en"}');
|
|
5047
|
+
fs.writeFileSync(path.join(tmp, 'README.md'), '# 한국어 콘텐츠 가득한 리드미'); // 종전엔 이것이 ko 로 뒤집었다
|
|
5048
|
+
langOk = detectLanguageValue(tmp, 'auto') === 'en' && detectLanguageValue(tmp, 'ko') === 'ko';
|
|
5049
|
+
} finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
|
|
5050
|
+
const s = read(__filename);
|
|
5051
|
+
// F-07: 단수형 별칭 매핑 존재
|
|
5052
|
+
const aliasOk = s.includes("({ decision: 'decisions', lesson: 'lessons', plans: 'plan' })[surface]");
|
|
5053
|
+
// F-02: 비율형 완전-통과 → declaredTestCount 승격 + 신뢰 경계 필드
|
|
5054
|
+
const ratioOk = s.includes('declaredPass.num === declaredPass.denom) declaredTestCount = declaredPass.denom')
|
|
5055
|
+
&& s.includes('semanticVerified: false') && s.includes("? 'executed' : 'static'");
|
|
5056
|
+
// F-08: Unix .env 0600
|
|
5057
|
+
const envOk = /mergeEnvFile[\s\S]{0,600}chmodSync\(p, 0o600\)/.test(s);
|
|
5058
|
+
return langOk && aliasOk && ratioOk && envOk;
|
|
5022
5059
|
} }
|
|
5023
5060
|
];
|
|
5024
5061
|
}
|
|
@@ -9129,6 +9166,8 @@ function memoryArchiveListCmd(root, opts = {}) {
|
|
|
9129
9166
|
// 매칭 archive 블록을 active 파일 끝에 추가 + archive 에서 제거
|
|
9130
9167
|
function memoryRestoreCmd(root, surface, target) {
|
|
9131
9168
|
root = absRoot(root);
|
|
9169
|
+
// 1.36.52 (외부 GPT 감사 F-07): 단수형 별칭 수용 — 생성 문서 예시(decision)와 CLI(decisions) 불일치로 실패하던 것.
|
|
9170
|
+
surface = ({ decision: 'decisions', lesson: 'lessons', plans: 'plan' })[surface] || surface;
|
|
9132
9171
|
if (!surface || !['decisions', 'lessons', 'plan'].includes(surface)) {
|
|
9133
9172
|
return fail('memory restore <decisions|lessons|plan> <target> 필요 (target: date YYYY-MM-DD 또는 substring)');
|
|
9134
9173
|
}
|
|
@@ -12180,6 +12219,11 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
12180
12219
|
const m5 = evidence.match(/(\d+)\s*tests?\b/i);
|
|
12181
12220
|
if (m5) declaredTestCount = parseInt(m5[1], 10);
|
|
12182
12221
|
}
|
|
12222
|
+
// 1.36.52 (외부 GPT 감사 F-02, 재현확정): 비율형 완전-통과("5/5 passed")를 명시 카운트와 동일 정규화 —
|
|
12223
|
+
// 종전엔 declaredTestCount 로만 정적 선언-수 대조를 해서 비율형 주장이 그 게이트를 우회했다.
|
|
12224
|
+
// 1.35.9 원칙 유지: 전부-통과(N/N) 주장만 승격(부분 비율은 비-테스트 서술일 수 있어 미승격).
|
|
12225
|
+
let _ratioPromoted = false;
|
|
12226
|
+
if (declaredTestCount == null && declaredPass && declaredPass.num === declaredPass.denom) { declaredTestCount = declaredPass.denom; _ratioPromoted = true; }
|
|
12183
12227
|
|
|
12184
12228
|
// 실제 파일 존재 검사
|
|
12185
12229
|
const fileChecks = files.map(f => ({ file: f, exists: exists(path.join(root, f)) }));
|
|
@@ -12286,6 +12330,10 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
12286
12330
|
// 1.9.309 (UR-0048, 설치리뷰 Opus critical): done 주장은 evidence(파일+테스트) 기본 강제 — 거짓완료 차단을 기본값·MCP·json 모두에서 작동.
|
|
12287
12331
|
// 이전: --require-evidence 플래그 시에만 검사 → 증거0 done 이 기본 통과(fileChecks.every([])=공허참)했고 MCP 는 도달 불가했음.
|
|
12288
12332
|
// 이제 done/완료 주장은 기본 강제. opt-out: --lenient. 비-done 주장은 영향 없음.
|
|
12333
|
+
// 1.36.52 (F-02 보정, 게이트 실측): 비율형 승격은 "정적 검증 전용" — --run-tests 가 주장 수 이상 전부-통과를
|
|
12334
|
+
// 실제 실행으로 확증하면 정적 선언-수 대조를 재적용하지 않는다(실행 증거 > 정적 휴리스틱; jest/mocha 러너는
|
|
12335
|
+
// 선언을 소스-grep 으로 셀 수 없는 형식이 많아 실행 확증 케이스를 정적 게이트가 거짓 차단했다).
|
|
12336
|
+
if (_ratioPromoted && runResult && !runResult.skipped && runResult.allPassed && runResult.parsed && runResult.parsed.num >= declaredTestCount) declaredTestCount = null;
|
|
12289
12337
|
const isDoneClaim = /done|완료|completed/i.test(row.status || '');
|
|
12290
12338
|
const evq = _evidenceQuality(evidence);
|
|
12291
12339
|
const mustHaveEvidence = !has('--lenient') && (isDoneClaim || has('--require-evidence'));
|
|
@@ -12343,6 +12391,10 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
12343
12391
|
const out = {
|
|
12344
12392
|
project: path.basename(root),
|
|
12345
12393
|
taskId, row,
|
|
12394
|
+
// 1.36.52 (외부 GPT 감사 F-02): 신뢰 경계 명시 — 이 검증은 정적/실행 흔적 대조이지 의미론 보증이 아니다.
|
|
12395
|
+
// evidenceLevel: static(파일·문자열 대조만) | executed(--run-tests 실제 실행 포함). 소비자가 경계를 오인하지 않도록 스키마에 고정.
|
|
12396
|
+
semanticVerified: false,
|
|
12397
|
+
evidenceLevel: (runResult && !runResult.skipped) ? 'executed' : 'static',
|
|
12346
12398
|
declared: { files: files.length, pass: declaredPass, testCount: declaredTestCount },
|
|
12347
12399
|
actual: { fileChecks, testCount: actualTestCount },
|
|
12348
12400
|
verdict: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.52",
|
|
4
4
|
"description": "The AI-coding operations layer that makes \"done\" require evidence — persistent memory, evidence-gated completion checks, and clean handoffs for any AI agent (Claude Code, Codex, Cursor). State lives as plain files in your repo. CLI + MCP, 0 runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"leerness",
|