leerness 1.36.63 → 1.36.64

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.64 — 2026-07-22 — lint 게이트 (외부감사 F-10) — R-0001 검수 9회전
4
+
5
+ 과거 실사고(raw U+2028 정규식 리터럴 → 전 명령 SyntaxError 출하) 재발을 구조적으로 차단하는 0-deps lint.
6
+
7
+ - **`scripts/lint.js` + `npm run lint`**: 배포 js 전수 node --check 구문 · U+2028/U+2029 감지 · BOM · JSON 유효성 (~3초). `npm test`/`test:fast`/(검수 #1)`test:core` 전 티어 선두 배선.
8
+ - 구현 중 자기참조 트랩 재발(6번째)을 자체 검출로 확인 — 검사 문자를 \u 이스케이프 표기로.
9
+ - (검수 #2) selftest 를 4축 전부 행위 검증으로(구문/U+2028/BOM/JSON 각각 검출 + 클린 통과 + 전 티어 배선). (검수 #3) 스폰 실패를 구문오류와 구분 + SyntaxError 본문 라인 우선 표시.
10
+ - 검수 통과 프로브: 실 28파일 클린 · FP 0(\u 이스케이프 텍스트/250KB 라인/CRLF) · 패키지 동봉·설치본 루트 해석 정상.
11
+
3
12
  ## 1.36.63 — 2026-07-22 — F-05 시리즈 완결: README 섹션·헤더·런타임 기본값 en (5회차) — R-0001 검수 8회전
4
13
 
5
14
  **en init 한글 잔존 53/61 → 6/61** — 잔여 6개 전부 스킬팩 한국어 콘텐츠(데이터, 의도적 제외). 감사 F-05 종결.
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.63 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
+ 이 프로젝트는 Leerness v1.36.64 하네스를 사용합니다. 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.63는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
+ Leerness v1.36.64는 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.63는 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.63 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
+ 현재 누적: **v1.9.x → 1.36.64 릴리스 태그 이력** (수백 라운드) · _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.63: 2026-07-22
238
+ Last synced by Leerness v1.36.64: 2026-07-22
239
239
  <!-- leerness:project-readme:end -->
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.63';
37
+ const VERSION = '1.36.64';
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') 시 호스트 프로세스 오염.
@@ -5373,6 +5373,34 @@ function _selfTestCases() {
5373
5373
  const idem = (m3.match(/MY CUSTOM LINE/g) || []).length === 1;
5374
5374
  const wired = read(__filename).includes('altTemplate: _altFiles[f]') && read(__filename).includes("'.harness/session-workflow.md', // 1.36.60");
5375
5375
  return noKoTemplate && customKept && enWrapper && control && idem && wired;
5376
+ } },
5377
+ { name: 'lint 게이트 (1.36.64, 외부감사 F-10): 구문/U+2028(실사고 재발 차단)/BOM/JSON — 클린 통과 + 위험문자 검출 행위검사 + npm test 배선', run: () => {
5378
+ const lintP = path.join(__dirname, '..', 'scripts', 'lint.js');
5379
+ if (!exists(lintP)) return false;
5380
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_lint_'));
5381
+ try {
5382
+ fs.mkdirSync(path.join(tmp, 'lib'), { recursive: true });
5383
+ fs.writeFileSync(path.join(tmp, 'lib', 'ok.js'), 'module.exports = 1;\n');
5384
+ const r1 = cp.spawnSync(process.execPath, [lintP, tmp], { encoding: 'utf8', timeout: 60000 });
5385
+ const cleanOk = r1.status === 0;
5386
+ fs.writeFileSync(path.join(tmp, 'lib', 'bad.js'), 'const s = "a' + String.fromCharCode(0x2028) + 'b";\n');
5387
+ const r2 = cp.spawnSync(process.execPath, [lintP, tmp], { encoding: 'utf8', timeout: 60000 });
5388
+ const caught = r2.status === 1 && /U\+2028/.test(r2.stdout);
5389
+ // (검수 #2) 4개 축 전부 행위 검증 — 구문/BOM/JSON 도 각각 검출
5390
+ fs.writeFileSync(path.join(tmp, 'lib', 'bad.js'), 'const = ;\n');
5391
+ const rSyn = cp.spawnSync(process.execPath, [lintP, tmp], { encoding: 'utf8', timeout: 60000 });
5392
+ const synCaught = rSyn.status === 1 && /syntax:/.test(rSyn.stdout);
5393
+ fs.writeFileSync(path.join(tmp, 'lib', 'bad.js'), Buffer.concat([Buffer.from([0xEF, 0xBB, 0xBF]), Buffer.from('module.exports=1;\n')]));
5394
+ const rBom = cp.spawnSync(process.execPath, [lintP, tmp], { encoding: 'utf8', timeout: 60000 });
5395
+ const bomCaught = rBom.status === 1 && /bom:/.test(rBom.stdout);
5396
+ fs.writeFileSync(path.join(tmp, 'lib', 'bad.js'), 'module.exports=1;\n');
5397
+ fs.writeFileSync(path.join(tmp, 'lib', 'x.json'), '{broken');
5398
+ const rJson = cp.spawnSync(process.execPath, [lintP, tmp], { encoding: 'utf8', timeout: 60000 });
5399
+ const jsonCaught = rJson.status === 1 && /json:/.test(rJson.stdout);
5400
+ const pkg = JSON.parse(read(path.join(__dirname, '..', 'package.json')));
5401
+ const wired = pkg.scripts.lint === 'node ./scripts/lint.js' && ['test', 'test:fast', 'test:core'].every(k => pkg.scripts[k].startsWith('node ./scripts/lint.js &&')); // (검수 #1) test:core 포함
5402
+ return cleanOk && caught && synCaught && bomCaught && jsonCaught && wired;
5403
+ } finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
5376
5404
  } }
5377
5405
  ];
