leerness 1.29.0 → 1.30.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 +92 -0
- package/README.md +4 -4
- package/bin/leerness.js +73 -21
- package/package.json +1 -1
- package/scripts/e2e.js +61 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,97 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.30.0 — 2026-06-16 — 🛡️ [안정화/Stable] handoff 본문 i18n 4종 안정 minor
|
|
4
|
+
|
|
5
|
+
**🛡️ 안정화(Stable) minor — handoff 본문 4블록 영어화 + i18n-coupling 감사 + 블록-스코프 t 회귀수정을 npm 공개.** 직전 minor(1.29.0) 이후 누적된 패치 4건(1.29.1~1.29.4)을 검증·통합해 배포. R-0011 정책의 21번째 stable minor. 한국어 우선 기본은 그대로.
|
|
6
|
+
|
|
7
|
+
### 이번 minor 통합 (1.29.1~1.29.4)
|
|
8
|
+
- **🔒🌐 보안 요약 섹션 영어화 + i18n-coupling 감사 (1.29.1)**: 영어 사용자가 커밋된 시크릿을 가질 때 정확히 노출되는 handoff 보안 요약 섹션(`## 🔒 보안 요약` / issues / `🚨 CRITICAL` / 자동회복 / `💡 자동 실행 옵션`)을 영어화. lib/ 전수로 1.28.1 `hasSecurityFired` 류 라벨-결합 버그 추가 탐색 → 0건. **블록-스코프 t 회귀 자체수정**: headline `t()` 스코프 밖이라 `ReferenceError → try 삼킴 → 섹션 증발` 회귀를 심었다가 **행위 검증**으로 적발(소스가드는 통과)·수정.
|
|
9
|
+
- **🌐 env-detect 블록 영어화 (1.29.2)**: `🖥 실행 환경` PATH 누락/변동 감지 + `→ 상세` 안내 영어화.
|
|
10
|
+
- **🌐 shell-guard 블록 영어화 (1.29.3)**: `🐚 터미널 셸 가드` 헤더 + 환경 버전 변동 재검토 + 최근 셸 실패 + 명령 실행 전 점검 영어화.
|
|
11
|
+
- **🌐 CLI 에이전트 슬래시 블록 영어화 (1.29.4)**: `🤖 CLI 에이전트 슬래시 명령` 헤더 + 활성 에이전트 + (하위명령) + 전체/기록/최신화 영어화.
|
|
12
|
+
- **공통**: 4블록 모두 headline `t()` 스코프 밖 → 블록 로컬 `t()`/`_uiLang(root)` 정의(1.29.1 교훈 적용). 한국어 verbatim 보존.
|
|
13
|
+
|
|
14
|
+
### 핵심 교훈 (defense-in-depth)
|
|
15
|
+
- **소스가드(문자열 존재) ≠ 동작**: 번역 헬퍼를 다른 섹션의 블록 스코프에 두면 `ReferenceError` 가 `try/catch` 에 삼켜져 표면 전체가 (양 언어 모두) 사라진다. selftest 소스가드는 통과하지만 런타임 출력이 0. **행위 검증 + e2e 가드**로만 잡힌다.
|
|
16
|
+
|
|
17
|
+
### 검증 (회귀 0)
|
|
18
|
+
- **selftest 250→254** (4블록 영어/한국어 보존 소스가드, split-literal 로 self-reference 회피).
|
|
19
|
+
- **E2E 368/368** — i18n 행위가드 **⑧~⑪ 신규**: 보안요약(.env+미흡 gitignore) / env-detect(스냅샷 변동) / shell-guard(셸실패+스냅샷변동) / agent-slash(env flag 활성) 4 시나리오를 실제 발동시켜 en 영어(블록 한글 0, Node 탐지) + ko 보존 검증.
|
|
20
|
+
- minor(1.30.0) — npm 배포(R-0011 stable) + annotated tag(Stable) + GitHub release(latest) + 게시본 클린룸 재실증.
|
|
21
|
+
|
|
22
|
+
### 잔여 (UR-0010 백로그)
|
|
23
|
+
- capabilities/commands/constraints/install-safety 영어화 · init en seed 템플릿 i18n · handoff 본문 잔여 표면(팀 스케줄/요청 자동완료/비정상종료) 점진 영어화.
|
|
24
|
+
|
|
25
|
+
## 1.29.4 — 2026-06-16 — handoff CLI 에이전트 슬래시 명령 블록 영어화 (UR-0010)
|
|
26
|
+
|
|
27
|
+
**🌐 handoff CLI 에이전트 슬래시 명령 블록을 영어화.** 1.29.3 잔여 백로그. 외부 CLI 에이전트(codex/claude/agy/copilot/ollama)가 env flag 로 활성일 때 노출되는 sub-agent 슬래시 명령 요약을 `--language en` 에서 영어로. 한국어 기본 보존.
|
|
28
|
+
|
|
29
|
+
### 변경 (UR-0010)
|
|
30
|
+
- **🌐 agent-slash 블록 4줄 영어화**: `## 🤖 CLI agent slash commands (UR-0021)` 헤더 + `N active agent(s) — use each one's slash commands when invoking a sub-agent:` + ` (subcommand)` 접미사 + `→ full list / record / refresh: leerness slash-commands [agent] [--record]`. 에이전트별 명령 라인(`id cmd…`)은 데이터라 무변경. ko verbatim 보존.
|
|
31
|
+
- **블록-스코프 t 함정 회피(1.29.1 교훈)**: 이 블록도 headline `t()` 스코프 밖 → 블록 로컬 `t()`/`_uiLang(root)` 정의.
|
|
32
|
+
|
|
33
|
+
### 검증 (회귀 0)
|
|
34
|
+
- **selftest 253→254** (agent-slash 영어/한국어 보존 소스가드, split-literal).
|
|
35
|
+
- **행위 검증(1.29.1 교훈)**: `LEERNESS_ENABLE_CODEX=1`+`LEERNESS_ENABLE_CLAUDE=1` 로 블록 발동 → `handoff --language en` 영어 렌더(블록 라인 한글 0, Node 탐지) / `handoff` ko 보존.
|
|
36
|
+
- **E2E i18n 행위가드 ⑪ 추가**: agent-slash en/ko 를 env flag 활성 시나리오로 e2e 에 못박음.
|
|
37
|
+
- patch(1.29.4) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
38
|
+
|
|
39
|
+
### 잔여 (UR-0010 백로그)
|
|
40
|
+
- capabilities/commands/constraints/install-safety 영어화 · init en seed 템플릿 i18n · handoff 본문 잔여 표면(팀 스케줄/요청 자동완료/비정상종료 등) 점진 영어화.
|
|
41
|
+
|
|
42
|
+
## 1.29.3 — 2026-06-16 — handoff shell-guard 블록 영어화 (UR-0010)
|
|
43
|
+
|
|
44
|
+
**🌐 handoff 터미널 셸 가드 블록을 영어화.** 1.29.2 probe 에서 관측한 인접 en-leak. 과거 셸 실패(예: PowerShell 5.1 `&&` 미지원) + 환경 버전 변동 알림을 `--language en` 에서 영어로. 한국어 기본 보존.
|
|
45
|
+
|
|
46
|
+
### 변경 (UR-0010)
|
|
47
|
+
- **🌐 shell-guard 블록 4줄 영어화**: `## 🐚 Terminal shell guard (UR-0020)` 헤더 + `⚠ Environment version changed — review past shell failures:` + `N recent shell failure(s) (showing up to 3):` + `→ check before running a command: leerness shell-guard "<command>"`. data 라인(`what: from → to`, `cmd (exit=…, shell)`)은 이미 영어라 무변경. ko verbatim 보존.
|
|
48
|
+
- **블록-스코프 t 함정 회피(1.29.1 교훈)**: 이 블록도 headline `t()` 스코프 밖 → 블록 로컬 `t()`/`_uiLang(root)` 정의하고 영어화(회귀 사전 차단).
|
|
49
|
+
|
|
50
|
+
### 검증 (회귀 0)
|
|
51
|
+
- **selftest 252→253** (shell-guard 영어/한국어 보존 소스가드, split-literal).
|
|
52
|
+
- **행위 검증(1.29.1 교훈)**: `.harness/shell-failures.json` 시드 + 스냅샷 변동으로 hasFailures/hasDrift 양쪽 발동 → `handoff --language en` 4줄 영어 렌더(블록 라인 한글 0, Node 탐지) / `handoff` ko 보존.
|
|
53
|
+
- **E2E i18n 행위가드 ⑩ 추가**: shell-guard en/ko 를 셸실패+스냅샷변동 시나리오로 e2e 에 못박음.
|
|
54
|
+
- patch(1.29.3) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
55
|
+
|
|
56
|
+
### 잔여 (UR-0010 백로그)
|
|
57
|
+
- handoff CLI 에이전트 슬래시 명령 블록(`## 🤖 CLI 에이전트 슬래시 명령`/`활성 에이전트 N개`) en-leak(외부 에이전트 활성 시 노출, 다음 라운드) · capabilities/commands/constraints/install-safety 영어화 · init en seed 템플릿 i18n.
|
|
58
|
+
|
|
59
|
+
## 1.29.2 — 2026-06-16 — handoff env-detect 블록 영어화 (UR-0010)
|
|
60
|
+
|
|
61
|
+
**🌐 handoff 실행 환경 변동/PATH 누락 알림을 영어화.** 1.29.1 감사 중 발견한 인접 en-leak. `🖥 실행 환경` 헤더 2종(PATH 누락 / 변동 감지) + `→ 상세` 안내를 `--language en` 에서 영어로. 변동 설명(`os.platform`/`node`/`tool added` 등)은 이미 영어라 source 무변경. 한국어 기본 보존.
|
|
62
|
+
|
|
63
|
+
### 변경 (UR-0010)
|
|
64
|
+
- **🌐 env-detect 블록 3줄 영어화**: `🖥 Runtime environment: ⚠ N PATH tool(s) missing — npm run may fail` / `🖥 Runtime environment: N change(s) detected` / `→ details: leerness env detect . --json`. ko verbatim 보존.
|
|
65
|
+
- **블록-스코프 t 함정 회피(1.29.1 교훈 적용)**: env-detect 블록도 headline 의 `t()` 스코프 밖이라, 처음부터 블록 로컬 `t()`/`_uiLang(root)` 를 정의하고 영어화 — `ReferenceError → try 삼킴 → 블록 증발` 회귀를 사전 차단.
|
|
66
|
+
|
|
67
|
+
### 검증 (회귀 0)
|
|
68
|
+
- **selftest 251→252** (env-detect 영어/한국어 보존 소스가드, split-literal).
|
|
69
|
+
- **행위 검증(1.29.1 교훈)**: 스냅샷 `node.version` 인위 변경으로 블록 강제 발동 → `handoff --language en` 이 영어 렌더(블록 라인 한글 0, Node 탐지) / `handoff` ko 보존 확인.
|
|
70
|
+
- **E2E i18n 행위가드 ⑨ 추가**: env-detect en/ko 를 스냅샷 변동 시나리오로 e2e 에 못박음.
|
|
71
|
+
- patch(1.29.2) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
72
|
+
|
|
73
|
+
### 잔여 (UR-0010 백로그)
|
|
74
|
+
- handoff shell-failures 재검토 알림(`⚠ 환경 버전 변동 — 과거 셸 실패 기록 재검토 권장`) en-leak(env 변동 시 동반 노출, 다음 라운드) · capabilities/commands/constraints/install-safety 영어화 · init en seed 템플릿 i18n.
|
|
75
|
+
|
|
76
|
+
## 1.29.1 — 2026-06-16 — i18n-coupling 감사(clean) + handoff 보안 요약 섹션 영어화 (UR-0010)
|
|
77
|
+
|
|
78
|
+
**🔒🌐 영어 사용자가 커밋된 시크릿을 가질 때 정확히 노출되는 handoff 보안 요약 섹션을 영어화.** 1.28.1 의 `hasSecurityFired` 라벨-결합 버그 클래스를 lib/ 전수 감사(추가 결합 버그 0)한 뒤, 감사 중 발견한 마지막 en-leak(보안 요약 본문)을 영어화. 한국어 우선 기본 보존.
|
|
79
|
+
|
|
80
|
+
### 감사 (i18n-coupling, clean)
|
|
81
|
+
- **lib/ 전수**: 번역된 라벨에 내부 로직이 string-match 로 결합된 곳(1.28.1 류) 추가 탐색 → **0 건**. drift `level === '🔴 critical'` 은 언어-안정(미번역), analyzer 조건은 bilingual-by-design, audit/pure-utils 한국어 조건은 미번역 템플릿 매칭, agents 조건은 안정 status 필드. 교차명령(handoff en headline 이 내부 ko-spawn `--json` 을 안정 필드로 파싱)도 견고.
|
|
82
|
+
|
|
83
|
+
### 변경 (UR-0010)
|
|
84
|
+
- **🌐 handoff 보안 요약 섹션 영어화**: `## 🔒 보안 요약` 헤더 + 2 issue(`.env→.env.example` / `.gitignore` 시크릿 누락) + `자동 수정` 안내 + `🚨 CRITICAL` + 자동 회복 3줄 + `💡 자동 실행 옵션` 을 `t(ko,en)` 으로 영어 opt-in. 한국어 verbatim 보존.
|
|
85
|
+
- **🐛 회귀 자체수정(같은 라운드)**: 영어화 첫 시도에서 이 블록이 headline 의 `t()` 스코프 밖이라 `t` 가 미정의 → `ReferenceError` 가 블록의 `try`/`catch` 에 **삼켜져 보안 요약 섹션 전체가 (양 언어 모두) 사라지는** 회귀를 심었다. 소스가드(문자열 존재)는 통과했지만 **행위 검증**에서 적발(맹신 X) → 블록 로컬 `t()` 정의로 수정. 같은 함정의 [[lesson-selftest-self-reference-trap]] / [[lesson-reverify-published-artifact]] 계열.
|
|
86
|
+
|
|
87
|
+
### 검증 (회귀 0)
|
|
88
|
+
- **selftest 250→251** (보안 요약 영어/한국어 보존 소스가드, split-literal 로 self-reference 회피) · **행위**: `.env`+미흡 `.gitignore` 시나리오에서 `handoff --language en` 보안 요약 영어 렌더(섹션 라인 한글 0, Node 탐지) / `handoff` ko 보존.
|
|
89
|
+
- **E2E i18n 행위가드 ⑧ 추가**: handoff 보안 요약 en/ko 를 e2e 로 못박음 — 소스가드가 못 잡는 "블록-스코프 t 누락 → 섹션 증발" 회귀를 행위로 차단(defense-in-depth).
|
|
90
|
+
- patch(1.29.1) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
91
|
+
|
|
92
|
+
### 잔여 (UR-0010 백로그)
|
|
93
|
+
- handoff env-detect 블록(`🖥 실행 환경`/`→ 상세: leerness env detect`) en-leak(다음 라운드) · capabilities/commands/constraints/install-safety 영어화 · init en seed 템플릿 i18n.
|
|
94
|
+
|
|
3
95
|
## 1.29.0 — 2026-06-16 — 🛡️ [안정화/Stable] drift auto-fix·진단 명령 영어화 안정 minor
|
|
4
96
|
|
|
5
97
|
**🛡️ 안정화(Stable) minor — drift 완전 영어화(버그수정 포함) + 진단 3종 영어화를 npm 공개.** 직전 minor(1.28.0) 이후 누적된 패치 2건(1.28.1 + 1.28.2)을 검증·통합해 배포. R-0011 정책의 20번째 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.30.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.30.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.29.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.30.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.30.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.30.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') 시 호스트 프로세스 오염.
|
|
@@ -3851,6 +3851,46 @@ function _selfTestCases() {
|
|
|
3851
3851
|
const koPreserved = dg.includes('설치/환경 진단') && dg.includes('## npm 환경') && dg.includes('버전별 헤드라인'); // ko 인자 보존(내부호출/e2e)
|
|
3852
3852
|
return injected && en && koPreserved;
|
|
3853
3853
|
} },
|
|
3854
|
+
{ name: 'Phase 10d (1.29.1): handoff 보안 요약 섹션 영어/한국어 보존 (소스 가드)', run: () => {
|
|
3855
|
+
const bin = read(__filename);
|
|
3856
|
+
// split-literals so this guard's own strings are not contiguous matches (self-reference trap)
|
|
3857
|
+
const en = bin.includes('Securit' + 'y summary —')
|
|
3858
|
+
&& bin.includes('auto-recover' + 'ed (LEERNESS_AUTO_SECURITY_FIX')
|
|
3859
|
+
&& bin.includes('auto-fix optio' + 'n: LEERNESS_AUTO_SECURITY_FIX');
|
|
3860
|
+
const koPreserved = bin.includes('보안 ' + '요약 (1.9.76)')
|
|
3861
|
+
&& bin.includes('자동 실행 ' + '옵션: LEERNESS_AUTO_SECURITY_FIX');
|
|
3862
|
+
return en && koPreserved;
|
|
3863
|
+
} },
|
|
3864
|
+
{ name: 'Phase 10e (1.29.2): handoff env-detect 블록 영어/한국어 보존 (소스 가드)', run: () => {
|
|
3865
|
+
const bin = read(__filename);
|
|
3866
|
+
// split-literals (self-reference trap 회피)
|
|
3867
|
+
const en = bin.includes('Runtime environ' + 'ment: ⚠')
|
|
3868
|
+
&& bin.includes('change(s) detec' + 'ted')
|
|
3869
|
+
&& bin.includes('→ detail' + 's: leerness env detect');
|
|
3870
|
+
const koPreserved = bin.includes('실행 환경 (1.9.145): ⚠ PATH 누' + '락')
|
|
3871
|
+
&& bin.includes('→ 상' + '세: leerness env detect');
|
|
3872
|
+
return en && koPreserved;
|
|
3873
|
+
} },
|
|
3874
|
+
{ name: 'Phase 10f (1.29.3): handoff shell-guard 블록 영어/한국어 보존 (소스 가드)', run: () => {
|
|
3875
|
+
const bin = read(__filename);
|
|
3876
|
+
// split-literals (self-reference trap 회피)
|
|
3877
|
+
const en = bin.includes('Terminal shell gua' + 'rd (UR-0020)')
|
|
3878
|
+
&& bin.includes('review past shell fail' + 'ures')
|
|
3879
|
+
&& bin.includes('check before running a comm' + 'and: leerness shell-guard');
|
|
3880
|
+
const koPreserved = bin.includes('터미널 셸 가' + '드 (1.9.263, UR-0020)')
|
|
3881
|
+
&& bin.includes('과거 셸 실패 기록 재검' + '토 권장');
|
|
3882
|
+
return en && koPreserved;
|
|
3883
|
+
} },
|
|
3884
|
+
{ name: 'Phase 10g (1.29.4): handoff CLI 에이전트 슬래시 블록 영어/한국어 보존 (소스 가드)', run: () => {
|
|
3885
|
+
const bin = read(__filename);
|
|
3886
|
+
// split-literals (self-reference trap 회피)
|
|
3887
|
+
const en = bin.includes('CLI agent slash comma' + 'nds (UR-0021)')
|
|
3888
|
+
&& bin.includes("active agent(s) — use each one'" + 's slash commands')
|
|
3889
|
+
&& bin.includes('full list / record / refr' + 'esh: leerness slash-commands');
|
|
3890
|
+
const koPreserved = bin.includes('CLI 에이전트 슬래시 명' + '령 (1.9.265~266, UR-0021)')
|
|
3891
|
+
&& bin.includes('전체/기록/최신' + '화: leerness slash-commands');
|
|
3892
|
+
return en && koPreserved;
|
|
3893
|
+
} },
|
|
3854
3894
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3855
3895
|
];
|
|
3856
3896
|
}
|
|
@@ -8806,23 +8846,26 @@ function handoff(root) {
|
|
|
8806
8846
|
const hasFailures = sf.failures && sf.failures.length > 0;
|
|
8807
8847
|
const hasDrift = drift && drift.changes && drift.changes.length > 0;
|
|
8808
8848
|
if (hasFailures || hasDrift) {
|
|
8849
|
+
// 1.29.3: headline t() 스코프 밖 — 로컬 t() (1.29.1 교훈: 없으면 ReferenceError 가 try 에 삼켜져 블록 증발)
|
|
8850
|
+
const _Lsh = _uiLang(root);
|
|
8851
|
+
const t = (ko, en) => (_Lsh === 'en' ? en : ko);
|
|
8809
8852
|
const isTty8 = process.stdout && process.stdout.isTTY;
|
|
8810
8853
|
const yl8 = s => isTty8 ? `\x1b[33m${s}\x1b[0m` : s;
|
|
8811
8854
|
const dm8 = s => isTty8 ? `\x1b[2m${s}\x1b[0m` : s;
|
|
8812
8855
|
const cy8 = s => isTty8 ? `\x1b[36m${s}\x1b[0m` : s;
|
|
8813
8856
|
log('');
|
|
8814
|
-
log(cy8(`## 🐚 터미널 셸 가드 (1.9.263, UR-0020)`));
|
|
8857
|
+
log(cy8(t(`## 🐚 터미널 셸 가드 (1.9.263, UR-0020)`, `## 🐚 Terminal shell guard (UR-0020)`)));
|
|
8815
8858
|
if (hasDrift) {
|
|
8816
|
-
log(yl8(` ⚠ 환경 버전 변동 — 과거 셸 실패 기록 재검토
|
|
8859
|
+
log(yl8(t(` ⚠ 환경 버전 변동 — 과거 셸 실패 기록 재검토 권장:`, ` ⚠ Environment version changed — review past shell failures:`)));
|
|
8817
8860
|
drift.changes.forEach(ch => log(dm8(` ${ch.what}: ${ch.from} → ${ch.to}`)));
|
|
8818
8861
|
}
|
|
8819
8862
|
if (hasFailures) {
|
|
8820
|
-
log(dm8(` 최근 셸 실패 ${sf.failures.length}건 (최대 3 표시):`));
|
|
8863
|
+
log(dm8(t(` 최근 셸 실패 ${sf.failures.length}건 (최대 3 표시):`, ` ${sf.failures.length} recent shell failure(s) (showing up to 3):`)));
|
|
8821
8864
|
sf.failures.slice(-3).reverse().forEach(f => {
|
|
8822
8865
|
const rules = (f.issues && f.issues.length) ? ` [${f.issues.join(',')}]` : '';
|
|
8823
8866
|
log(dm8(` • ${(f.cmd || '').slice(0, 50)} (exit=${f.exitCode}, ${f.shell})${rules}`));
|
|
8824
8867
|
});
|
|
8825
|
-
log(dm8(` → 명령 실행 전 점검: leerness shell-guard "<command>"`));
|
|
8868
|
+
log(dm8(t(` → 명령 실행 전 점검: leerness shell-guard "<command>"`, ` → check before running a command: leerness shell-guard "<command>"`)));
|
|
8826
8869
|
}
|
|
8827
8870
|
}
|
|
8828
8871
|
} catch {}
|
|
@@ -8835,20 +8878,23 @@ function handoff(root) {
|
|
|
8835
8878
|
return v && v !== '0' && String(v).toLowerCase() !== 'false';
|
|
8836
8879
|
});
|
|
8837
8880
|
if (enabledAgents.length > 0) {
|
|
8881
|
+
// 1.29.4: headline t() 스코프 밖 — 로컬 t() (1.29.1 교훈)
|
|
8882
|
+
const _Lag = _uiLang(root);
|
|
8883
|
+
const t = (ko, en) => (_Lag === 'en' ? en : ko);
|
|
8838
8884
|
const isTtyS = process.stdout && process.stdout.isTTY;
|
|
8839
8885
|
const cyS = s => isTtyS ? `\x1b[36m${s}\x1b[0m` : s;
|
|
8840
8886
|
const dmS = s => isTtyS ? `\x1b[2m${s}\x1b[0m` : s;
|
|
8841
8887
|
log('');
|
|
8842
|
-
log(cyS(`## 🤖 CLI 에이전트 슬래시 명령 (1.9.265~266, UR-0021)`));
|
|
8843
|
-
log(dmS(` 활성 에이전트 ${enabledAgents.length}개 — sub-agent 호출 시 각자 슬래시 명령
|
|
8888
|
+
log(cyS(t(`## 🤖 CLI 에이전트 슬래시 명령 (1.9.265~266, UR-0021)`, `## 🤖 CLI agent slash commands (UR-0021)`)));
|
|
8889
|
+
log(dmS(t(` 활성 에이전트 ${enabledAgents.length}개 — sub-agent 호출 시 각자 슬래시 명령 사용:`, ` ${enabledAgents.length} active agent(s) — use each one's slash commands when invoking a sub-agent:`)));
|
|
8844
8890
|
for (const a of enabledAgents) {
|
|
8845
8891
|
const hint = _agentSlashHint(root, a.id);
|
|
8846
8892
|
if (hint && hint.commands.length) {
|
|
8847
8893
|
const top = hint.commands.slice(0, 8).map(c => c.cmd).join(' ');
|
|
8848
|
-
log(dmS(` ${a.id.padEnd(8)} ${top}${hint.invoke === 'subcommand' ? ' (하위명령)' : ''}`));
|
|
8894
|
+
log(dmS(` ${a.id.padEnd(8)} ${top}${hint.invoke === 'subcommand' ? t(' (하위명령)', ' (subcommand)') : ''}`));
|
|
8849
8895
|
}
|
|
8850
8896
|
}
|
|
8851
|
-
log(dmS(` → 전체/기록/최신화: leerness slash-commands [agent] [--record]`));
|
|
8897
|
+
log(dmS(t(` → 전체/기록/최신화: leerness slash-commands [agent] [--record]`, ` → full list / record / refresh: leerness slash-commands [agent] [--record]`)));
|
|
8852
8898
|
}
|
|
8853
8899
|
} catch {}
|
|
8854
8900
|
|
|
@@ -9489,6 +9535,9 @@ function handoff(root) {
|
|
|
9489
9535
|
// 첫 실행에선 자동 캡처 (silent), 이후엔 변동/누락 시에만 노출.
|
|
9490
9536
|
if (!has('--no-env-detect') && !has('--compact') && !has('--quiet') && process.env.LEERNESS_NO_ENV_DETECT !== '1') {
|
|
9491
9537
|
try {
|
|
9538
|
+
// 1.29.2: headline t() 스코프 밖 — 로컬 t() (없으면 ReferenceError 가 try 에 삼켜져 env-detect 블록이 사라짐, 1.29.1 교훈)
|
|
9539
|
+
const _Lenv = _uiLang(root);
|
|
9540
|
+
const t = (ko, en) => (_Lenv === 'en' ? en : ko);
|
|
9492
9541
|
const isTtyEd = process.stdout && process.stdout.isTTY;
|
|
9493
9542
|
const edCy = s => isTtyEd ? `\x1b[35m${s}\x1b[0m` : s; // magenta
|
|
9494
9543
|
const edDim = s => isTtyEd ? `\x1b[2m${s}\x1b[0m` : s;
|
|
@@ -9502,14 +9551,14 @@ function handoff(root) {
|
|
|
9502
9551
|
} else if (diff.changes.length || (diff.missing && diff.missing.length)) {
|
|
9503
9552
|
// 변동/누락 알림
|
|
9504
9553
|
if (diff.missing && diff.missing.length) {
|
|
9505
|
-
log(edCy(`🖥 실행 환경 (1.9.145): ⚠ PATH 누락 ${diff.missing.length}건 — npm run 시 실패
|
|
9554
|
+
log(edCy(t(`🖥 실행 환경 (1.9.145): ⚠ PATH 누락 ${diff.missing.length}건 — npm run 시 실패 가능`, `🖥 Runtime environment: ⚠ ${diff.missing.length} PATH tool(s) missing — npm run may fail`)));
|
|
9506
9555
|
for (const m of diff.missing.slice(0, 3)) log(edDim(` • ${m.command} (used by: npm run ${m.usedBy})`));
|
|
9507
9556
|
}
|
|
9508
9557
|
if (diff.changes.length) {
|
|
9509
|
-
log(edCy(`🖥 실행 환경 (1.9.145): 변동 ${diff.changes.length}건
|
|
9558
|
+
log(edCy(t(`🖥 실행 환경 (1.9.145): 변동 ${diff.changes.length}건 감지`, `🖥 Runtime environment: ${diff.changes.length} change(s) detected`)));
|
|
9510
9559
|
for (const c of diff.changes.slice(0, 3)) log(edDim(` • ${c}`));
|
|
9511
9560
|
}
|
|
9512
|
-
log(edDim(` → 상세: leerness env detect . --json`));
|
|
9561
|
+
log(edDim(t(` → 상세: leerness env detect . --json`, ` → details: leerness env detect . --json`)));
|
|
9513
9562
|
log('');
|
|
9514
9563
|
// 갱신 (다음 비교 baseline)
|
|
9515
9564
|
try { writeUtf8(snapPath, JSON.stringify(curr, null, 2) + '\n'); } catch {}
|
|
@@ -9520,13 +9569,16 @@ function handoff(root) {
|
|
|
9520
9569
|
// 매 세션 시작 시 AI가 보안 위험을 즉시 인지. --no-security-summary 또는 --compact로 끄기
|
|
9521
9570
|
if (!has('--no-security-summary') && !has('--compact') && !has('--quiet')) {
|
|
9522
9571
|
try {
|
|
9572
|
+
// 1.29.1: 이 블록은 headline의 t() 스코프 밖 — 로컬 t() 정의 (없으면 ReferenceError가 try에 삼켜져 보안 요약 전체가 사라짐)
|
|
9573
|
+
const _Lsec = _uiLang(root);
|
|
9574
|
+
const t = (ko, en) => (_Lsec === 'en' ? en : ko);
|
|
9523
9575
|
const envExists = exists(path.join(root, '.env'));
|
|
9524
9576
|
if (envExists) {
|
|
9525
9577
|
const issues = [];
|
|
9526
9578
|
// 1) env diff
|
|
9527
9579
|
try {
|
|
9528
9580
|
const d = envDiff(root);
|
|
9529
|
-
if (d.inEnvOnly.length) issues.push(`.env→.env.example 누락 ${d.inEnvOnly.length}
|
|
9581
|
+
if (d.inEnvOnly.length) issues.push(t(`.env→.env.example 누락 ${d.inEnvOnly.length}건`, `.env→.env.example missing ${d.inEnvOnly.length}`));
|
|
9530
9582
|
} catch {}
|
|
9531
9583
|
// 2) gitignore 시크릿 패턴
|
|
9532
9584
|
try {
|
|
@@ -9535,7 +9587,7 @@ function handoff(root) {
|
|
|
9535
9587
|
const giLines = giText.split('\n').map(l => l.trim());
|
|
9536
9588
|
const SECRET_PATTERNS = ['.env', '.env.local', '.env.production', '.env.*.local', '*.pem', 'credentials.json'];
|
|
9537
9589
|
const missing = SECRET_PATTERNS.filter(p => !giLines.some(l => l === p || l === '/' + p));
|
|
9538
|
-
if (missing.length) issues.push(`.gitignore 시크릿 누락 ${missing.length}
|
|
9590
|
+
if (missing.length) issues.push(t(`.gitignore 시크릿 누락 ${missing.length}건`, `.gitignore missing secret patterns ${missing.length}`));
|
|
9539
9591
|
} catch {}
|
|
9540
9592
|
if (issues.length) {
|
|
9541
9593
|
const isTty = process.stdout && process.stdout.isTTY;
|
|
@@ -9543,31 +9595,31 @@ function handoff(root) {
|
|
|
9543
9595
|
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
9544
9596
|
const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
|
|
9545
9597
|
log('');
|
|
9546
|
-
log(red(`## 🔒 보안 요약 (1.9.76) — ${issues.length}건
|
|
9598
|
+
log(red(t(`## 🔒 보안 요약 (1.9.76) — ${issues.length}건 주의`, `## 🔒 Security summary — ${issues.length} to review`)));
|
|
9547
9599
|
for (const i of issues) log(dim(` ⚠ ${i}`));
|
|
9548
|
-
log(dim(` → 자동 수정: leerness audit --fix · 상세: leerness env check / leerness audit`));
|
|
9600
|
+
log(dim(t(` → 자동 수정: leerness audit --fix · 상세: leerness env check / leerness audit`, ` → auto-fix: leerness audit --fix · details: leerness env check / leerness audit`)));
|
|
9549
9601
|
// 1.9.80: critical 수준 (.gitignore에 .env 자체 누락) 시 자동 회복 옵션
|
|
9550
9602
|
const giText = exists(path.join(root, '.gitignore')) ? read(path.join(root, '.gitignore')) : '';
|
|
9551
9603
|
const giLines = giText.split('\n').map(l => l.trim());
|
|
9552
9604
|
const envInGitignore = giLines.includes('.env') || giLines.includes('/.env');
|
|
9553
9605
|
if (!envInGitignore) {
|
|
9554
9606
|
// .env 자체 누락 → 최우선 위험
|
|
9555
|
-
log(yel(` 🚨 CRITICAL (1.9.80): .env가 .gitignore에 없습니다! 시크릿 노출 위험 — 즉시 \`leerness audit --fix\`
|
|
9607
|
+
log(yel(t(` 🚨 CRITICAL (1.9.80): .env가 .gitignore에 없습니다! 시크릿 노출 위험 — 즉시 \`leerness audit --fix\` 권장.`, ` 🚨 CRITICAL: .env is not in .gitignore! secret-exposure risk — run \`leerness audit --fix\` now.`)));
|
|
9556
9608
|
// LEERNESS_AUTO_SECURITY_FIX=1 자동 실행 옵션
|
|
9557
9609
|
if (process.env.LEERNESS_AUTO_SECURITY_FIX === '1') {
|
|
9558
9610
|
try {
|
|
9559
9611
|
const r = cp.spawnSync(process.execPath, [__filename, 'audit', root, '--fix'],
|
|
9560
9612
|
{ encoding: 'utf8', timeout: 15000, env: { ...process.env, LEERNESS_INTERNAL: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' } });
|
|
9561
9613
|
if (r.status === 0) {
|
|
9562
|
-
log(dim(` ✓ 자동 회복 (LEERNESS_AUTO_SECURITY_FIX=1): audit --fix
|
|
9614
|
+
log(dim(t(` ✓ 자동 회복 (LEERNESS_AUTO_SECURITY_FIX=1): audit --fix 완료`, ` ✓ auto-recovered (LEERNESS_AUTO_SECURITY_FIX=1): audit --fix done`)));
|
|
9563
9615
|
} else {
|
|
9564
|
-
log(dim(` ⚠ 자동 회복 실패 (exit ${r.status}) — 수동 \`leerness audit --fix\`
|
|
9616
|
+
log(dim(t(` ⚠ 자동 회복 실패 (exit ${r.status}) — 수동 \`leerness audit --fix\` 권장`, ` ⚠ auto-recovery failed (exit ${r.status}) — run \`leerness audit --fix\` manually`)));
|
|
9565
9617
|
}
|
|
9566
9618
|
} catch (e) {
|
|
9567
|
-
log(dim(` ⚠ 자동 회복 예외: ${e.message}`));
|
|
9619
|
+
log(dim(t(` ⚠ 자동 회복 예외: ${e.message}`, ` ⚠ auto-recovery error: ${e.message}`)));
|
|
9568
9620
|
}
|
|
9569
9621
|
} else {
|
|
9570
|
-
log(dim(` 💡 자동 실행 옵션: LEERNESS_AUTO_SECURITY_FIX=1 leerness handoff .`));
|
|
9622
|
+
log(dim(t(` 💡 자동 실행 옵션: LEERNESS_AUTO_SECURITY_FIX=1 leerness handoff .`, ` 💡 auto-fix option: LEERNESS_AUTO_SECURITY_FIX=1 leerness handoff .`)));
|
|
9571
9623
|
}
|
|
9572
9624
|
}
|
|
9573
9625
|
log('');
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -6239,9 +6239,67 @@ total++;
|
|
|
6239
6239
|
const docKo = out(cp.spawnSync(process.execPath, [CLI, 'doctor'], { encoding: 'utf8', timeout: 20000, cwd: d }));
|
|
6240
6240
|
const doctorOk = /install\/environment diagnosis/.test(docEn) && !H.test(docEn) && /설치\/환경 진단/.test(docKo);
|
|
6241
6241
|
fs.rmSync(d, { recursive: true, force: true });
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6242
|
+
// ⑧ (1.29.1) handoff 보안 요약 섹션: .env + 미흡한 .gitignore → en 영어(섹션 라인 한글 0) + ko 기본 한글.
|
|
6243
|
+
// 소스가드만으로는 못 잡는 회귀를 e2e 로 보강: 보안 요약 블록은 headline 의 t() 스코프 밖이라,
|
|
6244
|
+
// 로컬 t() 누락 시 ReferenceError 가 try 에 삼켜져 섹션 전체가 (양 언어 모두) 사라진다. 이 가드가 그걸 잡는다.
|
|
6245
|
+
const dh = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-i18n-ho-'));
|
|
6246
|
+
cp.spawnSync(process.execPath, [CLI, 'init', dh, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
6247
|
+
fs.writeFileSync(path.join(dh, '.env'), 'API_KEY=sk-test-abc123def456ghi789jkl012mno345\n');
|
|
6248
|
+
fs.writeFileSync(path.join(dh, '.gitignore'), 'node_modules/\n');
|
|
6249
|
+
const hoEn = out(cp.spawnSync(process.execPath, [CLI, 'handoff', dh, '--language', 'en'], { encoding: 'utf8', timeout: 25000 }));
|
|
6250
|
+
const hoKo = out(cp.spawnSync(process.execPath, [CLI, 'handoff', dh], { encoding: 'utf8', timeout: 25000 }));
|
|
6251
|
+
const enSecLines = hoEn.split('\n').filter(l => /Security summary|auto-fix:|CRITICAL|auto-fix option|recover|missing secret/i.test(l));
|
|
6252
|
+
const hoEnOk = /Security summary/.test(hoEn) && enSecLines.length >= 2 && !enSecLines.some(l => H.test(l));
|
|
6253
|
+
const hoKoOk = /보안 요약/.test(hoKo);
|
|
6254
|
+
fs.rmSync(dh, { recursive: true, force: true });
|
|
6255
|
+
// ⑨ (1.29.2) handoff env-detect 블록: 환경 스냅샷 변동 시 → en 영어(블록 라인 한글 0) + ko 기본 한글.
|
|
6256
|
+
// 블록은 첫 핸드오프 후 .harness/environment.json 변동이 있어야 발동 → 스냅샷의 node.version 을 인위 변경해 강제.
|
|
6257
|
+
// (1.29.1 과 같은 블록-스코프 t 함정 가드 — env-detect 도 headline t() 스코프 밖이라 로컬 t() 필요.)
|
|
6258
|
+
const de = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-i18n-env-'));
|
|
6259
|
+
cp.spawnSync(process.execPath, [CLI, 'init', de, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
6260
|
+
const snap = path.join(de, '.harness', 'environment.json');
|
|
6261
|
+
const forceEnvChange = () => { try { const s = JSON.parse(fs.readFileSync(snap, 'utf8')); if (s.node) s.node.version = 'v0.0.0-test'; fs.writeFileSync(snap, JSON.stringify(s, null, 2) + '\n'); } catch {} };
|
|
6262
|
+
cp.spawnSync(process.execPath, [CLI, 'handoff', de], { encoding: 'utf8', timeout: 25000 }); // 첫 캡처(silent)
|
|
6263
|
+
forceEnvChange();
|
|
6264
|
+
const edEn = out(cp.spawnSync(process.execPath, [CLI, 'handoff', de, '--language', 'en'], { encoding: 'utf8', timeout: 25000 }));
|
|
6265
|
+
forceEnvChange(); // en 실행이 스냅샷 갱신 → ko 위해 재변경
|
|
6266
|
+
const edKo = out(cp.spawnSync(process.execPath, [CLI, 'handoff', de], { encoding: 'utf8', timeout: 25000 }));
|
|
6267
|
+
const edEnLines = edEn.split('\n').filter(l => /Runtime environment|env detect|change\(s\) detected/i.test(l));
|
|
6268
|
+
const edEnOk = /Runtime environment/.test(edEn) && edEnLines.length >= 1 && !edEnLines.some(l => H.test(l));
|
|
6269
|
+
const edKoOk = /실행 환경/.test(edKo);
|
|
6270
|
+
fs.rmSync(de, { recursive: true, force: true });
|
|
6271
|
+
// ⑩ (1.29.3) handoff shell-guard 블록: 셸 실패 기록 + 환경 스냅샷 변동 → en 영어(블록 라인 한글 0) + ko 기본 한글.
|
|
6272
|
+
// 블록은 hasFailures(.harness/shell-failures.json) 또는 hasDrift(스냅샷 변동) 시 발동. 둘 다 인위 구성.
|
|
6273
|
+
const ds = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-i18n-sh-'));
|
|
6274
|
+
cp.spawnSync(process.execPath, [CLI, 'init', ds, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
6275
|
+
const ssnap = path.join(ds, '.harness', 'environment.json');
|
|
6276
|
+
const sfail = path.join(ds, '.harness', 'shell-failures.json');
|
|
6277
|
+
const seedSh = () => {
|
|
6278
|
+
try { const s = JSON.parse(fs.readFileSync(ssnap, 'utf8')); if (s.node) s.node.version = 'v0.0.0-test'; fs.writeFileSync(ssnap, JSON.stringify(s, null, 2) + '\n'); } catch {}
|
|
6279
|
+
fs.writeFileSync(sfail, JSON.stringify({ failures: [{ cmd: 'ls && pwd', exitCode: 1, shell: 'powershell-5.1', issues: ['ps5-chain'] }] }, null, 2) + '\n');
|
|
6280
|
+
};
|
|
6281
|
+
cp.spawnSync(process.execPath, [CLI, 'handoff', ds], { encoding: 'utf8', timeout: 25000 }); // 첫 캡처(silent)
|
|
6282
|
+
seedSh();
|
|
6283
|
+
const shEn = out(cp.spawnSync(process.execPath, [CLI, 'handoff', ds, '--language', 'en'], { encoding: 'utf8', timeout: 25000 }));
|
|
6284
|
+
seedSh(); // en 실행이 스냅샷 갱신 → ko 위해 재구성
|
|
6285
|
+
const shKo = out(cp.spawnSync(process.execPath, [CLI, 'handoff', ds], { encoding: 'utf8', timeout: 25000 }));
|
|
6286
|
+
const shEnLines = shEn.split('\n').filter(l => /shell guard|shell failure|review past shell|shell-guard|check before running/i.test(l));
|
|
6287
|
+
const shEnOk = /Terminal shell guard/.test(shEn) && shEnLines.length >= 2 && !shEnLines.some(l => H.test(l));
|
|
6288
|
+
const shKoOk = /셸 가드/.test(shKo);
|
|
6289
|
+
fs.rmSync(ds, { recursive: true, force: true });
|
|
6290
|
+
// ⑪ (1.29.4) handoff CLI 에이전트 슬래시 블록: 외부 에이전트 env flag 활성 시 → en 영어(블록 라인 한글 0) + ko 기본 한글.
|
|
6291
|
+
const da = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-i18n-ag-'));
|
|
6292
|
+
cp.spawnSync(process.execPath, [CLI, 'init', da, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
6293
|
+
const agEnv = { ...process.env, LEERNESS_ENABLE_CODEX: '1', LEERNESS_ENABLE_CLAUDE: '1' };
|
|
6294
|
+
const agEn = out(cp.spawnSync(process.execPath, [CLI, 'handoff', da, '--language', 'en'], { encoding: 'utf8', timeout: 25000, env: agEnv }));
|
|
6295
|
+
const agKo = out(cp.spawnSync(process.execPath, [CLI, 'handoff', da], { encoding: 'utf8', timeout: 25000, env: agEnv }));
|
|
6296
|
+
const agEnLines = agEn.split('\n').filter(l => /agent slash|active agent|slash-commands|full list/i.test(l));
|
|
6297
|
+
const agEnOk = /CLI agent slash commands/.test(agEn) && agEnLines.length >= 2 && !agEnLines.some(l => H.test(l));
|
|
6298
|
+
const agKoOk = /에이전트 슬래시/.test(agKo);
|
|
6299
|
+
fs.rmSync(da, { recursive: true, force: true });
|
|
6300
|
+
ok = lensKoOk && lensEnOk && noLeak && stOk && healthOk && driftOk && doctorOk && hoEnOk && hoKoOk && edEnOk && edKoOk && shEnOk && shKoOk && agEnOk && agKoOk;
|
|
6301
|
+
} catch {}
|
|
6302
|
+
console.log(ok ? '✓ B(1.25.1/1.25.2/1.27.2/1.28.2/1.29.1/1.29.2/1.29.3/1.29.4) i18n 행위: --language en 런타임 영어(lens/health/drift/doctor/handoff보안요약/env-detect/shell-guard/agent-slash) + ko 기본 보존 + --language positional 무누출 + status 에러 en/ko (UR-0010)' : '✗ i18n 행위 회귀 가드 실패');
|
|
6245
6303
|
if (!ok) failed++;
|
|
6246
6304
|
}
|
|
6247
6305
|
|