leerness 1.9.408 → 1.9.409
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 +14 -0
- package/README.md +5 -5
- package/bin/harness.js +9 -1
- package/package.json +1 -1
- package/scripts/e2e.js +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.409 — 2026-06-07 — encoding-check --apply 가 .sh/shebang 깨뜨리던 것 차단 (8번째 버그헌트, UR-0113)
|
|
4
|
+
|
|
5
|
+
**🐚 인코딩 도구 자기모순 수정 — `env encoding-check --apply` 가 .sh(shebang) 파일에 BOM 을 추가해 스크립트 실행을 깨뜨리던 것 차단.**
|
|
6
|
+
|
|
7
|
+
### 배경 (2차 버그헌트 encoding 차원)
|
|
8
|
+
`--apply` 는 비-ASCII + no-BOM "위험" 파일에 UTF-8 BOM 을 추가하는데, `.sh`(예: `#!/bin/bash`) 에 BOM(EF BB BF)이 shebang 앞에 붙으면 커널이 `#!` 를 인식 못 해 스크립트 실행 불가. leerness 자체 인코딩 도구가 .sh 를 깨뜨리는 모순.
|
|
9
|
+
|
|
10
|
+
### 구현
|
|
11
|
+
- `--apply` 루프: `.sh` 확장자 또는 첫 2바이트가 `#!`(shebang)인 파일은 BOM 추가 스킵(`action: skipped-shebang`). .ps1/.bat 등은 기존대로 BOM 추가.
|
|
12
|
+
|
|
13
|
+
### 검증 (회귀 0)
|
|
14
|
+
- **selftest 154→155 PASS** (.sh shebang 보존 + .ps1 BOM 추가 행위검증).
|
|
15
|
+
- **E2E 347→348 PASS** (CLI --apply: .sh 첫바이트 `#!` 보존(BOM 없음) + .ps1 BOM 추가).
|
|
16
|
+
|
|
3
17
|
## 1.9.408 — 2026-06-07 — CRLF/CR SKILL.md frontmatter 파싱 복구 (8번째 버그헌트, UR-0112)
|
|
4
18
|
|
|
5
19
|
**🪟 Windows 호환 — CRLF/CR 줄바꿈의 SKILL.md frontmatter 가 통째로 소실돼 `skill install` 이 "name 필수" 로 실패하던 것 수정.**
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
> **A CLI harness that stops AI coding agents from faking completion, duplicating work, forgetting context, and colliding.**
|
|
5
5
|
|
|
6
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []()
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -471,7 +471,7 @@ MIT — © leerness contributors
|
|
|
471
471
|
<!-- leerness:project-readme:start -->
|
|
472
472
|
## Leerness Project Harness
|
|
473
473
|
|
|
474
|
-
이 프로젝트는 Leerness v1.9.
|
|
474
|
+
이 프로젝트는 Leerness v1.9.409 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
475
475
|
|
|
476
476
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
477
477
|
|
|
@@ -525,7 +525,7 @@ leerness memory restore decision <date|title>
|
|
|
525
525
|
|
|
526
526
|
### MCP server (외부 AI 통합)
|
|
527
527
|
|
|
528
|
-
Leerness v1.9.
|
|
528
|
+
Leerness v1.9.409는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
|
|
529
529
|
|
|
530
530
|
```jsonc
|
|
531
531
|
// 카테고리별
|
|
@@ -546,7 +546,7 @@ Leerness v1.9.408는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
546
546
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
547
547
|
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) 다음 라운드 예약.
|
|
548
548
|
|
|
549
|
-
현재 누적: **70 라운드 (1.9.40 → 1.9.
|
|
549
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.409)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
550
550
|
|
|
551
551
|
### 성능 가이드 (1.9.140 측정)
|
|
552
552
|
|
|
@@ -584,6 +584,6 @@ leerness release pack --close --auto-main-push
|
|
|
584
584
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
585
585
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
586
586
|
|
|
587
|
-
Last synced by Leerness v1.9.
|
|
587
|
+
Last synced by Leerness v1.9.409: 2026-06-06
|
|
588
588
|
<!-- leerness:project-readme:end -->
|
|
589
589
|
|
package/bin/harness.js
CHANGED
|
@@ -31,7 +31,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
31
31
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
32
32
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_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 분리 (MERGE_OVERWRITE_FILES/MINIMAL_SKIP_KEYS 포함)
|
|
33
33
|
|
|
34
|
-
const VERSION = '1.9.
|
|
34
|
+
const VERSION = '1.9.409';
|
|
35
35
|
|
|
36
36
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
37
37
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -2628,6 +2628,13 @@ function envCmd(root, sub) {
|
|
|
2628
2628
|
try {
|
|
2629
2629
|
const fullPath = path.join(root, r.file);
|
|
2630
2630
|
const orig = fs.readFileSync(fullPath);
|
|
2631
|
+
// 1.9.409 (8번째 버그헌트, UR-0113): .sh / shebang(#!) 파일은 BOM 추가 금지.
|
|
2632
|
+
// BOM(EF BB BF)이 shebang 앞에 붙으면 커널이 '#!' 를 인식 못 해 스크립트 실행 불가 → encoding check 가 자기 도구로 .sh 를 깨뜨리던 모순 차단.
|
|
2633
|
+
const isShebang = orig.length >= 2 && orig[0] === 0x23 && orig[1] === 0x21; // '#!'
|
|
2634
|
+
if (/\.sh$/i.test(r.file) || isShebang) {
|
|
2635
|
+
result.applied.push({ file: r.file, action: 'skipped-shebang (BOM은 shebang을 깨뜨림 — .sh는 no-BOM UTF-8 유지)' });
|
|
2636
|
+
continue;
|
|
2637
|
+
}
|
|
2631
2638
|
const bom = Buffer.from([0xEF, 0xBB, 0xBF]);
|
|
2632
2639
|
const fixed = Buffer.concat([bom, orig]);
|
|
2633
2640
|
fs.writeFileSync(fullPath, fixed);
|
|
@@ -3013,6 +3020,7 @@ function _selfTestCases() {
|
|
|
3013
3020
|
{ name: '8번째 버그헌트 (UR-0110): rule/decision/lesson add 동시쓰기 _withLock 직렬화 (UR-0043 갭 메움) (1.9.406)', run: () => { const src = read(__filename); const L = '_withLock('; const ruleLock = src.includes(L + 'rulesPath' + '(root), () =>'); const decLock = src.includes(L + 'decisionsJsonPath' + '(root), () =>'); const lesLock = src.includes(L + 'lessonsJsonPath' + '(root), () =>'); return ruleLock && decLock && lesLock; } },
|
|
3014
3021
|
{ name: '8번째 버그헌트 (UR-0111): MCP feature_link safe-write tier(권한경계) + _parseLimit NaN/음수 가드 (1.9.407)', run: () => { const m = require('../lib/pure-utils'); const pl = m._parseLimit('abc', 10) === 10 && m._parseLimit('-5', 10) === 10 && m._parseLimit('0', 10) === 10 && m._parseLimit('3', 10) === 3 && m._parseLimit('7.9', 10) === 7; const tools = require('../lib/mcp-tools'); const arr = Array.isArray(tools) ? tools : (tools.MCP_TOOLS || []); const fl = arr.find(x => x.name === 'leerness_feature_link'); const tierOk = !!fl && fl.requiredTier === 'safe-write'; return pl && tierOk; } },
|
|
3015
3022
|
{ name: '8번째 버그헌트 (UR-0112): _parseSkillMd CRLF/CR 줄바꿈 정규화 (Windows SKILL.md meta 소실 차단) (1.9.408)', run: () => { const m = require('../lib/pure-utils'); const lf = m._parseSkillMd('---\nname: s\ndescription: d\n---\nbody'); const crlf = m._parseSkillMd('---\r\nname: s\r\ndescription: d\r\n---\r\nbody'); const cr = m._parseSkillMd('---\rname: s\rdescription: d\r---\rbody'); const bom = m._parseSkillMd('---\r\nname: s\r\n---\r\nbody'); return lf.meta.name === 's' && crlf.meta.name === 's' && crlf.meta.description === 'd' && cr.meta.name === 's' && bom.meta.name === 's'; } },
|
|
3023
|
+
{ name: '8번째 버그헌트 (UR-0113): env encoding-check --apply 가 .sh/shebang 에 BOM 미추가(shebang 보존) (1.9.409)', run: () => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_bom_')); try { const sh = path.join(tmp, 's.sh'); fs.writeFileSync(sh, '#!/bin/bash\n# 한글\necho hi\n'); const ps = path.join(tmp, 's.ps1'); fs.writeFileSync(ps, '# 한글\nWrite-Host hi\n'); const save = process.argv; let out = ''; const _w = process.stdout.write; try { process.argv = ['node', 'h', 'env', 'encoding-check', '--path', tmp, '--apply', '--json']; process.stdout.write = s => { out += s; return true; }; envCmd(tmp, 'encoding-check'); } catch {} finally { process.stdout.write = _w; process.argv = save; } const shBuf = fs.readFileSync(sh); const psBuf = fs.readFileSync(ps); const shNoBom = !(shBuf[0] === 0xEF && shBuf[1] === 0xBB && shBuf[2] === 0xBF) && shBuf[0] === 0x23 && shBuf[1] === 0x21; const psBom = psBuf[0] === 0xEF && psBuf[1] === 0xBB && psBuf[2] === 0xBF; return shNoBom && psBom; } finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} } } },
|
|
3016
3024
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3017
3025
|
];
|
|
3018
3026
|
}
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -5790,5 +5790,26 @@ total++;
|
|
|
5790
5790
|
if (!ok) failed++;
|
|
5791
5791
|
}
|
|
5792
5792
|
|
|
5793
|
+
// 1.9.409 회귀 (8번째 버그헌트, UR-0113): env encoding-check --apply 가 .sh/shebang 에 BOM 미추가(실행 깨짐 방지), .ps1 은 추가
|
|
5794
|
+
total++;
|
|
5795
|
+
{
|
|
5796
|
+
let ok = false;
|
|
5797
|
+
try {
|
|
5798
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-bomsh-'));
|
|
5799
|
+
cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
5800
|
+
fs.writeFileSync(path.join(d, 'script.sh'), '#!/bin/bash\n# 한글 주석\necho hi\n');
|
|
5801
|
+
fs.writeFileSync(path.join(d, 'script.ps1'), '# 한글 주석\nWrite-Host hi\n');
|
|
5802
|
+
cp.spawnSync(process.execPath, [CLI, 'env', 'encoding-check', '--path', d, '--apply'], { encoding: 'utf8', timeout: 15000 });
|
|
5803
|
+
const sh = fs.readFileSync(path.join(d, 'script.sh'));
|
|
5804
|
+
const ps = fs.readFileSync(path.join(d, 'script.ps1'));
|
|
5805
|
+
const shOk = sh[0] === 0x23 && sh[1] === 0x21 && !(sh[0] === 0xEF); // '#!' 보존, BOM 없음
|
|
5806
|
+
const psOk = ps[0] === 0xEF && ps[1] === 0xBB && ps[2] === 0xBF; // .ps1 BOM 추가
|
|
5807
|
+
fs.rmSync(d, { recursive: true, force: true });
|
|
5808
|
+
ok = shOk && psOk;
|
|
5809
|
+
} catch {}
|
|
5810
|
+
console.log(ok ? '✓ B(1.9.409) 8th버그헌트: encoding-check --apply .sh shebang 보존(BOM 미추가) + .ps1 BOM (UR-0113)' : '✗ encoding-check BOM 스킵 실패');
|
|
5811
|
+
if (!ok) failed++;
|
|
5812
|
+
}
|
|
5813
|
+
|
|
5793
5814
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
5794
5815
|
if (failed > 0) process.exit(1);
|