leerness 1.20.0 → 1.21.0

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,59 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.21.0 — 2026-06-15 — 🛡️ [안정화/Stable] 영어 온보딩 첫걸음 + 검증 강화 안정 minor
4
+
5
+ **🛡️ 안정화(Stable) minor — 비한국어 사용자에게 영어 온보딩을 실제로 전달.** 직전 minor(1.20.0) 이후 누적된 패치 2건(1.20.1~1.20.2)을 검증·통합해 npm 공개. R-0011 정책의 12번째 stable minor. 핵심: **영어 첫 화면(opt-in)** 이 npm 사용자에게 닿고, 검증 플래그십의 정적 우회가 닫힘.
6
+
7
+ ### 이번 minor 통합 (1.20.1~1.20.2)
8
+ - **🌐 CLI 영어화 Phase 1 (UR-0010, 사용자 지정 방향)**: 영어 README 정문 ↔ 한국어 CLI 불일치(외부평가 #1 병목) 해소 시작. `--language en` / `LEERNESS_LANG=en` / en 으로 init 된 프로젝트에서 **init 시작하기 배너 전체 + handoff 헤드라인 라벨**이 영어로. 한국어 우선 정체성은 **기본값 유지**(영어는 명시 opt-in, system locale 미사용). 순수 `_uiLang`/`_tx`.
9
+ - **🛡️ 장식 no-op 정적 우회 차단 (검증)**: `"use strict"; module.exports={}` · `{}; //c` · `void 0;` · `;;;;` · `0;` 같은 "실로직 0" 파일이 정적 verify-claim 을 통과하던 우회를 차감식 residue 검사로 폐쇄(FP 0).
10
+ - **📄 README 플래그십 데모 정합**: task-id(T-0002) + "실파일 쓰면 같은 명령 통과" 정정 — 신규 유저 복붙이 실제로 동작. lens 헤더 동적 버전.
11
+ - **🔬 신뢰 투명성/렌즈 완전판** (1.19.x 누적 — 1.20.0 에 포함됨, 본 minor 는 1.20.x 패치 묶음).
12
+
13
+ ### 잔여 (UR-0010 Phase 2+, 백로그)
14
+ - handoff 헤드라인 **항목 라벨**(drift·보안·skills·health·mem 등 ~20종) + session close·verify-claim·help·status 출력 영어화 — 단계적 확대.
15
+
16
+ ### 검증 (회귀 0)
17
+ - **selftest 235→236** · **E2E 365/365** · 영어 배너/헤드라인 행위 + no-op 우회 차단 + 게시본 재실증.
18
+
19
+ ### 안정화 표시 (R-0006)
20
+ CHANGELOG [안정화/Stable] · git tag (Stable) · GitHub release (`--latest`) · npm dist-tag `stable` 시도.
21
+
22
+ ## 1.20.2 — 2026-06-15 — CLI 영어화 Phase 1: 첫 화면(init 배너·handoff 헤드라인) 영어 opt-in (UR-0010)
23
+
24
+ **🌐 "어떤 언어, 어떤 AI 에이전트로 작업하든"의 실질 첫걸음.** 외부평가가 꼽은 단일 최대 병목 — 영어 README 정문 ↔ 한국어 CLI 불일치 — 를 단계적으로 해소 시작. 한국어 우선 정체성은 기본값으로 유지하고, **영어는 명시 opt-in**.
25
+
26
+ ### 변경 (UR-0010 Phase 1)
27
+ - **UI 언어 해석 `_uiLang(root)`**: 우선순위 `--language` 플래그 > `LEERNESS_LANG` env > `.harness/manifest.json` 의 language(init 선택) > **`ko`(기본)**. system locale 은 의도적으로 미사용(영어 OS 한국 사용자 놀람 방지) — 영어는 항상 명시 opt-in.
28
+ - **init 시작하기 배너 영어화**: `--language en`(또는 LEERNESS_LANG=en, 또는 en 으로 init 된 프로젝트)에서 "Get started (3 steps)" + 메모리/인과/보안/MCP/release 섹션 전체 영어로. 비한국어 신규 유저의 첫 화면이 더 이상 한국어 벽이 아님.
29
+ - **handoff 헤드라인 라벨 영어화**: `📊 헤드라인 (버전태그…)` → 영어 시 `📊 Headline`(버전태그 노이즈 제거).
30
+ - 순수 함수 `_uiLang`/`_tx` 분리(단위 테스트). **한국어 기본 회귀 0**(플래그/env/manifest 없으면 그대로 한국어).
31
+
32
+ ### 잔여 (UR-0010 Phase 2+, 백로그)
33
+ - handoff 헤드라인 **항목 라벨**(보안 OK·skills·health…) 영어화, 그 외 명령(session close·verify-claim 출력·help·status 등) 영어화 — 단계적 확대. Phase 1 은 첫인상 표면(배너+헤드라인 프리픽스)에 한정.
34
+
35
+ ### 검증 (회귀 0)
36
+ - **selftest 235→236** (`_uiLang` 해석 4종 + `_tx` + 첫화면 분기 소스가드) · 행위: `--language en` → 영어 배너/헤드라인, 플래그 없음 → 한국어 유지 · **E2E 365/365**.
37
+ - patch(1.20.2) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
38
+
39
+ ## 1.20.1 — 2026-06-15 — 1.20.0 외부평가 채택: 장식 no-op 우회 차단 + README 데모 정합
40
+
41
+ **🔎 게시본 1.20.0 신규 멀티모델 클린룸 평가(5축) 채택 — 맹신 X로 재현된 것만.** 검증(8)·정직성/보안(9)·품질렌즈(8)는 GPT-5.5 대비 뚜렷이 상승했고, 평가가 찾은 재현 가능한 P2 두 가지(검증 정적모드 우회 + README 데모 부정확)를 닫음.
42
+
43
+ ### 변경 (1.20.0 외부평가 재현 채택)
44
+ - **🛡️ 장식된 no-op 정적 우회 차단 (검증, P2)**: 빈껍데기 검출이 순수 빈-export 형태만 잡던 것 — `"use strict"; module.exports={}`·`{}; // comment`·`module.exports={}; void 0;`·`;;;;`·`0;` 같은 "실로직 0" 파일이 정적 `verify-claim`을 exit 0 통과하던 우회 폐쇄. `_vcImplIsEmpty` 에 **차감식(residue) 검사** 추가 — 디렉티브 프롤로그 + 빈 export + no-op 리터럴/문을 제거하고 의미 토큰이 하나도 안 남으면 스텁. FP 0(실코드·`require` 재노출·이름붙은 export·`module.exports=0` 같은 의도적 값은 통과). `let x=1` 류 무의미 선언은 식별자가 남아 통과 — AST 토큰화 필요(백로그).
45
+ - **📄 README 플래그십 데모 정합 (온보딩, P2)**: ① task-id 수정 — init 이 T-0001(계획 task)을 차지하므로 `task add` 는 T-0002 생성, 데모를 실제 흐름(출력된 id 사용)으로. ② "실파일+실테스트 쓰면 같은 명령 통과" 정정 — evidence 의 테스트 개수 주장은 실측과 대조되므로(거짓이면 거부) 정직한 evidence + `--run-tests --test-cmd` 안내로 교체. 신규 유저 복붙이 실제로 동작.
46
+ - **🔖 lens 헤더 버전 정합 (P3)**: 하드코딩 `(1.18.3)` → 동적 `(v${VERSION})`.
47
+
48
+ ### 백로그 등록 (평가 추천 우선순위, 이번 라운드 미포함)
49
+ - **UR-0010 (최우선)**: CLI UX 영어화 — `--language en` 이 템플릿 파일만 바꾸고 CLI 자체 출력(init 배너·handoff 헤드라인·help)은 한국어 유지. 영어 README 정문 ↔ 한국어 CLI 불일치가 비한국어 시장의 단일 최대 병목(거대 작업, UR-0042 잔여 통합).
50
+ - **UR-0011**: 표면 core-vs-extended 티어링 — 84커맨드+85툴 평면 노출, banner/about/commands/help 의 "start here" 4종 불일치 → 핵심8 표준화 + `--help` 정리.
51
+ - **UR-0012**: secret scan AWS AKIA 키 ID + 추가 prefix 커버리지.
52
+
53
+ ### 검증 (회귀 0)
54
+ - **selftest 235** (no-op 우회 6종 차단 + FP 가드 3종 행위) · 5종 우회 재현 차단·FP 0 확인 · README 데모 재현(task-id·count 정합) · **E2E 365/365**.
55
+ - patch(1.20.1) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
56
+
3
57
  ## 1.20.0 — 2026-06-15 — 🛡️ [안정화/Stable] 품질 렌즈 완전판 + 신뢰 투명성 안정 minor
4
58
 
5
59
  **🛡️ 안정화(Stable) minor — "AI가 스스로 질문하고 행동하도록" 완성.** 직전 minor(1.19.0) 이후 누적된 패치 3건(1.19.1~1.19.3)을 검증·통합해 npm 공개. R-0011 정책의 11번째 stable minor. 핵심 테마: **사용자 자기질문 품질 렌즈를 정적 명령에서 완전한 워크플로 기능으로** + 신뢰 투명성(클린룸 평가 공개).
package/README.md CHANGED
@@ -28,12 +28,14 @@ npx leerness handoff . # everything your AI should know right now, in on
28
28
  Your project now has agent-independent memory. To see the flagship feature — catching a false "done" claim:
29
29
 
30
30
  ```bash
31
- npx leerness task add "Implement payment API"
32
- npx leerness task update T-0001 --status done --evidence "payment.js done, 5 tests passed"
33
- npx leerness verify-claim T-0001 # exit 1 — payment.js does not exist, no tests ran. Claim rejected.
31
+ npx leerness task add "Implement payment API" # prints the new id, e.g. T-0002 — use it below
32
+ npx leerness task update T-0002 --status done --evidence "payment.js implemented + tested"
33
+ npx leerness verify-claim T-0002 # exit 1 — payment.js does not exist. Claim rejected.
34
34
  ```
35
35
 
36
- Write the real file with a real test and the same command passes. That is the whole idea: **"done" must match reality.**
36
+ Now actually write `payment.js`, then run the **same** `verify-claim T-0002` it exits 0. That is the whole idea: **"done" must match reality.**
37
+
38
+ > Tip: if your evidence claims a specific test count (e.g. "5 tests passed"), leerness measures the real count and rejects a mismatch — so claim only what's true, or add `--run-tests --test-cmd "<your test cmd>"` to verify by running them.
37
39
 
38
40
  > Want a smaller footprint? `leerness init . --minimal` installs only the core memory + verification files instead of the full set.
39
41
 
@@ -102,7 +104,7 @@ MIT
102
104
  <!-- leerness:project-readme:start -->
103
105
  ## Leerness Project Harness
104
106
 
105
- 이 프로젝트는 Leerness v1.20.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
107
+ 이 프로젝트는 Leerness v1.21.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
106
108
 
107
109
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
108
110
 
@@ -156,7 +158,7 @@ leerness memory restore decision <date|title>
156
158
 
157
159
  ### MCP server (외부 AI 통합)
158
160
 
159
- Leerness v1.20.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
161
+ Leerness v1.21.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
160
162
 
161
163
  ```jsonc
162
164
  // 카테고리별
@@ -177,7 +179,7 @@ Leerness v1.20.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
177
179
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
178
180
  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) 다음 라운드 예약.
179
181
 
180
- 현재 누적: **70 라운드 (1.9.40 → 1.20.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
182
+ 현재 누적: **70 라운드 (1.9.40 → 1.21.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
181
183
 
182
184
  ### 성능 가이드 (1.9.140 측정)
183
185
 
@@ -215,6 +217,6 @@ leerness release pack --close --auto-main-push
215
217
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
216
218
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
217
219
 
218
- Last synced by Leerness v1.20.0: 2026-06-15
220
+ Last synced by Leerness v1.21.0: 2026-06-15
219
221
  <!-- leerness:project-readme:end -->
220
222
 
package/bin/leerness.js CHANGED
@@ -32,7 +32,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
32
32
  // 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
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
 
35
- const VERSION = '1.20.0';
35
+ const VERSION = '1.21.0';
36
36
 
37
37
  // 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
38
38
  // 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
@@ -252,6 +252,22 @@ function detectLanguageValue(root, value = 'auto') {
252
252
  // ③ en 폴백
253
253
  return 'en';
254
254
  }
255
+ // 1.20.2 (UR-0010 CLI 영어화 Phase 1): UI 출력 언어 해석 — 한국어 우선 기본, 영어 opt-in.
256
+ // 우선순위: --language 플래그 > LEERNESS_LANG env > .harness/manifest.json 의 language(init 선택) > 'ko'.
257
+ // (system locale 은 의도적으로 미사용 — 영어 OS 한국 사용자 놀람 방지. 영어는 명시 opt-in.)
258
+ function _uiLang(root) {
259
+ try {
260
+ const flag = String(arg('--language', '') || '').toLowerCase();
261
+ if (flag === 'en' || flag === 'ko') return flag;
262
+ const env = String(process.env.LEERNESS_LANG || '').toLowerCase();
263
+ if (env === 'en' || env === 'ko') return env;
264
+ const mf = path.join(absRoot(root || process.cwd()), '.harness', 'manifest.json');
265
+ if (exists(mf)) { const l = String((JSON.parse(read(mf)) || {}).language || '').toLowerCase(); if (l === 'en' || l === 'ko') return l; }
266
+ } catch {}
267
+ return 'ko';
268
+ }
269
+ // ko/en 쌍에서 해석된 UI 언어로 선택 (Phase 1: 첫 화면 한정 사용).
270
+ function _tx(lang, ko, en) { return lang === 'en' ? en : ko; }
255
271
  function fm(role, readWhen, updateWhen, body) {
256
272
  return `---\nleernessRole: ${role}\nreadWhen:\n${readWhen.map(x => ' - ' + x).join('\n')}\nupdateWhen:\n${updateWhen.map(x => ' - ' + x).join('\n')}\ndoNotStore:\n - 실제 토큰\n - 비밀번호\n - 운영 쿠키\n - 민감한 개인정보 원문\n---\n${MARK}\n${body}`;
257
273
  }
@@ -3624,7 +3640,18 @@ function _selfTestCases() {
3624
3640
  && E('module.exports = Object.freeze({ a: 1 });\n') === false
3625
3641
  && E('module.exports = class { run(){ return 1; } };\n') === false
3626
3642
  && E('module.exports = (a,b) => a+b;\n') === false;
3627
- return base && bypass && real;
3643
+ // 1.20.1 (1.20.0 외부평가 재현): 장식된 no-op 우회(true 여야 함) — 디렉티브 프롤로그 + no-op 문
3644
+ const noop = E('"use strict";\nmodule.exports = {};\n') === true
3645
+ && E('{}; // c\n') === true
3646
+ && E('module.exports = {};\nvoid 0;\n') === true
3647
+ && E(';;;;\n') === true
3648
+ && E('0;\n') === true
3649
+ && E("'use strict';\nexports.default = {};\n") === true;
3650
+ // 1.20.1 FP 가드(false 여야 함): 단일 의미 토큰만 있어도 통과
3651
+ const noopFP = E('"use strict";\nfunction f(){ return 1; }\nmodule.exports = { f };\n') === false
3652
+ && E('module.exports = 0;\n') === false // 0 을 export 하는 의도적 값 — 스텁 아님
3653
+ && E('module.exports = require("./x"); void 0;\n') === false;
3654
+ return base && bypass && real && noop && noopFP;
3628
3655
  } },
3629
3656
  { name: '위장 스텁 차단 (1.18.2): stub 루프 _vcImplIsEmpty 사용 + 메시지 + FILE_EXTS java/php 정합 (소스 가드)', run: () => {
3630
3657
  const src = read(__filename);
@@ -3691,6 +3718,25 @@ function _selfTestCases() {
3691
3718
  const d = read(dp);
3692
3719
  return /AI clean-room evaluations/i.test(d) && /heuristic, not semantic/i.test(d) && /npm i leerness@/.test(d);
3693
3720
  } },
3721
+ { name: 'CLI 영어화 Phase 1 (1.20.2, UR-0010): _uiLang 해석(flag>env>manifest>ko) + 첫화면 _t 적용 (행위+소스)', run: () => {
3722
+ const save = process.argv; const saveEnv = process.env.LEERNESS_LANG;
3723
+ try {
3724
+ // 기본 ko (한국어 우선 정체성 보존)
3725
+ process.argv = ['node', 'h', 'handoff']; delete process.env.LEERNESS_LANG;
3726
+ if (_uiLang('/no/such/dir') !== 'ko') return false;
3727
+ // --language en 플래그 → en
3728
+ process.argv = ['node', 'h', 'handoff', '--language', 'en'];
3729
+ if (_uiLang('/no/such/dir') !== 'en') return false;
3730
+ // LEERNESS_LANG env → en (플래그 없을 때)
3731
+ process.argv = ['node', 'h', 'handoff']; process.env.LEERNESS_LANG = 'en';
3732
+ if (_uiLang('/no/such/dir') !== 'en') return false;
3733
+ // _tx 선택
3734
+ if (_tx('en', '가', 'A') !== 'A' || _tx('ko', '가', 'A') !== '가') return false;
3735
+ } finally { process.argv = save; if (saveEnv === undefined) delete process.env.LEERNESS_LANG; else process.env.LEERNESS_LANG = saveEnv; }
3736
+ // 첫화면(배너/헤드라인)이 언어 분기 사용
3737
+ const src = read(__filename);
3738
+ return src.includes("const L = _uiLang(arg('--path', process.cwd()));") && src.includes("_uiLang(root) === 'en' ? '📊 Headline'");
3739
+ } },
3694
3740
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
3695
3741
  ];
3696
3742
  }
@@ -4326,7 +4372,7 @@ function lensCmd(domain, opts = {}) {
4326
4372
  const picked = domain ? { [domain]: catalog[domain] } : catalog;
4327
4373
  if (jsonMode) { log(JSON.stringify({ ok: true, lenses: picked }, null, 2)); return; }
4328
4374
  const hasCustom = Object.values(catalog).some(l => l && (l._custom || l._customAdded));
4329
- log(`# leerness lens — 분야별 자기질문 품질 렌즈 (1.18.3)${hasCustom ? ' + 프로젝트 커스텀(.harness/quality-lenses.json)' : ''}`);
4375
+ log(`# leerness lens — 분야별 자기질문 품질 렌즈 (v${VERSION})${hasCustom ? ' + 프로젝트 커스텀(.harness/quality-lenses.json)' : ''}`);
4330
4376
  log(`완료 선언 전 해당 분야 질문에 스스로 답해보세요. "그렇다(통과)"라고 답할 수 없으면 아직 완료가 아닙니다.`);
4331
4377
  for (const [key, l] of Object.entries(picked)) {
4332
4378
  log('');
@@ -8467,7 +8513,9 @@ function handoff(root) {
8467
8513
  if (parts.length) {
8468
8514
  const isTty = process.stdout && process.stdout.isTTY;
8469
8515
  const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
8470
- log(cy(`📊 헤드라인 (1.9.81/93/113/152/162/192/197/204/207/209/215/220/223/226): ${parts.join(' · ')}`));
8516
+ // 1.20.2 (UR-0010 Phase 1): 헤드라인 라벨 UI 언어 적용 (영어 버전태그 노이즈 제거; 항목 라벨 영어화는 Phase 2).
8517
+ const _hl = _uiLang(root) === 'en' ? '📊 Headline' : '📊 헤드라인 (1.9.81/93/113/152/162/192/197/204/207/209/215/220/223/226)';
8518
+ log(cy(`${_hl}: ${parts.join(' · ')}`));
8471
8519
  }
8472
8520
  } catch {}
8473
8521
  }
@@ -9892,7 +9940,19 @@ function _vcImplIsEmpty(body) {
9892
9940
  }).filter(Boolean);
