leerness 1.9.409 → 1.9.410

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.410 — 2026-06-07 — 값 없는 --path raw TypeError 크래시 차단 (8번째 버그헌트, UR-0114)
4
+
5
+ **🛡 견고성 — `--path`(값 없이) 같은 비-문자열 인자가 `path.resolve(true)` raw Node TypeError 로 크래시하던 것을 cwd 폴백으로 차단.**
6
+
7
+ ### 배경 (2차 버그헌트 arg-boundary)
8
+ 값 없는 `--path` 플래그는 `arg()` 가 boolean `true` 를 반환 → `absRoot(p) = path.resolve(p || cwd)` 에서 `true || cwd = true`(truthy) → `path.resolve(true)` → "The paths[0] argument must be of type string. Received type boolean (true)" raw 크래시. absRoot 를 쓰는 모든 명령(status/audit/handoff/…)에 영향.
9
+
10
+ ### 구현
11
+ - `absRoot(p)`: `(typeof p === 'string' && p.trim()) ? p : process.cwd()` — 비-문자열/빈/공백 입력은 cwd 폴백(`--path=` 빈값 동작과 일관). 실제 문자열 경로는 그대로 보존.
12
+
13
+ ### 검증 (회귀 0)
14
+ - **selftest 155→156 PASS** (true/''/undefined/공백 → cwd, 실경로 보존 5케이스).
15
+ - **E2E 348→349 PASS** (`status --path` raw TypeError 누출 안 됨 + cwd 폴백 정상 실행).
16
+
3
17
  ## 1.9.409 — 2026-06-07 — encoding-check --apply 가 .sh/shebang 깨뜨리던 것 차단 (8번째 버그헌트, UR-0113)
4
18
 
5
19
  **🐚 인코딩 도구 자기모순 수정 — `env encoding-check --apply` 가 .sh(shebang) 파일에 BOM 을 추가해 스크립트 실행을 깨뜨리던 것 차단.**
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
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.409-green)]() [![tests](https://img.shields.io/badge/e2e-348%2F348-success)]() [![selftest](https://img.shields.io/badge/selftest-155-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-85-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-10-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.410-green)]() [![tests](https://img.shields.io/badge/e2e-349%2F349-success)]() [![selftest](https://img.shields.io/badge/selftest-156-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-85-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-10-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
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.409 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
474
+ 이 프로젝트는 Leerness v1.9.410 하네스를 사용합니다. 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.409는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
528
+ Leerness v1.9.410는 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.409는 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.409)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
549
+ 현재 누적: **70 라운드 (1.9.40 → 1.9.410)** · 매 라운드 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.409: 2026-06-06
587
+ Last synced by Leerness v1.9.410: 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.409';
34
+ const VERSION = '1.9.410';
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') 시 호스트 프로세스 오염.
@@ -3021,6 +3021,7 @@ function _selfTestCases() {
3021
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; } },
3022
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
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 {} } } },
3024
+ { name: '8번째 버그헌트 (UR-0114): absRoot 비문자열(--path 값없음 boolean true) → cwd 폴백(raw TypeError 차단) (1.9.410)', run: () => { const io = require('../lib/io'); const cwd = process.cwd(); const tBool = io.absRoot(true) === cwd; const tEmpty = io.absRoot('') === cwd; const tUndef = io.absRoot(undefined) === cwd; const tSpace = io.absRoot(' ') === cwd; const tReal = io.absRoot(os.tmpdir()) === path.resolve(os.tmpdir()); return tBool && tEmpty && tUndef && tSpace && tReal; } },
3024
3025
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
3025
3026
  ];
3026
3027
  }
package/lib/io.js CHANGED
@@ -20,7 +20,9 @@ function failJson(jsonMode, code, msg) {
20
20
  function today() { return new Date().toISOString().slice(0, 10); }
21
21
  function now() { return new Date().toISOString(); }
22
22
 
23
- function absRoot(p) { return path.resolve(p || process.cwd()); }
23
+ // 1.9.410 (8번째 버그헌트, UR-0114): 없는 --path 는 arg()가 boolean true 를 반환 → 'true || cwd'=true(truthy) → path.resolve(true) raw TypeError.
24
+ // 비-문자열/공백 입력은 cwd 로 폴백(--path= 빈값 동작과 일관) → 크래시 차단.
25
+ function absRoot(p) { return path.resolve((typeof p === 'string' && p.trim()) ? p : process.cwd()); }
24
26
  function exists(p) { return fs.existsSync(p); }
25
27
  function read(p) {
26
28
  // 1.9.147: UTF-8 BOM 자동 strip — Windows PowerShell Out-File 등이 BOM 붙이는 경우 JSON.parse 실패 방지
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.409",
3
+ "version": "1.9.410",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -5811,5 +5811,20 @@ total++;
5811
5811
  if (!ok) failed++;
5812
5812
  }
5813
5813
 
5814
+ // 1.9.410 회귀 (8번째 버그헌트, UR-0114): 값 없는 --path (boolean true) 가 raw TypeError 크래시 안 함(cwd 폴백)
5815
+ total++;
5816
+ {
5817
+ let ok = false;
5818
+ try {
5819
+ const r = cp.spawnSync(process.execPath, [CLI, 'status', '--path'], { encoding: 'utf8', timeout: 15000 });
5820
+ const out = (r.stdout || '') + (r.stderr || '');
5821
+ const noCrash = !/paths\[0\].*argument must be of type string|Received type boolean/.test(out); // raw TypeError 누출 안 됨
5822
+ const ran = /Leerness:/.test(out) || /Files:/.test(out); // cwd 로 폴백해 정상 실행
5823
+ ok = noCrash && ran;
5824
+ } catch {}
5825
+ console.log(ok ? '✓ B(1.9.410) 8th버그헌트: 값없는 --path raw TypeError 차단(cwd 폴백) (UR-0114)' : '✗ --path 크래시 가드 실패');
5826
+ if (!ok) failed++;
5827
+ }
5828
+
5814
5829
  console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
5815
5830
  if (failed > 0) process.exit(1);