leerness 1.28.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 +45 -0
- package/README.md +4 -4
- package/bin/leerness.js +19 -4
- package/lib/diagnostics.js +44 -41
- package/lib/drift.js +23 -22
- package/package.json +1 -1
- package/scripts/e2e.js +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
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
|
+
|
|
3
48
|
## 1.28.0 — 2026-06-15 — 🛡️ [안정화/Stable] 정직성 후속 + drift 영어화 안정 minor
|
|
4
49
|
|
|
5
50
|
**🛡️ 안정화(Stable) minor — 13번째 외부리뷰 정직성 수정 + drift 진단 영어화를 npm 공개.** 직전 minor(1.27.0) 이후 누적된 패치 2건(1.27.1 + 1.27.2)을 검증·통합해 배포. R-0011 정책의 19번째 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.
|
|
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.
|
|
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.28.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.
|
|
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.
|
|
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.
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -3836,6 +3836,21 @@ function _selfTestCases() {
|
|
|
3836
3836
|
const koPreserved = dr.includes('| 신호 | age | 임계 | 가중치 | 발화 |') && dr.includes('session close 누락') && dr.includes('권장 조치'); // ko 인자 보존(e2e ko/내부호출)
|
|
3837
3837
|
return injected && en && koPreserved;
|
|
3838
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
|
+
} },
|
|
3839
3854
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3840
3855
|
];
|
|
3841
3856
|
}
|
|
@@ -16115,7 +16130,7 @@ function mcpServeCmd(root) {
|
|
|
16115
16130
|
}
|
|
16116
16131
|
|
|
16117
16132
|
// 1.9.394 (UR-0025): whatsNewCmd 를 lib/diagnostics.js 로 분리 (whats-new 서브시스템 완결 — 파서는 1.9.393 에 pure-utils 로). deps 위임.
|
|
16118
|
-
function whatsNewCmd(root) { return _diag.whatsNewCmd(root, { VERSION, arg, has }); }
|
|
16133
|
+
function whatsNewCmd(root) { return _diag.whatsNewCmd(root, { VERSION, uiLang: _uiLang(root), arg, has }); }
|
|
16119
16134
|
|
|
16120
16135
|
// 1.9.71: .env / .env.example 자동 동기화 — 누락 키 감지 + (옵션) 자동 추가
|
|
16121
16136
|
// 보안 정책: .env의 실제 값은 절대 옮기지 않음. .env.example엔 키만 (빈 값).
|
|
@@ -19734,8 +19749,8 @@ function reviewRequestCmd(root, request) { return _reviewRequest.reviewRequestCm
|
|
|
19734
19749
|
// 1.9.392 (UR-0025 큰 핸들러 모듈화 4번째): doctor/which 진단 핸들러를 lib/diagnostics.js 로 분리.
|
|
19735
19750
|
// harness 는 deps(VERSION · _selfTestCases · _detectShellCtx · _mcpToolCount · has · harnessPath)를 구성해 위임(thin wrapper). 호출부/동작 무변경.
|
|
19736
19751
|
const _diag = require('../lib/diagnostics');
|
|
19737
|
-
function doctorCmd(opts = {}) { return _diag.doctorCmd(opts, { VERSION, _selfTestCases, _detectShellCtx, _mcpToolCount, has, harnessPath: __filename }); }
|
|
19738
|
-
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 }); }
|
|
19739
19754
|
|
|
19740
19755
|
// 1.23.1 (UR-0010 Phase 6): 영어 큐레이트 도움말 — 한국어 help 의 줄별 번역이 아니라, 카테고리별로 정리한 별도 영어판.
|
|
19741
19756
|
// 레거시 버전태그(1.9.x) 군더더기를 빼고 영어 사용자가 읽기 쉽게. 전체 전수 목록은 `leerness commands`.
|
package/lib/diagnostics.js
CHANGED
|
@@ -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('✓')} 설치
|
|
42
|
-
log(` ${gr('✓')} MCP
|
|
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}
|
|
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('✓')}
|
|
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(
|
|
104
|
-
log(
|
|
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 };
|
package/lib/drift.js
CHANGED
|
@@ -168,24 +168,25 @@ function driftCheckCmd(root, opts = {}, deps = {}) {
|
|
|
168
168
|
// 재귀(_noAutoFix)는 auto-fix 블록을 건너뛰고 마지막 JSON(아래 has('--json') 블록)만 출력 → afLog 로 첫 패스 진행로그만 무음화.
|
|
169
169
|
const afLog = has('--json') ? () => {} : log;
|
|
170
170
|
// 1.9.82: 보안 신호가 fired에 있으면 우선 audit --fix 호출
|
|
171
|
-
|
|
171
|
+
// 1.28.1 (Phase 10b): 보안 신호 판정을 *언어-안정* 한 file 필드로 — 1.27.2 에서 label 을 언어화하면서 한국어 라벨 regex 매칭이 --language en 에서 깨지던 버그 수정(보안 auto-fix 미발동 방지).
|
|
172
|
+
const hasSecurityFired = fired.some(f => f.file === '.env / .gitignore');
|
|
172
173
|
if (autoFix && hasSecurityFired) {
|
|
173
174
|
afLog('');
|
|
174
|
-
afLog(`🔒 --auto-fix 활성 (1.9.82) — 보안 신호 회복: audit --fix 자동 실행
|
|
175
|
+
afLog(t(`🔒 --auto-fix 활성 (1.9.82) — 보안 신호 회복: audit --fix 자동 실행 중...`, `🔒 --auto-fix on — recovering security signal: running audit --fix...`));
|
|
175
176
|
try {
|
|
176
177
|
const r = cp.spawnSync(process.execPath, [harnessPath, 'audit', root, '--fix'],
|
|
177
178
|
{ encoding: 'utf8', timeout: 30000, env: { ...process.env, LEERNESS_INTERNAL: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' } });
|
|
178
179
|
if (r.status === 0) {
|
|
179
|
-
afLog(`✓ audit --fix 완료 — .gitignore + .env.example
|
|
180
|
+
afLog(t(`✓ audit --fix 완료 — .gitignore + .env.example 동기화`, `✓ audit --fix done — .gitignore + .env.example synced`));
|
|
180
181
|
// 재검사 (보안 신호 회복 확인)
|
|
181
182
|
afLog('');
|
|
182
|
-
afLog(`재검사
|
|
183
|
+
afLog(t(`재검사 중...`, `re-checking...`));
|
|
183
184
|
return driftCheckCmd(root, { ...opts, _noAutoFix: true }, deps); // 재귀 1회 (auto-fix 없이, 1.9.432 depth 가드)
|
|
184
185
|
} else {
|
|
185
|
-
afLog(`⚠ audit --fix 실패 (exit ${r.status}) — 수동 \`leerness audit --fix\`
|
|
186
|
+
afLog(t(`⚠ audit --fix 실패 (exit ${r.status}) — 수동 \`leerness audit --fix\` 권장`, `⚠ audit --fix failed (exit ${r.status}) — run \`leerness audit --fix\` manually`));
|
|
186
187
|
}
|
|
187
188
|
} catch (e) {
|
|
188
|
-
afLog(`⚠ auto-fix 보안 회복 오류: ${e.message}`);
|
|
189
|
+
afLog(t(`⚠ auto-fix 보안 회복 오류: ${e.message}`, `⚠ auto-fix security recovery error: ${e.message}`));
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
192
|
// 1.9.242: drift check --auto-fix 에 env encoding BOM 자동 추가 통합 (사용자 명시 UR-0014 2단계)
|
|
@@ -195,7 +196,7 @@ function driftCheckCmd(root, opts = {}, deps = {}) {
|
|
|
195
196
|
const encScan = _scanShellScriptsEncoding(root);
|
|
196
197
|
if (encScan.atRisk && encScan.atRisk.length > 0) {
|
|
197
198
|
afLog('');
|
|
198
|
-
afLog(`🌐 --auto-fix 활성 (1.9.242) — 셸 스크립트 인코딩 위험 ${encScan.atRisk.length}건 BOM 자동 추가
|
|
199
|
+
afLog(t(`🌐 --auto-fix 활성 (1.9.242) — 셸 스크립트 인코딩 위험 ${encScan.atRisk.length}건 BOM 자동 추가 중...`, `🌐 --auto-fix on — adding UTF-8 BOM to ${encScan.atRisk.length} at-risk shell script(s)...`));
|
|
199
200
|
let ok = 0;
|
|
200
201
|
for (const r of encScan.atRisk) {
|
|
201
202
|
try {
|
|
@@ -207,10 +208,10 @@ function driftCheckCmd(root, opts = {}, deps = {}) {
|
|
|
207
208
|
ok++;
|
|
208
209
|
} catch {}
|
|
209
210
|
}
|
|
210
|
-
afLog(`✓ UTF-8 BOM 추가 ${ok}/${encScan.atRisk.length}건 (1.9.242 UR-0014)`);
|
|
211
|
+
afLog(t(`✓ UTF-8 BOM 추가 ${ok}/${encScan.atRisk.length}건 (1.9.242 UR-0014)`, `✓ added UTF-8 BOM ${ok}/${encScan.atRisk.length}`));
|
|
211
212
|
}
|
|
212
213
|
} catch (e) {
|
|
213
|
-
afLog(`⚠ env encoding auto-fix 오류 (1.9.242): ${e.message}`);
|
|
214
|
+
afLog(t(`⚠ env encoding auto-fix 오류 (1.9.242): ${e.message}`, `⚠ env encoding auto-fix error: ${e.message}`));
|
|
214
215
|
}
|
|
215
216
|
}
|
|
216
217
|
// 1.9.225: drift check --auto-fix 에 delivered 패턴 자동 적용 통합 (1.9.223/224 시스템 회수)
|
|
@@ -221,16 +222,16 @@ function driftCheckCmd(root, opts = {}, deps = {}) {
|
|
|
221
222
|
const delivered = _detectDeliveredRequests(root);
|
|
222
223
|
if (delivered.candidates && delivered.candidates.length > 0) {
|
|
223
224
|
afLog('');
|
|
224
|
-
afLog(`📥 --auto-fix 활성 (1.9.225) — delivered 패턴 ${delivered.candidates.length}건 자동 완료
|
|
225
|
+
afLog(t(`📥 --auto-fix 활성 (1.9.225) — delivered 패턴 ${delivered.candidates.length}건 자동 완료 중...`, `📥 --auto-fix on — auto-completing ${delivered.candidates.length} delivered pattern(s)...`));
|
|
225
226
|
let ok = 0;
|
|
226
227
|
for (const c of delivered.candidates) {
|
|
227
228
|
const u = _updateUserRequest(root, c.id, { status: 'completed', autoCompletedAt: new Date().toISOString(), autoCompleteReason: 'drift-auto-fix-1.9.225' });
|
|
228
229
|
if (u) ok++;
|
|
229
230
|
}
|
|
230
|
-
afLog(`✓ delivered 자동 완료 ${ok}/${delivered.candidates.length}
|
|
231
|
+
afLog(t(`✓ delivered 자동 완료 ${ok}/${delivered.candidates.length}건`, `✓ delivered auto-completed ${ok}/${delivered.candidates.length}`));
|
|
231
232
|
}
|
|
232
233
|
} catch (e) {
|
|
233
|
-
afLog(`⚠ delivered auto-apply 오류 (1.9.225): ${e.message}`);
|
|
234
|
+
afLog(t(`⚠ delivered auto-apply 오류 (1.9.225): ${e.message}`, `⚠ delivered auto-apply error: ${e.message}`));
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
// 1.9.293: drift check --auto-fix 에 idempotency task/user-request 중복 자동 정리 통합
|
|
@@ -241,10 +242,10 @@ function driftCheckCmd(root, opts = {}, deps = {}) {
|
|
|
241
242
|
const totalFixed = idemFixes.reduce((n, f) => n + (f.removedExact || 0) + (f.droppedSameText || 0) + (f.count || 0), 0);
|
|
242
243
|
if (totalFixed > 0) {
|
|
243
244
|
afLog('');
|
|
244
|
-
afLog(`🔁 --auto-fix 활성 (1.9.293) — idempotency 중복 ${totalFixed}건 자동 정리 (task/user-request dedup)`);
|
|
245
|
+
afLog(t(`🔁 --auto-fix 활성 (1.9.293) — idempotency 중복 ${totalFixed}건 자동 정리 (task/user-request dedup)`, `🔁 --auto-fix on — deduped ${totalFixed} idempotency duplicate(s) (task/user-request)`));
|
|
245
246
|
}
|
|
246
247
|
} catch (e) {
|
|
247
|
-
afLog(`⚠ idempotency auto-fix 오류 (1.9.293): ${e.message}`);
|
|
248
|
+
afLog(t(`⚠ idempotency auto-fix 오류 (1.9.293): ${e.message}`, `⚠ idempotency auto-fix error: ${e.message}`));
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
251
|
// 1.9.236: drift check --auto-fix 에 release cleanup 통합 (1.9.235 회수)
|
|
@@ -260,7 +261,7 @@ function driftCheckCmd(root, opts = {}, deps = {}) {
|
|
|
260
261
|
.filter(l => l && /^release\/\d+\.\d+\.\d+$/.test(l));
|
|
261
262
|
if (merged.length > 50) {
|
|
262
263
|
afLog('');
|
|
263
|
-
afLog(`🗑 --auto-fix 활성 (1.9.236) — release/* merged ${merged.length}개 (50+) 자동 정리 (keep 10)...`);
|
|
264
|
+
afLog(t(`🗑 --auto-fix 활성 (1.9.236) — release/* merged ${merged.length}개 (50+) 자동 정리 (keep 10)...`, `🗑 --auto-fix on — cleaning up ${merged.length} merged release/* branches (50+, keep 10)...`));
|
|
264
265
|
// 정렬 (semver desc)
|
|
265
266
|
merged.sort((a, b) => {
|
|
266
267
|
const va = a.replace('release/', '').split('.').map(n => parseInt(n, 10) || 0);
|
|
@@ -276,20 +277,20 @@ function driftCheckCmd(root, opts = {}, deps = {}) {
|
|
|
276
277
|
const r = cp.spawnSync('git', ['branch', '-d', b], { cwd: root, encoding: 'utf8' });
|
|
277
278
|
if (r.status === 0) ok++;
|
|
278
279
|
}
|
|
279
|
-
afLog(`✓ release cleanup 자동 완료 ${ok}/${toDelete.length}건 (keep 10)`);
|
|
280
|
+
afLog(t(`✓ release cleanup 자동 완료 ${ok}/${toDelete.length}건 (keep 10)`, `✓ release cleanup done ${ok}/${toDelete.length} (keep 10)`));
|
|
280
281
|
}
|
|
281
282
|
}
|
|
282
283
|
} catch (e) {
|
|
283
|
-
afLog(`⚠ release cleanup auto-fix 오류 (1.9.236): ${e.message}`);
|
|
284
|
+
afLog(t(`⚠ release cleanup auto-fix 오류 (1.9.236): ${e.message}`, `⚠ release cleanup auto-fix error: ${e.message}`));
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
287
|
if (autoFix && level === '🔴 critical' && !hasSecurityFired) {
|
|
287
288
|
afLog('');
|
|
288
|
-
afLog(`🔧 --auto-fix 활성 — session close 자동 실행
|
|
289
|
+
afLog(t(`🔧 --auto-fix 활성 — session close 자동 실행 중...`, `🔧 --auto-fix on — running session close...`));
|
|
289
290
|
try {
|
|
290
291
|
const r = cp.spawnSync(process.execPath, [harnessPath, 'session', 'close', root], { encoding: 'utf8', timeout: 60000, env: { ...process.env, LEERNESS_INTERNAL: '1' } });
|
|
291
292
|
if (r.status === 0) {
|
|
292
|
-
afLog(`✓ session close 자동
|
|
293
|
+
afLog(t(`✓ session close 자동 완료`, `✓ session close done`));
|
|
293
294
|
// autoResolved 카운트
|
|
294
295
|
const stats = _readUsageStats(root);
|
|
295
296
|
stats.drift = stats.drift || {};
|
|
@@ -299,13 +300,13 @@ function driftCheckCmd(root, opts = {}, deps = {}) {
|
|
|
299
300
|
writeUtf8(p, JSON.stringify(stats, null, 2) + '\n');
|
|
300
301
|
// 재검사
|
|
301
302
|
afLog('');
|
|
302
|
-
afLog(`재검사
|
|
303
|
+
afLog(t(`재검사 중...`, `re-checking...`));
|
|
303
304
|
return driftCheckCmd(root, { ...opts, _noAutoFix: true }, deps); // 재귀 1회 (auto-fix 없이, 1.9.432 depth 가드)
|
|
304
305
|
} else {
|
|
305
|
-
afLog(`⚠ session close 실패 (exit ${r.status}) — 수동 실행
|
|
306
|
+
afLog(t(`⚠ session close 실패 (exit ${r.status}) — 수동 실행 필요`, `⚠ session close failed (exit ${r.status}) — run manually`));
|
|
306
307
|
}
|
|
307
308
|
} catch (e) {
|
|
308
|
-
afLog(`⚠ auto-fix 오류: ${e.message}`);
|
|
309
|
+
afLog(t(`⚠ auto-fix 오류: ${e.message}`, `⚠ auto-fix error: ${e.message}`));
|
|
309
310
|
}
|
|
310
311
|
}
|
|
311
312
|
if (has('--json')) {
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -6234,10 +6234,14 @@ total++;
|
|
|
6234
6234
|
const drEn = out(cp.spawnSync(process.execPath, [CLI, 'drift', 'check', d, '--language', 'en'], { encoding: 'utf8', timeout: 20000 }));
|
|
6235
6235
|
const drKo = out(cp.spawnSync(process.execPath, [CLI, 'drift', 'check', d], { encoding: 'utf8', timeout: 20000 }));
|
|
6236
6236
|
const driftOk = /signal \| age \| threshold/.test(drEn) && !H.test(drEn) && /신호 \| age \| 임계/.test(drKo);
|
|
6237
|
+
// ⑦ (1.28.2 Phase 10c) doctor: en 영어(한글 0) + ko 기본 한글 보존
|
|
6238
|
+
const docEn = out(cp.spawnSync(process.execPath, [CLI, 'doctor', '--language', 'en'], { encoding: 'utf8', timeout: 20000, cwd: d }));
|
|
6239
|
+
const docKo = out(cp.spawnSync(process.execPath, [CLI, 'doctor'], { encoding: 'utf8', timeout: 20000, cwd: d }));
|
|
6240
|
+
const doctorOk = /install\/environment diagnosis/.test(docEn) && !H.test(docEn) && /설치\/환경 진단/.test(docKo);
|
|
6237
6241
|
fs.rmSync(d, { recursive: true, force: true });
|
|
6238
|
-
ok = lensKoOk && lensEnOk && noLeak && stOk && healthOk && driftOk;
|
|
6242
|
+
ok = lensKoOk && lensEnOk && noLeak && stOk && healthOk && driftOk && doctorOk;
|
|
6239
6243
|
} catch {}
|
|
6240
|
-
console.log(ok ? '✓ B(1.25.1/1.25.2/1.27.2) i18n 행위: --language en 런타임 영어(lens/health/drift) + ko 기본 보존 + --language positional 무누출 + status 에러 en/ko (UR-0010)' : '✗ i18n 행위 회귀 가드 실패');
|
|
6244
|
+
console.log(ok ? '✓ B(1.25.1/1.25.2/1.27.2/1.28.2) i18n 행위: --language en 런타임 영어(lens/health/drift/doctor) + ko 기본 보존 + --language positional 무누출 + status 에러 en/ko (UR-0010)' : '✗ i18n 행위 회귀 가드 실패');
|
|
6241
6245
|
if (!ok) failed++;
|
|
6242
6246
|
}
|
|
6243
6247
|
|