9893
9941
  if (codeLines.length === 0) return true; // ① 코드 0줄
9894
9942
  const joined = codeLines.join(' ').replace(/\s+/g, ' ').trim();
9895
- return _VC_EMPTY_SHELL_RE.test(joined); // ② 빈 export 껍데기뿐
9943
+ if (_VC_EMPTY_SHELL_RE.test(joined)) return true; // ② 빈 export 껍데기뿐
9944
+ // ③ 1.20.1 (1.20.0 외부평가 재현): 장식된 no-op 우회 — 디렉티브 프롤로그 + 빈 export + no-op 문만 남으면 스텁.
9945
+ // "use strict"; module.exports={} · {};//c · module.exports={};void 0; · ;;;; · 0; (정적모드 우회) 폐쇄. FP 0: 실코드는 식별자/키워드가 남음.
9946
+ // (let x=1 같은 무의미 선언은 식별자가 남아 통과 — AST 토큰 카운트 필요, 백로그.)
9947
+ const residue = joined
9948
+ .replace(/^\s*(['"])use strict\1\s*;?/i, '') // 디렉티브 프롤로그
9949
+ .replace(/(?:module\.)?exports(?:\.[A-Za-z0-9_$]+)?\s*=\s*(?:\{\s*\}|\[\s*\])\s*;?/g, '') // 빈 export
9950
+ .replace(/export\s+default\s*(?:\{\s*\}|\[\s*\])\s*;?/g, '')
9951
+ .replace(/export\s*\{\s*\}\s*;?/g, '')
9952
+ .replace(/\bvoid\s+0\b/g, '') // void 0
9953
+ .replace(/\b(?:null|undefined|false|true|0)\b/g, '') // 단독 no-op 리터럴
9954
+ .replace(/[\s;{}()[\],.]/g, ''); // 공백/구두점
9955
+ return residue === ''; // 의미 토큰이 하나도 안 남으면 스텁
9896
9956
  }
9897
9957
 
9898
9958
  function verifyClaimCmd(root, taskId) {
@@ -11611,43 +11671,46 @@ function _banner(opts = {}) {
11611
11671
  cprint(' ' + C.green(padded) + C.dim('# ' + desc));
11612
11672
  };
11613
11673
 
11674
+ // 1.20.2 (UR-0010 Phase 1): 첫 화면 배너 — UI 언어(한국어 우선, 영어 opt-in) 적용.
11675
+ const L = _uiLang(arg('--path', process.cwd()));
11676
+ const t = (ko, en) => (L === 'en' ? en : ko);
11614
11677
  cprint('');
11615
- cprint(C.bold(C.cyan(' ✨ 시작하기 (3단계면 끝)')));
11616
- cmd('npx leerness init .', '1️⃣ 하네스 설치 + AI 도구 자동 연결');
11617
- cmd('npx leerness handoff .', '2️⃣ 세션 시작 — 컨텍스트·기억·feature impact 자동 회수');
11618
- cmd('npx leerness session close .', '3️⃣ 세션 종료 — 마감 통계 + 다음 라운드 추천');
11619
-
11620
- section('🧠 메모리 5종 CRUD (1.9.142 — cascade 방지)');
11621
- cmd('leerness task add "<제목>"', 'progress-tracker 등록');
11622
- cmd('leerness decision add "<제목>" --reason "..."', '되돌리기 어려운 결정 영구화');
11623
- cmd('leerness lesson save "<교훈>" --tag "..."', '재발견 가능한 통찰 저장');
11624
- cmd('leerness plan add "<milestone>"', '계획 단계 등록');
11625
- cmd('leerness rule add "<룰>" --trigger every-X', '자연어 영구 룰');
11626
- cmd('leerness feature add "<기능>" --files "..."', 'Feature Graph 노드 (1.9.141)');
11627
-
11628
- section('🔗 인과관계 + 영향 추적 (1.9.141~143)');
11629
- cmd('leerness feature impact <F-XXXX>', '코드 변경 전 영향받는 feature 자동 회수');
11630
- cmd('leerness feature list --json', '전체 그래프 + 엣지');
11631
- cmd('leerness audit . --json', 'orphan/cycle 무결성 검증');
11632
-
11633
- section('🛡 보안·드리프트·게으름 가드');
11634
- cmd('leerness drift check . --auto-fix', 'drift + 보안 자동 회복');
11635
- cmd('leerness lazy detect . --json', '거짓 완료/no test run 감지');
11636
- cmd('leerness env sync .', '.env ↔ .env.example 동기화');
11637
- cmd('leerness health . --json', '종합 헬스 (drift+보안+skill+feature)');
11638
-
11639
- section(`🤖 외부 AI 통합 (MCP ${_mcpToolCount()} 도구)`); // 1.9.315 (UR-0054): 하드코딩 46 → 동적
11678
+ cprint(C.bold(C.cyan(t(' ✨ 시작하기 (3단계면 끝)', ' ✨ Get started (3 steps)'))));
11679
+ cmd('npx leerness init .', t('1️⃣ 하네스 설치 + AI 도구 자동 연결', '1️⃣ install the harness + auto-wire AI tools'));
11680
+ cmd('npx leerness handoff .', t('2️⃣ 세션 시작 — 컨텍스트·기억·feature impact 자동 회수', '2️⃣ start a session — context, memory, feature impact in one call'));
11681
+ cmd('npx leerness session close .', t('3️⃣ 세션 종료 — 마감 통계 + 다음 라운드 추천', '3️⃣ end a session — closing stats + next-round suggestion'));
11682
+
11683
+ section(t('🧠 메모리 5종 CRUD (1.9.142 — cascade 방지)', '🧠 Memory (5 surfaces)'));
11684
+ cmd('leerness task add "<title>"', t('progress-tracker 등록', 'add a task to progress-tracker'));
11685
+ cmd('leerness decision add "<title>" --reason "..."', t('되돌리기 어려운 결정 영구화', 'persist a hard-to-reverse decision'));
11686
+ cmd('leerness lesson save "<lesson>" --tag "..."', t('재발견 가능한 통찰 저장', 'save a rediscoverable insight'));
11687
+ cmd('leerness plan add "<milestone>"', t('계획 단계 등록', 'add a plan milestone'));
11688
+ cmd('leerness rule add "<rule>" --trigger every-X', t('자연어 영구 룰', 'natural-language standing rule'));
11689
+ cmd('leerness feature add "<feature>" --files "..."', t('Feature Graph 노드 (1.9.141)', 'Feature Graph node'));
11690
+
11691
+ section(t('🔗 인과관계 + 영향 추적 (1.9.141~143)', '🔗 Causality + impact tracking'));
11692
+ cmd('leerness feature impact <F-XXXX>', t('코드 변경 전 영향받는 feature 자동 회수', 'which features a code change affects'));
11693
+ cmd('leerness feature list --json', t('전체 그래프 + 엣지', 'full graph + edges'));
11694
+ cmd('leerness audit . --json', t('orphan/cycle 무결성 검증', 'orphan/cycle integrity check'));
11695
+
11696
+ section(t('🛡 보안·드리프트·게으름 가드', '🛡 Security · drift · laziness guards'));
11697
+ cmd('leerness drift check . --auto-fix', t('drift + 보안 자동 회복', 'drift + security auto-heal'));
11698
+ cmd('leerness lazy detect . --json', t('거짓 완료/no test run 감지', 'detect false-done / no-test-run'));
11699
+ cmd('leerness env sync .', t('.env ↔ .env.example 동기화', 'sync .env <-> .env.example'));
11700
+ cmd('leerness health . --json', t('종합 헬스 (drift+보안+skill+feature)', 'overall health (drift+security+skill+feature)'));
11701
+
11702
+ section(t(`🤖 외부 AI 통합 (MCP ${_mcpToolCount()} 도구)`, `🤖 External AI integration (MCP, ${_mcpToolCount()} tools)`)); // 1.9.315 (UR-0054): 하드코딩 46 → 동적
11640
11703
  cmd('npx leerness mcp serve', 'stdio JSON-RPC server');
11641
- cmd('leerness memory status . --json', '5 surface + featureGraph 한 호출');
11642
- cmd('leerness memory archive list --query "kw"', 'DELETE 5종 archive 검색');
11643
- cmd('leerness memory restore <surface> <target>', 'archive → active 복원');
11704
+ cmd('leerness memory status . --json', t('5 surface + featureGraph 한 호출', '5 surfaces + featureGraph in one call'));
11705
+ cmd('leerness memory archive list --query "kw"', t('DELETE 5종 archive 검색', 'search the archive of deleted items'));
11706
+ cmd('leerness memory restore <surface> <target>', t('archive → active 복원', 'restore archive -> active'));
11644
11707
 
11645
- section('🚀 Release 자동화');
11646
- cmd('leerness release pack --close --auto-main-push', '한 줄 release (1.9.140 main push 통합)');
11647
- cmd('leerness release sync-main .', 'release branch → main 자동 fast-forward');
11708
+ section(t('🚀 Release 자동화', '🚀 Release automation'));
11709
+ cmd('leerness release pack --close --auto-main-push', t('한 줄 release (1.9.140 main push 통합)', 'one-line release (with main push)'));
11710
+ cmd('leerness release sync-main .', t('release branch → main 자동 fast-forward', 'release branch -> main fast-forward'));
11648
11711
 
11649
11712
  cprint('');
11650
- cprint(C.dim(' 📚 자세히: `leerness --help` · 자율 모드: `<<autonomous-loop-dynamic>>` 신호로 진행'));
11713
+ cprint(C.dim(t(' 📚 자세히: `leerness --help` · 자율 모드: `<<autonomous-loop-dynamic>>` 신호로 진행', ' 📚 More: `leerness --help`')));
11651
11714
  cprint('');
11652
11715
  }
11653
11716
  }
@@ -20022,7 +20085,7 @@ module.exports = {
20022
20085
  // 1.9.289: shell-safe 인용 (Codex #3) — 단위 테스트
20023
20086
  _shellQuoteArg,
20024
20087
  // 1.18.1: 명령 실행 권한 결정 (재실증 신규 P1: --test-cmd 비-JS 인터프리터 거짓차단) — 단위 테스트
20025
- _isCommandPermitted, RUN_CORE_ALLOW,
20088
+ _isCommandPermitted, RUN_CORE_ALLOW, _uiLang, _tx,
20026
20089
  // 1.18.2: verify-claim 위장 스텁(빈 export 껍데기) 판정 — 단위 테스트
20027
20090
  _vcImplIsEmpty, _VC_EMPTY_SHELL_RE,
20028
20091
  // 1.18.3 (UR-0003): 분야별 자기질문 품질 렌즈 — 단위 테스트. 1.19.2: 파일→도메인 매핑(완료-검증 advisory)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",