leerness 1.27.0 → 1.29.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,93 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.29.0 — 2026-06-16 — 🛡️ [안정화/Stable] drift auto-fix·진단 명령 영어화 안정 minor
4
+
5
+ **🛡️ 안정화(Stable) minor — drift 완전 영어화(버그수정 포함) + 진단 3종 영어화를 npm 공개.** 직전 minor(1.28.0) 이후 누적된 패치 2건(1.28.1 + 1.28.2)을 검증·통합해 배포. R-0011 정책의 20번째 stable minor. 한국어 우선 기본은 그대로.
6
+
7
+ ### 이번 minor 통합 (1.28.1~1.28.2)
8
+ - **🐛+🌐 drift --auto-fix 영어화 + 보안신호 판정 버그수정 (1.28.1)**: 1.27.2 가 보안 신호 라벨을 언어화하며 내부 `hasSecurityFired` 가 한국어 라벨 정규식에 결합돼 `--language en` 에서 보안 auto-fix 가 미발동하던 잠복 버그를 언어-안정 필드(`f.file`)로 수정. drift `--auto-fix` 진행 로그 ~21줄 영어화 → `drift check` 완전 영어(출력 + auto-fix).
9
+ - **🌐 진단 모듈 영어화 (1.28.2)**: `doctor`(설치/환경)·`which`(버전 충돌·npx 캐시)·`whats-new`(CHANGELOG 차분) 3종을 영어 opt-in. 기존 위임 가드 보존.
10
+ - **한국어 우선 기본 보존**: 영어는 명시 opt-in. 한국어 출력/내부 호출 무영향(e2e 무회귀).
11
+
12
+ ### 잔여 (UR-0010 백로그)
13
+ - capabilities/commands/constraints/install-safety 영어화 · init en seed 템플릿 i18n.
14
+
15
+ ### 검증 (회귀 0)
16
+ - **selftest 250/250** · **E2E 368/368** (i18n 행위가드 lens/health/drift/doctor en/ko) · 게시본 클린룸 재실증.
17
+ - minor(1.29.0) — npm 배포(R-0011 stable) + annotated tag(Stable) + GitHub release(latest).
18
+
19
+ ## 1.28.2 — 2026-06-16 — CLI 영어화 Phase 10c: 진단 모듈(doctor/which/whats-new) 완전 영어화 (UR-0010)
20
+
21
+ **🌐 설치/버전 진단 3종을 영어로.** 문제 해결 시 자주 쓰는 진단 명령 `doctor`(설치/환경)·`which`(버전 충돌·npx 캐시)·`whats-new`(버전 변경 요약)를 한 모듈(lib/diagnostics.js)에서 완전 영어화.
22
+
23
+ ### 변경 (UR-0010 Phase 10c)
24
+ - **lib/diagnostics.js 3 명령 영어화 (DI uiLang)**: doctor(설치/환경 진단·설치 경로·MCP 도구·selftest 통과/실패·셸·정상/문제), which(현재 실행·버전·npm 환경·글로벌 설치·PATH 후보·진단·강제 최신 방법), whats-new(버전 파악 실패/CHANGELOG 없음 에러·신규 명령·플래그·파일·버전별 헤드라인·권장 행동). `t(ko,en)`, ko 인자 verbatim.
25
+ - **3 DI 호출에 uiLang 주입**: doctorCmd/whichCmd(`_uiLang(arg('--path', cwd))`) + whatsNewCmd(`_uiLang(root)`). 기존 1.9.392/1.9.394 위임 가드 문자열 보존(selftest 무회귀).
26
+ - **한국어 기본 유지**: 영어는 명시 opt-in. ko/내부 호출 무영향.
27
+
28
+ ### 잔여 (UR-0010 백로그)
29
+ - capabilities/commands/constraints/install-safety 영어화 · init en seed 템플릿 i18n.
30
+
31
+ ### 검증 (회귀 0)
32
+ - **selftest 249→250** (진단 영어/한국어 보존 + uiLang 주입 소스가드) · 행위(doctor/which/whats-new `--language en` 한글 0 [Node 탐지] / ko 보존) · **E2E 368/368** (i18n 행위가드에 doctor en/ko 추가).
33
+ - patch(1.28.2) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
34
+
35
+ ## 1.28.1 — 2026-06-16 — Phase 10b: drift --auto-fix 로그 영어화 + 보안신호 판정 버그 수정 (UR-0010)
36
+
37
+ **🐛+🌐 drift 를 완전 영어로 + 직전 라운드가 심은 버그 수정.** `drift check --auto-fix` 진행 로그를 영어화하면서, 1.27.2 가 보안 신호 라벨을 언어화하며 함께 심은 **내부 판정 버그**를 발견·수정.
38
+
39
+ ### 변경
40
+ - **🐛 hasSecurityFired 언어-결합 버그 수정**: 1.27.2 에서 보안 신호 `label` 을 `t(ko,en)` 로 언어화했는데, 내부 `hasSecurityFired` 가 한국어 라벨 정규식(`/보안 위험 \(1\.9\.78\)/`)으로 매칭하고 있어 **`drift check --auto-fix --language en` 에서 보안 auto-fix 가 발동하지 않던** 잠복 버그. 언어-안정 필드(`f.file === '.env / .gitignore'`)로 매칭 변경 → en/ko 모두 정상 발동. (번역된 라벨에 내부 로직이 결합되면 안 된다는 교훈 — [[lesson-adversarial-harden-heuristic]] 의 'valueGroup' 류와 동일.)
41
+ - **🌐 drift --auto-fix 로그 영어화 (~21줄)**: 보안/인코딩/delivered/idempotency/release-cleanup/session-close 자동회복 진행 로그를 `t(ko,en)`. 이로써 `drift check` 가 **완전히** 영어 지원(출력 + auto-fix).
42
+ - **한국어 기본 유지**: 영어는 명시 opt-in. ko 로그/내부 호출 무영향.
43
+
44
+ ### 검증 (회귀 0)
45
+ - **selftest 248→249** (afLog 영어/한국어 + 버그수정 소스가드) · 행위(보안 auto-fix en/ko 모두 발동 + en 로그 한글 0 [Node 탐지] + ko 보존) · **E2E 368/368**.
46
+ - patch(1.28.1) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
47
+
48
+ ## 1.28.0 — 2026-06-15 — 🛡️ [안정화/Stable] 정직성 후속 + drift 영어화 안정 minor
49
+
50
+ **🛡️ 안정화(Stable) minor — 13번째 외부리뷰 정직성 수정 + drift 진단 영어화를 npm 공개.** 직전 minor(1.27.0) 이후 누적된 패치 2건(1.27.1 + 1.27.2)을 검증·통합해 배포. R-0011 정책의 19번째 stable minor. 한국어 우선 기본은 그대로.
51
+
52
+ ### 이번 minor 통합 (1.27.1~1.27.2)
53
+ - **🔎 정직성 후속 수정 (1.27.1)**: `audit <미초기화경로>` 가 "미초기화" 선언 후 없는 하네스에 design/reuse 체크를 보고하던 모순 출력 차단(요약/JSON 직행). `verify-claim --run-tests` 가 비-테스트 `--test-cmd`(exit 0, 미파싱)를 `✓ all passed` 로 거짓표기하던 것을 `✓ ran (exit 0) — test count unconfirmed` 로 정직 표기(판정/exit 불변 → FP=0).
54
+ - **🌐 drift check 출력 영어화 (1.27.2)**: `drift check` 기본 출력(경로/상태/신호 표/보안 신호/권장 조치)을 영어 opt-in. `--auto-fix` 진행 로그는 Phase 10b 백로그. 내부 호출(handoff/health)은 ko 기본이라 무영향.
55
+ - **한국어 우선 기본 보존**: 영어는 명시 opt-in. 한국어 출력/내부 JSON 은 그대로(e2e 무회귀).
56
+
57
+ ### 잔여 (백로그)
58
+ - drift `--auto-fix` 로그(Phase 10b) · capabilities/commands/doctor/install-safety/constraints 영어화 · init en seed 템플릿 i18n.
59
+
60
+ ### 검증 (회귀 0)
61
+ - **selftest 248/248** · **E2E 368/368** (정직성 후속 가드 + i18n 행위가드 lens/health/drift en/ko) · 게시본 클린룸 재실증.
62
+ - minor(1.28.0) — npm 배포(R-0011 stable) + annotated tag(Stable) + GitHub release(latest).
63
+
64
+ ## 1.27.2 — 2026-06-15 — CLI 영어화 Phase 10: drift check 출력 영어화 (UR-0010)
65
+
66
+ **🌐 drift 진단 출력을 영어로.** 고빈도 진단 `drift check` 의 **기본 출력**(경로/상태/신호 표/보안 신호/권장 조치)을 영어 opt-in 으로. `--auto-fix` 진행 로그(~25줄)는 정직하게 Phase 10b 로 분리(반쪽 주장 회피).
67
+
68
+ ### 변경 (UR-0010 Phase 10)
69
+ - **drift check 출력 영어화 (lib/drift.js, DI uiLang)**: 경로 없음 에러, 표 헤더(`신호/임계/가중치/발화`→`signal/threshold/weight/fired`), 신호 라벨 8종(`session close 누락`→`session close missing`, `current-state 갱신 없음`, `task update 없음`, `progress-tracker 비어있음`, `task-log 갱신 없음`, `보안 위험`→`security risk`, `Feature Graph 미정리`→`unlinked`, `task 0건 sub-app`), 보안 issue 2종, `권장 조치` 블록. `t(ko,en)`, ko 인자 verbatim.
70
+ - **내부 호출 무영향**: handoff/health 가 drift 를 내부 spawn(LEERNESS_INTERNAL, `--language` 없음)할 땐 ko 기본 라벨 — 한국어 출력/JSON 파싱 그대로(무회귀). 라벨은 `--json` 값도 언어 따름(en 시 영어).
71
+
72
+ ### 잔여 (UR-0010 Phase 10b+, 백로그)
73
+ - drift `--auto-fix` 진행 로그(~25줄) + capabilities/commands/doctor/install-safety/constraints + init en seed 템플릿 i18n.
74
+
75
+ ### 검증 (회귀 0)
76
+ - **selftest 247→248** (drift 영어/한국어 보존 + uiLang 주입 소스가드) · 행위(drift `--language en` 한글 0 / ko 보존 / 내부호출 ko 유지) · **E2E 368/368** (i18n 행위가드에 drift en/ko 추가).
77
+ - patch(1.27.2) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
78
+
79
+ ## 1.27.1 — 2026-06-15 — 13번째 외부리뷰 정직성 후속: audit 미초기화 모순출력 + verify-claim no-parse 표기
80
+
81
+ **🔎 13번째 외부리뷰의 정직성 잔여 P2/P3 2건 수정(맹신 X 양방향 재현).** 둘 다 leerness 핵심 정체성(정직한 보고)을 직접 건드리는 출력 문제.
82
+
83
+ ### 변경
84
+ - **audit 미초기화 경로 모순 출력 차단 (#2)**: `audit <미초기화경로>` 가 "미초기화" 를 선언한 직후 design/reuse 체크를 **없는 하네스에 대해** 보고하던 모순(예: "✓ no duplicate design guide candidates")을 차단 — 미초기화 감지 시 요약/JSON 으로 직행 후 종료. exit code(1)·`--json` 페이로드(not_initialized finding)는 종전과 동일, **정상 프로젝트 audit 은 무영향**(모든 체크 계속).
85
+ - **verify-claim --run-tests no-parse 정직 표기 (#3)**: 비-테스트 `--test-cmd`(예: `echo hi`)가 exit 0 이면서 테스트 비율을 못 파싱한 경우 `✓ all passed` 로 거짓표기하던 것을 `✓ ran (exit 0) — test count unconfirmed`(실행됨, 테스트 수 미확인)로 정직 표기. **메시지만 변경, 판정/exit 불변** → 출력 포맷이 다른 정상 테스트러너의 통과 주장을 거부하지 않음(FP=0). 진짜 N/N 테스트는 계속 `✓ all passed`, 실패(exit≠0)는 계속 `✗ FAIL`.
86
+
87
+ ### 검증 (회귀 0)
88
+ - **selftest 246→247** (소스가드; 기존 1.9.421 "audit body=lib" 가드와 자기참조 충돌을 코멘트 앵커로 회피 — [[lesson-selftest-self-reference-trap]] 적용) · 행위(맹신 X 양방향) · **E2E 367→368** (정직성 후속 회귀가드 1건).
89
+ - patch(1.27.1) — npm 미배포(R-0011, GitHub/CHANGELOG 누적). 잔여(init en seed=대형 템플릿 i18n, Phase 10 진단 영어화)는 백로그.
90
+
3
91
  ## 1.27.0 — 2026-06-15 — 🛡️ [안정화/Stable] 보안 수정 안정 minor (개인키 스캔 FN + placeholder FP)
4
92
 
5
93
  **🛡️ 안정화(Stable) minor — 13번째 외부리뷰에서 확인된 보안 수정을 조기 npm 공개.** 직전 minor(1.26.0) 이후 1.26.1 패치 1건이지만, **보안 FN/FP(거짓 "보안 OK" + CI 파손)는 패치 누적을 기다리기보다 조기 공개가 합리적**이라 단독 minor 로 게시. R-0011 정책의 18번째 stable minor.
package/README.md CHANGED
@@ -104,7 +104,7 @@ MIT
104
104
  <!-- leerness:project-readme:start -->
105
105
  ## Leerness Project Harness
106
106
 
107
- 이 프로젝트는 Leerness v1.27.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
107
+ 이 프로젝트는 Leerness v1.29.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
108
108
 
109
109
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
110
110
 
@@ -158,7 +158,7 @@ leerness memory restore decision <date|title>
158
158
 
159
159
  ### MCP server (외부 AI 통합)
160
160
 
161
- Leerness v1.27.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
161
+ Leerness v1.29.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
162
162
 
163
163
  ```jsonc
164
164
  // 카테고리별
@@ -179,7 +179,7 @@ Leerness v1.27.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
179
179
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
180
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) 다음 라운드 예약.
181
181
 
182
- 현재 누적: **70 라운드 (1.9.40 → 1.27.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
182
+ 현재 누적: **70 라운드 (1.9.40 → 1.29.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
183
183
 
184
184
  ### 성능 가이드 (1.9.140 측정)
185
185
 
@@ -217,6 +217,6 @@ leerness release pack --close --auto-main-push
217
217
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
218
218
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
219
219
 
220
- Last synced by Leerness v1.27.0: 2026-06-15
220
+ Last synced by Leerness v1.29.0: 2026-06-16
221
221
  <!-- leerness:project-readme:end -->
222
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.27.0';
35
+ const VERSION = '1.29.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') 시 호스트 프로세스 오염.
@@ -3820,6 +3820,37 @@ function _selfTestCases() {
3820
3820
  const retroGuard = bin.includes("failJson(has('--json'), 'invalid_arg'") && bin.includes('Math.min(days, 36500)');
3821
3821
  return keyFile && dbVg && phPlaceholder && retroGuard;
3822
3822
  } },
3823
+ { name: '13번째 외부리뷰 정직성 후속 (1.27.1): audit 미초기화 early-return + verify-claim no-parse 표기 (소스 가드)', run: () => {
3824
+ const bin = read(__filename);
3825
+ const aud = read(path.join(path.dirname(__filename), '..', 'lib', 'audit.js'));
3826
+ // 주의: bin 에 'not_'+'initialized' 리터럴을 두면 1.9.421('body 가 lib 로 이동') 가드가 깨짐 → 코멘트 텍스트로 앵커.
3827
+ const auditReturn = aud.includes('// 1.27.1 (13번째 외부리뷰 #2)') && /외부리뷰 #2\)[\s\S]{0,1200}?process\.exitCode = 1;\s*\n\s*return;/.test(aud);
3828
+ const vcMsg = bin.includes("test count unconfirmed") && bin.includes('runResult.parsed ? ');
3829
+ return auditReturn && vcMsg;
3830
+ } },
3831
+ { name: 'CLI 영어화 Phase 10 (1.27.2, UR-0010): drift check 출력 영어/한국어 보존 + uiLang 주입 (소스 가드)', run: () => {
3832
+ const bin = read(__filename);
3833
+ const dr = read(path.join(path.dirname(__filename), '..', 'lib', 'drift.js'));
3834
+ const injected = bin.includes('uiLang: _uiLang(root), harnessPath: __filename, readProgressRows, planPath, handoffPath, currentStatePath, taskLogPath');
3835
+ const en = dr.includes('| signal | age | threshold | weight | fired |') && dr.includes('session close missing') && dr.includes('recommended actions') && dr.includes('security risk:');
3836
+ const koPreserved = dr.includes('| 신호 | age | 임계 | 가중치 | 발화 |') && dr.includes('session close 누락') && dr.includes('권장 조치'); // ko 인자 보존(e2e ko/내부호출)
3837
+ return injected && en && koPreserved;
3838
+ } },
3839
+ { name: 'Phase 10b (1.28.1): drift --auto-fix 로그 영어화 + hasSecurityFired 언어안정 매칭 (소스 가드)', run: () => {
3840
+ const dr = read(path.join(path.dirname(__filename), '..', 'lib', 'drift.js'));
3841
+ const bugFix = dr.includes("f.file === '.env / .gitignore'"); // 보안 신호 판정을 언어-안정 file 필드로(라벨 regex 결합 제거)
3842
+ const afLogEn = dr.includes('recovering security signal: running audit --fix') && dr.includes('re-checking...') && dr.includes('auto-fix error:');
3843
+ const afLogKo = dr.includes('보안 신호 회복: audit --fix 자동 실행 중') && dr.includes('재검사 중...'); // ko 인자 보존
3844
+ return bugFix && afLogEn && afLogKo;
3845
+ } },
3846
+ { name: 'Phase 10c (1.28.2): 진단 모듈(doctor/which/whats-new) 영어/한국어 보존 + uiLang 주입 (소스 가드)', run: () => {
3847
+ const bin = read(__filename);
3848
+ const dg = read(path.join(path.dirname(__filename), '..', 'lib', 'diagnostics.js'));
3849
+ const injected = bin.includes("uiLang: _uiLang(arg('--path', process.cwd())), _selfTestCases") && bin.includes('_diag.whatsNewCmd(root, { VERSION, uiLang: _uiLang(root)');
3850
+ const en = dg.includes('install/environment diagnosis') && dg.includes('## npm environment') && dg.includes('per-version headlines') && dg.includes('how to force the latest');
3851
+ const koPreserved = dg.includes('설치/환경 진단') && dg.includes('## npm 환경') && dg.includes('버전별 헤드라인'); // ko 인자 보존(내부호출/e2e)
3852
+ return injected && en && koPreserved;
3853
+ } },
3823
3854
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
3824
3855
  ];
3825
3856
  }
@@ -10383,7 +10414,8 @@ function verifyClaimCmd(root, taskId) {
10383
10414
  // 1.17.4 (UR-0047): 측정 불가는 '통과' 가 아니라 '검증 미수행' — 이전엔 실측 0 인데 ✓ pass(실측≥주장) 모순 표기.
10384
10415
  log(` - ${t('테스트 카운트', 'test count')}: ${declaredTestCount == null ? t('⊘ (주장 없음)', '⊘ (none claimed)') : !testMeasured ? t(`⊘ 측정 불가 — 주장 ${declaredTestCount}개 검증 미수행 (pass 아님)`, `⊘ not measurable — claimed ${declaredTestCount} not verified (not a pass)`) : testOk ? t('✓ pass (실측 ≥ 주장)', '✓ pass (measured ≥ claimed)') : t('⚠ 주장보다 적음', '⚠ fewer than claimed')}`);
10385
10416
  if (runResult && !runResult.skipped) {
10386
- log(` - ${runResult.cmd || 'npm test'} ${t('실행', 'run')}: ${runTestsOk ? '✓ all passed' : ' FAIL'}`);
10417
+ // 1.27.1 (13번째 외부리뷰 #3): exit 0 인데 테스트 비율을 못 파싱한 경우(예: 비-테스트 --test-cmd) '✓ all passed' 거짓표기하지 않음 — '실행됨, 테스트 수 미확인' 으로 정직 표기(판정/exit 불변 → 이색 테스트러너 FP 없음).
10418
+ log(` - ${runResult.cmd || 'npm test'} ${t('실행', 'run')}: ${runTestsOk ? (runResult.parsed ? '✓ all passed' : t('✓ 실행됨 (exit 0) — 테스트 수 미확인', '✓ ran (exit 0) — test count unconfirmed')) : '✗ FAIL'}`);
10387
10419
  if (declaredPass) log(` - ${t('주장과 실행 결과 일치', 'claimed matches run')}: ${declaredPassMatchesActual ? '✓ pass' : t('⚠ 다름', '⚠ differs')}`);
10388
10420
  }
10389
10421
  // 1.11.2 (UR-0175): optimism+정직성 — done 주장은 기본 게이팅(claimsChecked). 완화: --lenient.
@@ -14913,7 +14945,7 @@ function autoUpdateInstall(root) {
14913
14945
  // 1.9.37: drift detection — 메타파일 staleness 측정으로 "leerness 점점 안 쓰는" 현상 감지
14914
14946
  const _drift = require('../lib/drift');
14915
14947
  // 1.9.422 (UR-0025/UR-0125 큰 핸들러 모듈화 7번째): driftCheckCmd → lib/drift.js (DI 위임, thin wrapper)
14916
- function driftCheckCmd(root, opts = {}) { return _drift.driftCheckCmd(root, opts, { VERSION, has, arg, harnessPath: __filename, readProgressRows, planPath, handoffPath, currentStatePath, taskLogPath, envDiff, _usageStatsPath, _readUsageStats, _updateUserRequest, _scanShellScriptsEncoding, _readFeatureGraph, _detectDeliveredRequests, _autoFixIdempotency }); }
14948
+ function driftCheckCmd(root, opts = {}) { return _drift.driftCheckCmd(root, opts, { VERSION, has, arg, uiLang: _uiLang(root), harnessPath: __filename, readProgressRows, planPath, handoffPath, currentStatePath, taskLogPath, envDiff, _usageStatsPath, _readUsageStats, _updateUserRequest, _scanShellScriptsEncoding, _readFeatureGraph, _detectDeliveredRequests, _autoFixIdempotency }); }
14917
14949
 
14918
14950
  // 1.9.69: skill-suggestions.md rolling history 인덱스 — mtime 기반 캐시
14919
14951
  // handoff에서 같은 키워드 과거 추천 결과를 즉시 노출 (재매칭 불필요)
@@ -16098,7 +16130,7 @@ function mcpServeCmd(root) {
16098
16130
  }
16099
16131
 
16100
16132
  // 1.9.394 (UR-0025): whatsNewCmd 를 lib/diagnostics.js 로 분리 (whats-new 서브시스템 완결 — 파서는 1.9.393 에 pure-utils 로). deps 위임.
16101
- function whatsNewCmd(root) { return _diag.whatsNewCmd(root, { VERSION, arg, has }); }
16133
+ function whatsNewCmd(root) { return _diag.whatsNewCmd(root, { VERSION, uiLang: _uiLang(root), arg, has }); }
16102
16134
 
16103
16135
  // 1.9.71: .env / .env.example 자동 동기화 — 누락 키 감지 + (옵션) 자동 추가
16104
16136
  // 보안 정책: .env의 실제 값은 절대 옮기지 않음. .env.example엔 키만 (빈 값).
@@ -19717,8 +19749,8 @@ function reviewRequestCmd(root, request) { return _reviewRequest.reviewRequestCm
19717
19749
  // 1.9.392 (UR-0025 큰 핸들러 모듈화 4번째): doctor/which 진단 핸들러를 lib/diagnostics.js 로 분리.
19718
19750
  // harness 는 deps(VERSION · _selfTestCases · _detectShellCtx · _mcpToolCount · has · harnessPath)를 구성해 위임(thin wrapper). 호출부/동작 무변경.
19719
19751
  const _diag = require('../lib/diagnostics');
19720
- function doctorCmd(opts = {}) { return _diag.doctorCmd(opts, { VERSION, _selfTestCases, _detectShellCtx, _mcpToolCount, has, harnessPath: __filename }); }
19721
- function whichCmd() { return _diag.whichCmd({ VERSION, has, harnessPath: __filename }); }
19752
+ function doctorCmd(opts = {}) { return _diag.doctorCmd(opts, { VERSION, uiLang: _uiLang(arg('--path', process.cwd())), _selfTestCases, _detectShellCtx, _mcpToolCount, has, harnessPath: __filename }); }
19753
+ function whichCmd() { return _diag.whichCmd({ VERSION, uiLang: _uiLang(arg('--path', process.cwd())), has, harnessPath: __filename }); }
19722
19754
 
19723
19755
  // 1.23.1 (UR-0010 Phase 6): 영어 큐레이트 도움말 — 한국어 help 의 줄별 번역이 아니라, 카테고리별로 정리한 별도 영어판.
19724
19756
  // 레거시 버전태그(1.9.x) 군더더기를 빼고 영어 사용자가 읽기 쉽게. 전체 전수 목록은 `leerness commands`.
package/lib/audit.js CHANGED
@@ -26,6 +26,11 @@ function audit(root, opts = {}, deps = {}) {
26
26
  failures++;
27
27
  fail(`미초기화 또는 존재하지 않는 경로: ${root} (.harness/AGENTS.md 없음 — leerness init 필요)`);
28
28
  _finding('not_initialized', 'fail', 'uninitialized or missing path (.harness or AGENTS.md absent)', { root });
29
+ // 1.27.1 (13번째 외부리뷰 #2): 미초기화 시 후속 체크(design/reuse 등)를 없는 하네스에 대해 보고하던 모순 출력 차단 — 요약/JSON 으로 직행 후 종료(exit code/JSON 페이로드는 종전과 동일).
30
+ log(`Audit summary: warnings=${warnings} failures=${failures}`);
31
+ if (jsonMode) { process.stdout.write = _origWrite; process.stdout.write(JSON.stringify({ version: VERSION, root, warnings, failures, fixed, healthy: false, fixApplied: fix, strict: has('--strict'), strictThreshold: has('--strict') ? parseInt(arg('--threshold', '1'), 10) : null, summary: `warnings=${warnings} failures=${failures}`, findings }, null, 2) + '\n'); }
32
+ process.exitCode = 1;
33
+ return;
29
34
  }
30
35
  const designCands = ['designguide.md','design-guide.md','docs/designguide.md','docs/design-guide.md','.harness/designguide.md'];
31
36
  const dups = designCands.filter(f => exists(path.join(root,f)));
@@ -10,7 +10,8 @@ const { log, fail, absRoot, exists, read } = require('./io');
10
10
  const { parseHarnessVersion, _parseChangelogBetween } = require('./pure-utils');
11
11
 
12
12
  function doctorCmd(opts = {}, deps = {}) {
13
- const { VERSION, _selfTestCases, _detectShellCtx, _mcpToolCount, has, harnessPath } = deps;
13
+ const { VERSION, uiLang, _selfTestCases, _detectShellCtx, _mcpToolCount, has, harnessPath } = deps;
14
+ const t = (ko, en) => (uiLang === 'en' ? en : ko); // 1.28.2 (UR-0010 Phase 10c)
14
15
  const json = opts.json || has('--json');
15
16
  // 1) 코어 무결성: selftest 케이스 인라인 실행
16
17
  let pass = 0; const failNames = [];
@@ -35,23 +36,24 @@ function doctorCmd(opts = {}, deps = {}) {
35
36
  if (json) { process.stdout.write(JSON.stringify(report, null, 2) + '\n'); if (!report.healthy) process.exitCode = 1; return report; }
36
37
  const isTty = process.stdout && process.stdout.isTTY;
37
38
  const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s, rd = s => isTty ? `\x1b[31m${s}\x1b[0m` : s, cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s, dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
38
- log(cy(`# leerness doctor — 설치/환경 진단`));
39
+ log(cy(t(`# leerness doctor — 설치/환경 진단`, `# leerness doctor — install/environment diagnosis`)));
39
40
  log('');
40
41
  log(` ${gr('✓')} version ${VERSION} · node ${process.version} · ${process.platform}/${process.arch}`);
41
- log(` ${gr('✓')} 설치 경로: ${dm(harnessPath)}`);
42
- log(` ${gr('✓')} MCP 도구: ${mcpCount}`);
42
+ log(` ${gr('✓')} ${t('설치 경로', 'install path')}: ${dm(harnessPath)}`);
43
+ log(` ${gr('✓')} ${t('MCP 도구', 'MCP tools')}: ${mcpCount}`);
43
44
  // 1.13.1 (15th 블라인드 리뷰 P3, Sonnet): pass 수에 '실패' 가 붙어 "209/210 실패"(=209건 실패로 오독)되던 문구 → "통과 N/M (K건 실패)" 로 명확화.
44
- log(` ${report.selftest.ok ? gr('✓') : rd('✗')} selftest: ${pass}/${total} 통과${report.selftest.ok ? '' : ` (${total - pass}건 실패)`}`);
45
+ log(` ${report.selftest.ok ? gr('✓') : rd('✗')} selftest: ${pass}/${total} ${t('통과', 'passed')}${report.selftest.ok ? '' : t(` (${total - pass}건 실패)`, ` (${total - pass} failed)`)}`);
45
46
  if (!report.selftest.ok) report.selftest.failed.slice(0, 5).forEach(n => log(rd(` ✗ ${n}`)));
46
- log(` ${gr('✓')} 셸: ${shell || 'unknown'}${psVersion && shell === 'powershell' ? ` (PowerShell ${psVersion})` : ''}`);
47
+ log(` ${gr('✓')} ${t('셸', 'shell')}: ${shell || 'unknown'}${psVersion && shell === 'powershell' ? ` (PowerShell ${psVersion})` : ''}`);
47
48
  log('');
48
- log(report.healthy ? gr(' ✓ leerness 설치 정상') : rd(' ✗ 문제 감지 — 재설치: npm i -g leerness@latest · 진단: leerness which'));
49
+ log(report.healthy ? gr(t(' ✓ leerness 설치 정상', ' ✓ leerness install OK')) : rd(t(' ✗ 문제 감지 — 재설치: npm i -g leerness@latest · 진단: leerness which', ' ✗ problem detected — reinstall: npm i -g leerness@latest · diagnose: leerness which')));
49
50
  if (!report.healthy) process.exitCode = 1;
50
51
  return report;
51
52
  }
52
53
 
53
54
  function whichCmd(deps = {}) {
54
- const { VERSION, has, harnessPath } = deps;
55
+ const { VERSION, uiLang, has, harnessPath } = deps;
56
+ const t = (ko, en) => (uiLang === 'en' ? en : ko); // 1.28.2 (UR-0010 Phase 10c)
55
57
  const out = {
56
58
  version: VERSION,
57
59
  runningFrom: harnessPath,
@@ -91,48 +93,49 @@ function whichCmd(deps = {}) {
91
93
  // 진단: 글로벌 설치된 leerness 와 현재 실행 버전이 다르면 경고
92
94
  out.diagnostics = [];
93
95
  if (out.npm.globalInstalled && out.npm.globalInstalled !== VERSION) {
94
- out.diagnostics.push(`⚠ 글로벌 설치 ${out.npm.globalInstalled} ≠ 현재 실행 ${VERSION} — npx 캐시 또는 PATH 충돌 의심`);
95
- out.diagnostics.push(` → 강제 최신: npm i -g leerness@latest / 또는 npx --yes leerness@latest <command>`);
96
+ out.diagnostics.push(t(`⚠ 글로벌 설치 ${out.npm.globalInstalled} ≠ 현재 실행 ${VERSION} — npx 캐시 또는 PATH 충돌 의심`, `⚠ global install ${out.npm.globalInstalled} ≠ running ${VERSION} — suspect npx cache or PATH conflict`));
97
+ out.diagnostics.push(t(` → 강제 최신: npm i -g leerness@latest / 또는 npx --yes leerness@latest <command>`, ` → force latest: npm i -g leerness@latest / or npx --yes leerness@latest <command>`));
96
98
  }
97
99
  if (out.pathCandidates.length > 1) {
98
- out.diagnostics.push(`⚠ PATH 에 leerness 가 ${out.pathCandidates.length}개 — 우선순위 충돌 가능`);
99
- out.diagnostics.push(` → 명시적 경로 사용: ${out.runningFrom}`);
100
+ out.diagnostics.push(t(`⚠ PATH 에 leerness 가 ${out.pathCandidates.length}개 — 우선순위 충돌 가능`, `⚠ leerness appears ${out.pathCandidates.length}× on PATH — possible precedence conflict`));
101
+ out.diagnostics.push(t(` → 명시적 경로 사용: ${out.runningFrom}`, ` → use the explicit path: ${out.runningFrom}`));
100
102
  }
101
103
  if (has('--json')) { log(JSON.stringify(out, null, 2)); return; }
102
104
  log(`# leerness which (1.9.164)`);
103
- log(`현재 실행: ${out.runningFrom}`);
104
- log(`버전: v${out.version}`);
105
+ log(`${t('현재 실행', 'running from')}: ${out.runningFrom}`);
106
+ log(`${t('버전', 'version')}: v${out.version}`);
105
107
  log(`Node: ${out.nodeVersion} (${out.platform}/${out.arch})`);
106
108
  log('');
107
- log(`## npm 환경`);
109
+ log(t(`## npm 환경`, `## npm environment`));
108
110
  if (out.npm.globalRoot) log(` npm root -g: ${out.npm.globalRoot}`);
109
- if (out.npm.cacheDir) log(` npm cache: ${out.npm.cacheDir} (npx 옛 버전이 여기 캐싱 — 의심 시 \`npm cache clean --force\`)`);
110
- if (out.npm.globalInstalled) log(` 글로벌 설치: leerness@${out.npm.globalInstalled}`);
111
- else log(` 글로벌 설치: (없음 — npx 또는 로컬 경로만 사용 중)`);
111
+ if (out.npm.cacheDir) log(t(` npm cache: ${out.npm.cacheDir} (npx 옛 버전이 여기 캐싱 — 의심 시 \`npm cache clean --force\`)`, ` npm cache: ${out.npm.cacheDir} (npx caches old versions here — if suspect: \`npm cache clean --force\`)`));
112
+ if (out.npm.globalInstalled) log(t(` 글로벌 설치: leerness@${out.npm.globalInstalled}`, ` global install: leerness@${out.npm.globalInstalled}`));
113
+ else log(t(` 글로벌 설치: (없음 — npx 또는 로컬 경로만 사용 중)`, ` global install: (none — using npx or a local path only)`));
112
114
  if (out.pathCandidates.length) {
113
115
  log('');
114
- log(`## PATH 후보 (${out.pathCandidates.length}개)`);
116
+ log(t(`## PATH 후보 (${out.pathCandidates.length}개)`, `## PATH candidates (${out.pathCandidates.length})`));
115
117
  for (const p of out.pathCandidates) log(` - ${p}`);
116
118
  }
117
119
  if (out.diagnostics.length) {
118
120
  log('');
119
- log(`## ⚠ 진단`);
121
+ log(t(`## ⚠ 진단`, `## ⚠ diagnostics`));
120
122
  for (const d of out.diagnostics) log(` ${d}`);
121
123
  } else {
122
124
  log('');
123
- log(`✓ 충돌 없음 (현재 실행 버전 = 글로벌 설치 버전)`);
125
+ log(t(`✓ 충돌 없음 (현재 실행 버전 = 글로벌 설치 버전)`, `✓ no conflict (running version = global install version)`));
124
126
  }
125
127
  log('');
126
- log(`💡 강제 최신 실행 방법:`);
127
- log(` 1) npx --yes leerness@latest <command> # npx 캐시 무시하고 최신 다운로드`);
128
- log(` 2) npm i -g leerness@latest # 글로벌 설치 갱신`);
129
- log(` 3) npm cache clean --force # npx 캐시 강제 비우기 (의심 시)`);
128
+ log(t(`💡 강제 최신 실행 방법:`, `💡 how to force the latest:`));
129
+ log(t(` 1) npx --yes leerness@latest <command> # npx 캐시 무시하고 최신 다운로드`, ` 1) npx --yes leerness@latest <command> # bypass npx cache, download latest`));
130
+ log(t(` 2) npm i -g leerness@latest # 글로벌 설치 갱신`, ` 2) npm i -g leerness@latest # update the global install`));
131
+ log(t(` 3) npm cache clean --force # npx 캐시 강제 비우기 (의심 시)`, ` 3) npm cache clean --force # force-clear the npx cache (if suspect)`));
130
132
  }
131
133
 
132
134
  // 1.9.394 (UR-0025): whats-new — 현재 워크스페이스 버전 → 도구 버전 CHANGELOG 차분(신규 명령/플래그/파일 요약). introspection 핸들러.
133
135
  // 순수 파서 _parseChangelogBetween(pure-utils) 사용. deps: VERSION/arg/has. CHANGELOG 경로는 root 우선, 없으면 pkg 자체(lib/../).
134
136
  function whatsNewCmd(root, deps = {}) {
135
- const { VERSION, arg, has } = deps;
137
+ const { VERSION, uiLang, arg, has } = deps;
138
+ const t = (ko, en) => (uiLang === 'en' ? en : ko); // 1.28.2 (UR-0010 Phase 10c)
136
139
  root = absRoot(root || process.cwd());
137
140
  const fromV = arg('--from', null) || (function () {
138
141
  const hv = path.join(root, '.harness', 'HARNESS_VERSION');
@@ -141,29 +144,29 @@ function whatsNewCmd(root, deps = {}) {
141
144
  })();
142
145
  const toV = arg('--to', null) || VERSION;
143
146
  if (!fromV) {
144
- fail('현재 버전을 파악할 수 없습니다. --from <version> 명시');
147
+ fail(t('현재 버전을 파악할 수 없습니다. --from <version> 명시', 'cannot determine current version. specify --from <version>'));
145
148
  return process.exit(1);
146
149
  }
147
150
  // CHANGELOG.md — 우선 root, 없으면 leerness-pkg 자체 (lib/../CHANGELOG.md = pkg 루트)
148
151
  let changelogPath = path.join(root, 'CHANGELOG.md');
149
152
  if (!exists(changelogPath)) changelogPath = path.join(__dirname, '..', 'CHANGELOG.md');
150
153
  if (!exists(changelogPath)) {
151
- fail('CHANGELOG.md 없음');
154
+ fail(t('CHANGELOG.md 없음', 'CHANGELOG.md not found'));
152
155
  return process.exit(1);
153
156
  }
154
157
  const diff = _parseChangelogBetween(read(changelogPath), fromV, toV);
155
158
  if (has('--json')) { log(JSON.stringify({ from: fromV, to: toV, versions: diff }, null, 2)); return; }
156
159
  if (!diff.length) {
157
160
  log(`# leerness whats-new (1.9.41)`);
158
- log(`현재 ${fromV} ↔ 대상 ${toV}: 새 항목 없음 (또는 CHANGELOG에 기록 안 됨)`);
161
+ log(t(`현재 ${fromV} ↔ 대상 ${toV}: 새 항목 없음 (또는 CHANGELOG에 기록 안 됨)`, `current ${fromV} ↔ target ${toV}: no new entries (or not recorded in CHANGELOG)`));
159
162
  return;
160
163
  }
161
164
  log(`# leerness whats-new (1.9.41)`);
162
- log(`현재 워크스페이스 버전: ${fromV} → 대상: ${toV}`);
163
- log(`범위: ${diff.length}개 버전 (${diff[0].version} → ${diff[diff.length - 1].version})`);
165
+ log(t(`현재 워크스페이스 버전: ${fromV} → 대상: ${toV}`, `current workspace version: ${fromV} → target: ${toV}`));
166
+ log(t(`범위: ${diff.length}개 버전 (${diff[0].version} → ${diff[diff.length - 1].version})`, `range: ${diff.length} version(s) (${diff[0].version} → ${diff[diff.length - 1].version})`));
164
167
  log('');
165
168
  // AI 가독 요약 — 각 버전당 한 줄 + 신규 명령/플래그/파일
166
- log(`## 🆕 신규 명령·플래그·파일 (AI 에이전트는 다음 명령을 우선 시도)`);
169
+ log(t(`## 🆕 신규 명령·플래그·파일 (AI 에이전트는 다음 명령을 우선 시도)`, `## 🆕 new commands·flags·files (AI agents: try these first)`));
167
170
  const allCommands = new Set();
168
171
  const allFlags = new Set();
169
172
  const allFiles = new Set();
@@ -172,11 +175,11 @@ function whatsNewCmd(root, deps = {}) {
172
175
  v.newFlags.forEach(f => allFlags.add(f));
173
176
  v.newFiles.forEach(f => allFiles.add(f));
174
177
  }
175
- if (allCommands.size) log(` 📌 신규 명령: ${[...allCommands].join(', ')}`);
176
- if (allFlags.size) log(` 🚩 신규 플래그: ${[...allFlags].join(', ')}`);
177
- if (allFiles.size) log(` 📄 신규 파일: ${[...allFiles].join(', ')}`);
178
+ if (allCommands.size) log(t(` 📌 신규 명령: ${[...allCommands].join(', ')}`, ` 📌 new commands: ${[...allCommands].join(', ')}`));
179
+ if (allFlags.size) log(t(` 🚩 신규 플래그: ${[...allFlags].join(', ')}`, ` 🚩 new flags: ${[...allFlags].join(', ')}`));
180
+ if (allFiles.size) log(t(` 📄 신규 파일: ${[...allFiles].join(', ')}`, ` 📄 new files: ${[...allFiles].join(', ')}`));
178
181
  log('');
179
- log(`## 📜 버전별 헤드라인`);
182
+ log(t(`## 📜 버전별 헤드라인`, `## 📜 per-version headlines`));
180
183
  for (const v of diff) {
181
184
  // body 첫 줄(또는 strong header) 추출
182
185
  const firstLine = (v.body.match(/^\*\*([^*]+)\*\*/) || [])[1]
@@ -184,11 +187,11 @@ function whatsNewCmd(root, deps = {}) {
184
187
  log(` • ${v.version}${v.date ? ` (${v.date})` : ''} — ${firstLine || '(no headline)'}`);
185
188
  }
186
189
  log('');
187
- log(`## 💡 권장 행동`);
188
- log(` 1. 위 신규 명령들을 시도해 보세요 (예: \`leerness <명령> --help\`)`);
189
- log(` 2. 신규 파일들을 읽어 보세요 (예: \`cat .harness/session-workflow.md\`)`);
190
- log(` 3. AGENTS.md/CLAUDE.md 재독 — migrate가 인스트럭션을 업데이트했을 수 있음`);
191
- log(` 4. 상세: \`cat CHANGELOG.md\` 또는 \`leerness whats-new --json\``);
190
+ log(t(`## 💡 권장 행동`, `## 💡 recommended actions`));
191
+ log(t(` 1. 위 신규 명령들을 시도해 보세요 (예: \`leerness <명령> --help\`)`, ` 1. Try the new commands above (e.g. \`leerness <command> --help\`)`));
192
+ log(t(` 2. 신규 파일들을 읽어 보세요 (예: \`cat .harness/session-workflow.md\`)`, ` 2. Read the new files (e.g. \`cat .harness/session-workflow.md\`)`));
193
+ log(t(` 3. AGENTS.md/CLAUDE.md 재독 — migrate가 인스트럭션을 업데이트했을 수 있음`, ` 3. Re-read AGENTS.md/CLAUDE.md — migrate may have updated the instructions`));
194
+ log(t(` 4. 상세: \`cat CHANGELOG.md\` 또는 \`leerness whats-new --json\``, ` 4. Details: \`cat CHANGELOG.md\` or \`leerness whats-new --json\``));
192
195
  }
193
196
 
194
197
  module.exports = { doctorCmd, whichCmd, whatsNewCmd };