5378
5406
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.63",
3
+ "version": "1.36.64",
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",
@@ -48,10 +48,11 @@
48
48
  "LICENSE"
49
49
  ],
50
50
  "scripts": {
51
- "test": "node ./bin/leerness.js --version && node ./bin/leerness.js selftest && node ./scripts/e2e.js",
52
- "test:core": "node ./bin/leerness.js --version && node ./bin/leerness.js selftest && node ./scripts/e2e-core.js",
53
- "test:fast": "node ./bin/leerness.js selftest && node ./scripts/smoke.js",
51
+ "test": "node ./scripts/lint.js && node ./bin/leerness.js --version && node ./bin/leerness.js selftest && node ./scripts/e2e.js",
52
+ "test:core": "node ./scripts/lint.js && node ./bin/leerness.js --version && node ./bin/leerness.js selftest && node ./scripts/e2e-core.js",
53
+ "test:fast": "node ./scripts/lint.js && node ./bin/leerness.js selftest && node ./scripts/smoke.js",
54
54
  "test:smoke": "node ./scripts/smoke.js",
55
+ "lint": "node ./scripts/lint.js",
55
56
  "prepack": "node ./bin/leerness.js readme sync . && node ./bin/leerness.js --version"
56
57
  },
57
58
  "publishConfig": {
@@ -0,0 +1,63 @@
1
+ // scripts/lint.js — 0-deps 경량 lint 게이트 (1.36.64, 외부감사 F-10: lint/typecheck 부재)
2
+ // 범위: 배포되는 모든 .js 의 ① node --check 구문 ② 위험 문자(U+2028/U+2029 — 과거 raw U+2028 정규식 리터럴이
3
+ // 전 명령 SyntaxError 를 일으킨 실사고 재발 차단) ③ BOM ④ 저장소 .json 파싱 유효성.
4
+ // 빠르고(수 초) CI 친화(exit 1). npm run lint / test:fast 에 배선.
5
+ 'use strict';
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const cp = require('child_process');
9
+
10
+ const ROOT = path.resolve(process.argv[2] || path.join(__dirname, '..')); // 테스트용 루트 오버라이드 허용
11
+ const TARGET_DIRS = ['bin', 'lib', 'scripts'];
12
+ const failures = [];
13
+
14
+ function listFiles(dir, ext) {
15
+ const out = [];
16
+ const walk = (d) => {
17
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
18
+ if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
19
+ const p = path.join(d, e.name);
20
+ if (e.isDirectory()) walk(p);
21
+ else if (e.name.endsWith(ext)) out.push(p);
22
+ }
23
+ };
24
+ if (fs.existsSync(dir)) walk(dir);
25
+ return out;
26
+ }
27
+
28
+ const jsFiles = TARGET_DIRS.flatMap(d => listFiles(path.join(ROOT, d), '.js'));
29
+
30
+ // ① 구문 (node --check)
31
+ for (const f of jsFiles) {
32
+ const r = cp.spawnSync(process.execPath, ['--check', f], { encoding: 'utf8', timeout: 30000 });
33
+ if (r.error) { failures.push(`syntax-check spawn failed: ${path.relative(ROOT, f)} — ${r.error.code || r.error.message}`); continue; } // (검수 #3) 스폰 실패를 구문오류와 구분
34
+ if (r.status !== 0) {
35
+ const lines = (r.stderr || '').split('\n');
36
+ const detail = lines.find(l => /Error/.test(l)) || lines[0] || ''; // (검수 #3) SyntaxError 본문 우선
37
+ failures.push(`syntax: ${path.relative(ROOT, f)} — ${detail.trim()}`);
38
+ }
39
+ }
40
+
41
+ // ② 위험 문자 + ③ BOM
42
+ for (const f of jsFiles) {
43
+ const buf = fs.readFileSync(f);
44
+ if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) failures.push(`bom: ${path.relative(ROOT, f)}`);
45
+ const text = buf.toString('utf8');
46
+ const ls = text.indexOf('\u2028'); const ps = text.indexOf('\u2029'); // 이스케이프 표기 — 자기참조 트랩 방지
47
+ if (ls >= 0) failures.push(`U+2028 line-separator: ${path.relative(ROOT, f)}:${text.slice(0, ls).split('\n').length} (과거 전-명령 SyntaxError 실사고 문자)`);
48
+ if (ps >= 0) failures.push(`U+2029 paragraph-separator: ${path.relative(ROOT, f)}:${text.slice(0, ps).split('\n').length}`);
49
+ }
50
+
51
+ // ④ 저장소 JSON 유효성 (배포 관련 루트 + data 성격 파일)
52
+ const jsonFiles = ['package.json', ...listFiles(path.join(ROOT, 'lib'), '.json'), ...listFiles(path.join(ROOT, 'scripts'), '.json')].map(f => path.isAbsolute(f) ? f : path.join(ROOT, f));
53
+ for (const f of jsonFiles) {
54
+ if (!fs.existsSync(f)) continue;
55
+ try { JSON.parse(fs.readFileSync(f, 'utf8').replace(/^/, '')); } catch (e) { failures.push(`json: ${path.relative(ROOT, f)} — ${e.message.slice(0, 80)}`); }
56
+ }
57
+
58
+ if (failures.length) {
59
+ console.log(`✗ lint: ${failures.length} issue(s)`);
60
+ failures.forEach(x => console.log(' - ' + x));
61
+ process.exit(1);
62
+ }
63
+ console.log(`✓ lint: ${jsFiles.length} js (syntax/U+2028/BOM) + ${jsonFiles.length} json — clean`);