leerness 1.21.0 → 1.22.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 +48 -0
- package/README.md +4 -4
- package/bin/leerness.js +67 -51
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.0 — 2026-06-15 — 🛡️ [안정화/Stable] 확장된 영어 지원(헤드라인 + verify-claim) 안정 minor
|
|
4
|
+
|
|
5
|
+
**🛡️ 안정화(Stable) minor — 영어 CLI 커버리지를 핵심 검증 흐름까지 확장해 npm 공개.** 직전 minor(1.21.0) 이후 누적된 패치 2건(1.21.1~1.21.2)을 검증·통합해 배포. R-0011 정책의 13번째 stable minor. 영어 opt-in 표면이 첫 화면(1.21.0)에서 **handoff 헤드라인 + verify-claim 플래그십**까지 확장 — 한국어 우선 기본은 그대로.
|
|
6
|
+
|
|
7
|
+
### 이번 minor 통합 (1.21.1~1.21.2)
|
|
8
|
+
- **🌐 handoff 헤드라인 항목 라벨 영어화 (Phase 2)**: 매 세션 가장 많이 보는 출력 — `--language en` 시 `📊 Headline: drift healthy · security OK · N skills · health · mem …` 완전 영어. 보안/시크릿/미답요청/비정상종료/플랫폼제약 등 전 항목.
|
|
9
|
+
- **🌐 verify-claim 출력 영어화 (Phase 3, 플래그십)**: 완료 검증 시 보는 핵심 출력 ~30 문자열 영어화 — File check / Test count / Summary / implementation substance / test-impl link / git diff cross-check / optimism+honesty / final verdict.
|
|
10
|
+
- **한국어 우선 기본 보존**: 영어는 명시 opt-in(`--language en`/`LEERNESS_LANG=en`/en init 프로젝트). 플래그 없으면 한국어 — e2e 무회귀로 검증.
|
|
11
|
+
|
|
12
|
+
### 잔여 (UR-0010 Phase 4+, 백로그)
|
|
13
|
+
- session close 마감 보고(~117 문자열) + help + status — 단계적 확대.
|
|
14
|
+
|
|
15
|
+
### 검증 (회귀 0)
|
|
16
|
+
- **selftest 236→238** · **E2E 365/365** · en/ko 헤드라인·verify-claim 행위 + 게시본 재실증.
|
|
17
|
+
|
|
18
|
+
### 안정화 표시 (R-0006)
|
|
19
|
+
CHANGELOG [안정화/Stable] · git tag (Stable) · GitHub release (`--latest`) · npm dist-tag `stable` 시도.
|
|
20
|
+
|
|
21
|
+
## 1.21.2 — 2026-06-15 — CLI 영어화 Phase 3: verify-claim(플래그십) 출력 영어화 (UR-0010)
|
|
22
|
+
|
|
23
|
+
**🌐 플래그십 명령 출력을 영어로.** 사용자가 "완료됐나?"를 확인할 때 보는 `verify-claim` 의 human 출력 전체를 영어 opt-in 으로 — 온보딩 다음으로 영향 큰 표면. 한국어 기본 유지(영어 명시 opt-in).
|
|
24
|
+
|
|
25
|
+
### 변경 (UR-0010 Phase 3)
|
|
26
|
+
- **verify-claim 출력 영어화**: human 렌더(–json 제외) 시작에서 `_uiLang(root)` 1회 해석 후 전 출력을 `t(ko,en)` 분기 — `📂 파일 검증`→`File check`, `🧪 테스트 카운트`→`Test count`, `🚦 실행`→`run`, `종합`→`Summary`, `파일 모두 존재`→`all files exist`, `구현 실체`→`implementation substance`, `테스트-구현 연결`→`test-impl link`, `git diff 교차검증`→`git diff cross-check`, `낙관적 표시+정직성`→`optimism+honesty`, evidence 완전성/외과적 변경/한계/최종 판정까지 ~30 문자열. 품질 렌즈 advisory 헤더도 영어.
|
|
27
|
+
- **한국어 기본 무회귀**: e2e 는 `--language` 없이 실행되며 init 이 이 환경에서 manifest.language=`ko` 로 저장(검증함) → verify-claim 기본 출력 한국어 유지. ko 원문은 `t()` 의 ko 인자로 소스에 보존.
|
|
28
|
+
|
|
29
|
+
### 잔여 (UR-0010 Phase 4+, 백로그)
|
|
30
|
+
- session close 마감 보고 + help + status + 그 외 명령 본문 영어화 — 단계적 확대.
|
|
31
|
+
|
|
32
|
+
### 검증 (회귀 0)
|
|
33
|
+
- **selftest 237→238** (verify-claim t() 경유 + 한국어 보존 소스가드) · 행위(en: `## Summary / all files exist / evidence claim matches`, ko 기본: `## 종합 / 파일 모두 존재`) · **E2E 365/365**.
|
|
34
|
+
- patch(1.21.2) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
35
|
+
|
|
36
|
+
## 1.21.1 — 2026-06-15 — CLI 영어화 Phase 2: handoff 헤드라인 항목 라벨 영어화 (UR-0010)
|
|
37
|
+
|
|
38
|
+
**🌐 매 세션 가장 많이 보는 출력(handoff 헤드라인)을 영어로 완성.** Phase 1(배너 + 헤드라인 프리픽스)에 이어, 헤드라인의 **항목 라벨 전체**를 영어 opt-in 으로. 비한국어 사용자가 세션마다 보던 한국어 토막들(보안 OK·미답 요청·비정상종료·플랫폼 제약 등)이 사라짐.
|
|
39
|
+
|
|
40
|
+
### 변경 (UR-0010 Phase 2)
|
|
41
|
+
- **handoff 헤드라인 항목 라벨 영어화**: 블록 1회 `_uiLang(root)` 해석 후 각 항목을 `t(ko,en)` 분기 — `🔒 보안 OK`→`security OK`, `🚨 시크릿 N건`→`N secret(s)`, `🚨 .env 미무시`→`.env not ignored`, `🔌 MCP N회`→`MCP Nx`, `📒 skill query N회`, `🪄 slash 24h N회`, `📥 미답 요청 N건`→`N unanswered request(s)`, `📥 요청 N (tracked)`, `📥 자동완료가능 N건`→`N auto-completable`, `🔌 비정상종료`→`abnormal-exit`, `🚦 N 플랫폼 제약`→`N platform constraint(s)`, `R N남음`→`N left`. **한국어 기본 유지**(영어 opt-in).
|
|
42
|
+
- 행위 확인: en → `📊 Headline: drift healthy · 🔒 security OK · 📚 N skills · ⚕ health: ✓ · 🧠 mem …` 완전 영어 / 플래그 없음 → `📊 헤드라인`(한국어).
|
|
43
|
+
|
|
44
|
+
### 잔여 (UR-0010 Phase 3+, 백로그)
|
|
45
|
+
- session close 마감 보고 + verify-claim 출력 + help + status 등 명령 본문 영어화 — 단계적 확대.
|
|
46
|
+
|
|
47
|
+
### 검증 (회귀 0)
|
|
48
|
+
- **selftest 236→237** (헤드라인 t() 경유 소스가드) · 행위(en 헤드라인 완전 영어 + ko 기본) · **E2E 365/365**.
|
|
49
|
+
- patch(1.21.1) — npm 미배포(R-0011, GitHub/CHANGELOG 누적).
|
|
50
|
+
|
|
3
51
|
## 1.21.0 — 2026-06-15 — 🛡️ [안정화/Stable] 영어 온보딩 첫걸음 + 검증 강화 안정 minor
|
|
4
52
|
|
|
5
53
|
**🛡️ 안정화(Stable) minor — 비한국어 사용자에게 영어 온보딩을 실제로 전달.** 직전 minor(1.20.0) 이후 누적된 패치 2건(1.20.1~1.20.2)을 검증·통합해 npm 공개. R-0011 정책의 12번째 stable minor. 핵심: **영어 첫 화면(opt-in)** 이 npm 사용자에게 닿고, 검증 플래그십의 정적 우회가 닫힘.
|
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.22.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.22.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.21.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.22.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.22.0: 2026-06-15
|
|
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.22.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') 시 호스트 프로세스 오염.
|
|
@@ -3610,7 +3610,7 @@ function _selfTestCases() {
|
|
|
3610
3610
|
const src = read(__filename);
|
|
3611
3611
|
const authPass = src.includes('userAuthorized: true, timeout: 5 * 60 * 1000, kind: ' + "'verify_claim_test'");
|
|
3612
3612
|
const skipOnBlock = src.includes('if (r.blocked) {') && src.includes('테스트 명령 차단') && src.includes('불일치 판정 아님');
|
|
3613
|
-
const label = src.includes('` - ${runResult.cmd ' + "|| 'npm test'}
|
|
3613
|
+
const label = src.includes('` - ${runResult.cmd ' + "|| 'npm test'} ${t('실행', 'run')}:"); // P3: 하드코딩 제거 + 1.21.2 영어화 t()
|
|
3614
3614
|
return authPass && skipOnBlock && label;
|
|
3615
3615
|
} },
|
|
3616
3616
|
{ name: '재실증 P2 (1.18.1): task update id 뒤 non-path positional(status) 거부 + path-like 허용 (소스 가드)', run: () => {
|
|
@@ -3737,6 +3737,18 @@ function _selfTestCases() {
|
|
|
3737
3737
|
const src = read(__filename);
|
|
3738
3738
|
return src.includes("const L = _uiLang(arg('--path', process.cwd()));") && src.includes("_uiLang(root) === 'en' ? '📊 Headline'");
|
|
3739
3739
|
} },
|
|
3740
|
+
{ name: 'CLI 영어화 Phase 2 (1.21.1, UR-0010): handoff 헤드라인 항목 라벨 t() 경유 (소스 가드)', run: () => {
|
|
3741
|
+
const src = read(__filename);
|
|
3742
|
+
return src.includes('const _L = _uiLang(root); const t = (ko, en) => (_L === ' + "'en' ? en : ko);")
|
|
3743
|
+
&& src.includes("security OK") && src.includes("unanswered request(s)") && src.includes("abnormal-exit") && src.includes("platform constraint(s)");
|
|
3744
|
+
} },
|
|
3745
|
+
{ name: 'CLI 영어화 Phase 3 (1.21.2, UR-0010): verify-claim 출력 t() 경유 + 한국어 기본 보존 (소스 가드)', run: () => {
|
|
3746
|
+
const src = read(__filename);
|
|
3747
|
+
const en = src.includes('## File check') && src.includes('## Test count') && src.includes('## Summary')
|
|
3748
|
+
&& src.includes('implementation substance (done default)') && src.includes('evidence claim matches actual files');
|
|
3749
|
+
const koPreserved = src.includes('## 종합') && src.includes('구현 실체 (done 기본)'); // ko 원문이 t() ko 인자로 남아 e2e(ko) 무회귀
|
|
3750
|
+
return en && koPreserved;
|
|
3751
|
+
} },
|
|
3740
3752
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3741
3753
|
];
|
|
3742
3754
|
}
|
|
@@ -8278,6 +8290,8 @@ function handoff(root) {
|
|
|
8278
8290
|
if (!has('--no-headline') && !has('--compact') && !has('--quiet')) {
|
|
8279
8291
|
try {
|
|
8280
8292
|
const parts = [];
|
|
8293
|
+
// 1.20.3 (UR-0010 Phase 2): 헤드라인 항목 라벨 UI 언어 적용 (영어 opt-in, 한국어 기본). 블록 1회 해석.
|
|
8294
|
+
const _L = _uiLang(root); const t = (ko, en) => (_L === 'en' ? en : ko);
|
|
8281
8295
|
// 1) drift level (가벼운 check)
|
|
8282
8296
|
try {
|
|
8283
8297
|
const r = cp.spawnSync(process.execPath, [__filename, 'drift', 'check', root, '--json'],
|
|
@@ -8291,15 +8305,15 @@ function handoff(root) {
|
|
|
8291
8305
|
try {
|
|
8292
8306
|
const sec = _collectSecretFindings(root);
|
|
8293
8307
|
if (sec.committed.length) {
|
|
8294
|
-
parts.push(`🚨 시크릿 ${sec.committed.length}
|
|
8308
|
+
parts.push(t(`🚨 시크릿 ${sec.committed.length}건`, `🚨 ${sec.committed.length} secret(s)`));
|
|
8295
8309
|
} else {
|
|
8296
8310
|
const envPath = path.join(root, '.env');
|
|
8297
8311
|
if (exists(envPath)) {
|
|
8298
8312
|
const giText = exists(path.join(root, '.gitignore')) ? read(path.join(root, '.gitignore')) : '';
|
|
8299
8313
|
const giLines = giText.split('\n').map(l => l.trim());
|
|
8300
|
-
parts.push((giLines.includes('.env') || giLines.includes('/.env')) ? '🔒 보안 OK' : '🚨 .env 미무시');
|
|
8314
|
+
parts.push((giLines.includes('.env') || giLines.includes('/.env')) ? t('🔒 보안 OK', '🔒 security OK') : t('🚨 .env 미무시', '🚨 .env not ignored'));
|
|
8301
8315
|
} else {
|
|
8302
|
-
parts.push('🔒 보안 OK');
|
|
8316
|
+
parts.push(t('🔒 보안 OK', '🔒 security OK'));
|
|
8303
8317
|
}
|
|
8304
8318
|
}
|
|
8305
8319
|
} catch {}
|
|
@@ -8307,7 +8321,7 @@ function handoff(root) {
|
|
|
8307
8321
|
try {
|
|
8308
8322
|
const stats = _readUsageStats(root);
|
|
8309
8323
|
const mcpTotal = stats.mcp?.tools ? Object.values(stats.mcp.tools).reduce((s, n) => s + n, 0) : 0;
|
|
8310
|
-
if (mcpTotal > 0) parts.push(`🔌 MCP ${mcpTotal}
|
|
8324
|
+
if (mcpTotal > 0) parts.push(`🔌 MCP ${mcpTotal}${t('회', 'x')}`);
|
|
8311
8325
|
} catch {}
|
|
8312
8326
|
// 4) skill match history 누적
|
|
8313
8327
|
try {
|
|
@@ -8315,7 +8329,7 @@ function handoff(root) {
|
|
|
8315
8329
|
if (exists(histPath)) {
|
|
8316
8330
|
const txt = read(histPath);
|
|
8317
8331
|
const cnt = (txt.match(/^## [\d-]+ [\d:]+ — query/gm) || []).length;
|
|
8318
|
-
if (cnt > 0) parts.push(`📒 skill query ${cnt}
|
|
8332
|
+
if (cnt > 0) parts.push(`📒 skill query ${cnt}${t('회', 'x')}`);
|
|
8319
8333
|
}
|
|
8320
8334
|
} catch {}
|
|
8321
8335
|
// 5) 설치된 skill 수
|
|
@@ -8376,7 +8390,7 @@ function handoff(root) {
|
|
|
8376
8390
|
}
|
|
8377
8391
|
} catch {}
|
|
8378
8392
|
}
|
|
8379
|
-
if (slashCount > 0) parts.push(`🪄 slash 24h ${slashCount}
|
|
8393
|
+
if (slashCount > 0) parts.push(`🪄 slash 24h ${slashCount}${t('회', 'x')}`);
|
|
8380
8394
|
}
|
|
8381
8395
|
} catch {}
|
|
8382
8396
|
// 10) 1.9.192: 공식 organization skill catalog 캐시 매칭 (C축 보강 — 사용자 명시)
|
|
@@ -8404,7 +8418,7 @@ function handoff(root) {
|
|
|
8404
8418
|
if (rh.roundCount >= 5) {
|
|
8405
8419
|
let label = `🔄 R${rh.roundCount}`;
|
|
8406
8420
|
if (rh.nextMilestone != null && rh.roundsToNextMilestone <= 20) {
|
|
8407
|
-
label += ` → R${rh.nextMilestone} (${rh.roundsToNextMilestone}R 남음)`;
|
|
8421
|
+
label += ` → R${rh.nextMilestone} (${rh.roundsToNextMilestone}R ${t('남음', 'left')})`;
|
|
8408
8422
|
}
|
|
8409
8423
|
parts.push(label);
|
|
8410
8424
|
// 1.9.230: 임박 마일스톤 ETA 별도 노출 (다음 마일스톤이 매우 가까울 때만)
|
|
@@ -8449,11 +8463,11 @@ function handoff(root) {
|
|
|
8449
8463
|
let detected = { candidates: [] };
|
|
8450
8464
|
try { detected = _detectDeliveredRequests(root); } catch {}
|
|
8451
8465
|
if (detected.candidates && detected.candidates.length > 0) {
|
|
8452
|
-
parts.push(`📥 자동완료가능 ${detected.candidates.length}건 (1.9.223)`);
|
|
8466
|
+
parts.push(t(`📥 자동완료가능 ${detected.candidates.length}건 (1.9.223)`, `📥 ${detected.candidates.length} auto-completable`));
|
|
8453
8467
|
} else if (audit.missing && audit.missing.length > 0) {
|
|
8454
|
-
parts.push(`📥 미답 요청 ${audit.missing.length}
|
|
8468
|
+
parts.push(t(`📥 미답 요청 ${audit.missing.length}건`, `📥 ${audit.missing.length} unanswered request(s)`));
|
|
8455
8469
|
} else if (audit.open > 0) {
|
|
8456
|
-
parts.push(`📥 요청 ${audit.open} (tracked)`);
|
|
8470
|
+
parts.push(t(`📥 요청 ${audit.open} (tracked)`, `📥 ${audit.open} request(s) (tracked)`));
|
|
8457
8471
|
}
|
|
8458
8472
|
} catch {}
|
|
8459
8473
|
// 14) 1.9.209: pre-wake-audit 최근 보고서 (사용자 명시) — 깨어남 직후 자동 노출
|
|
@@ -8472,7 +8486,7 @@ function handoff(root) {
|
|
|
8472
8486
|
try {
|
|
8473
8487
|
const ad = _detectAbnormalShutdown(root);
|
|
8474
8488
|
if (ad.abnormalShutdown) {
|
|
8475
|
-
parts.push(`🔌 비정상종료 ${ad.severity} (${ad.signals.length}신호)`);
|
|
8489
|
+
parts.push(t(`🔌 비정상종료 ${ad.severity} (${ad.signals.length}신호)`, `🔌 abnormal-exit ${ad.severity} (${ad.signals.length} signals)`));
|
|
8476
8490
|
}
|
|
8477
8491
|
} catch {}
|
|
8478
8492
|
// 15) 1.9.215: 현재 활성 task에서 constraints/intent 자동 분석 (1.9.208/213 통합)
|
|
@@ -8492,7 +8506,7 @@ function handoff(root) {
|
|
|
8492
8506
|
try {
|
|
8493
8507
|
const cc = _checkRequestConstraints(root, req);
|
|
8494
8508
|
if (cc.matched && cc.matched.length > 0) {
|
|
8495
|
-
parts.push(`🚦 ${cc.matched.length} 플랫폼
|
|
8509
|
+
parts.push(t(`🚦 ${cc.matched.length} 플랫폼 제약`, `🚦 ${cc.matched.length} platform constraint(s)`));
|
|
8496
8510
|
}
|
|
8497
8511
|
} catch {}
|
|
8498
8512
|
// 1.9.213 intent classify
|
|
@@ -10195,39 +10209,41 @@ function verifyClaimCmd(root, taskId) {
|
|
|
10195
10209
|
return;
|
|
10196
10210
|
}
|
|
10197
10211
|
|
|
10212
|
+
// 1.21.2 (UR-0010 Phase 3): verify-claim 출력 UI 언어 적용 (영어 opt-in, 한국어 기본). human 렌더 한정(--json 은 위에서 return).
|
|
10213
|
+
const _L = _uiLang(root); const t = (ko, en) => (_L === 'en' ? en : ko);
|
|
10198
10214
|
log(`# verify-claim ${taskId} (${path.basename(root)})`);
|
|
10199
10215
|
log(`Request: ${row.request}`);
|
|
10200
10216
|
log(`Status: ${row.status} · Updated: ${row.updated}`);
|
|
10201
10217
|
log(`Evidence: ${evidence.slice(0, 200)}${evidence.length > 200 ? '…' : ''}`);
|
|
10202
10218
|
log('');
|
|
10203
|
-
log(`## 📂 파일 검증 (${files.length}건 주장)`);
|
|
10204
|
-
if (!files.length) log(' (evidence에서 파일 경로를 추출하지 못함)');
|
|
10219
|
+
log(t(`## 📂 파일 검증 (${files.length}건 주장)`, `## 📂 File check (${files.length} claimed)`));
|
|
10220
|
+
if (!files.length) log(t(' (evidence에서 파일 경로를 추출하지 못함)', ' (no file paths extracted from evidence)'));
|
|
10205
10221
|
else {
|
|
10206
|
-
for (const c of fileChecks) log(` ${c.exists ? '✓' : '✗'} ${c.file}${c.exists ? '' : ' ← 누락'}`);
|
|
10222
|
+
for (const c of fileChecks) log(` ${c.exists ? '✓' : '✗'} ${c.file}${c.exists ? '' : t(' ← 누락', ' ← missing')}`);
|
|
10207
10223
|
}
|
|
10208
10224
|
log('');
|
|
10209
|
-
log(`## 🧪 테스트
|
|
10210
|
-
if (declaredPass) log(` 주장 (pass): ${declaredPass.num}/${declaredPass.denom}`);
|
|
10211
|
-
if (declaredTestCount) log(` 주장 (개수): ${declaredTestCount}
|
|
10212
|
-
if (actualTestCount != null) log(` 실측: ${actualTestCount}개 테스트 호출 (${_vcTests.length ? '주장된 테스트 파일' : '관례 탐색: 루트/tests·test_*.py·*.test.*'})`);
|
|
10213
|
-
else log(` 실측: 테스트 파일 못 찾음 — 카운트 검증 미수행 (pass 아님)`);
|
|
10225
|
+
log(t(`## 🧪 테스트 카운트`, `## 🧪 Test count`));
|
|
10226
|
+
if (declaredPass) log(t(` 주장 (pass): ${declaredPass.num}/${declaredPass.denom}`, ` claimed (pass): ${declaredPass.num}/${declaredPass.denom}`));
|
|
10227
|
+
if (declaredTestCount) log(t(` 주장 (개수): ${declaredTestCount}개`, ` claimed (count): ${declaredTestCount}`));
|
|
10228
|
+
if (actualTestCount != null) log(t(` 실측: ${actualTestCount}개 테스트 호출 (${_vcTests.length ? '주장된 테스트 파일' : '관례 탐색: 루트/tests·test_*.py·*.test.*'})`, ` measured: ${actualTestCount} test call(s) (${_vcTests.length ? 'claimed test files' : 'convention scan: root/tests·test_*.py·*.test.*'})`));
|
|
10229
|
+
else log(t(` 실측: 테스트 파일 못 찾음 — 카운트 검증 미수행 (pass 아님)`, ` measured: no test file found — count not verified (not a pass)`));
|
|
10214
10230
|
|
|
10215
10231
|
// 1.9.19: --run-tests 결과
|
|
10216
10232
|
let runTestsOk = true;
|
|
10217
10233
|
let declaredPassMatchesActual = true;
|
|
10218
10234
|
if (runResult) {
|
|
10219
10235
|
log('');
|
|
10220
|
-
log(`## 🚦 ${runResult.cmd || '테스트'} 실행 (--run-tests)`);
|
|
10236
|
+
log(`## 🚦 ${runResult.cmd || t('테스트', 'test')} ${t('실행', 'run')} (--run-tests)`);
|
|
10221
10237
|
if (runResult.skipped) {
|
|
10222
10238
|
log(` ⚠ skipped: ${runResult.reason}`);
|
|
10223
10239
|
} else {
|
|
10224
10240
|
log(` exit: ${runResult.exitCode}`);
|
|
10225
|
-
if (runResult.parsed) log(` 실행 결과: ${runResult.parsed.num}/${runResult.parsed.denom} ${runResult.parsed.num === runResult.parsed.denom ? 'passed' : 'partial'}`);
|
|
10226
|
-
else log(` (pass/fail 비율을 stdout에서 파싱 못함)`);
|
|
10241
|
+
if (runResult.parsed) log(t(` 실행 결과: ${runResult.parsed.num}/${runResult.parsed.denom} ${runResult.parsed.num === runResult.parsed.denom ? 'passed' : 'partial'}`, ` result: ${runResult.parsed.num}/${runResult.parsed.denom} ${runResult.parsed.num === runResult.parsed.denom ? 'passed' : 'partial'}`));
|
|
10242
|
+
else log(t(` (pass/fail 비율을 stdout에서 파싱 못함)`, ` (could not parse pass/fail ratio from stdout)`));
|
|
10227
10243
|
runTestsOk = runResult.allPassed;
|
|
10228
10244
|
if (declaredPass && runResult.parsed) {
|
|
10229
10245
|
declaredPassMatchesActual = (runResult.parsed.num === declaredPass.num && runResult.parsed.denom === declaredPass.denom);
|
|
10230
|
-
log(` 주장 vs 실행: ${declaredPassMatchesActual ? '✓ 일치' : `⚠ 불일치 (주장 ${declaredPass.num}/${declaredPass.denom} ≠ 실행 ${runResult.parsed.num}/${runResult.parsed.denom})`}`);
|
|
10246
|
+
log(t(` 주장 vs 실행: ${declaredPassMatchesActual ? '✓ 일치' : `⚠ 불일치 (주장 ${declaredPass.num}/${declaredPass.denom} ≠ 실행 ${runResult.parsed.num}/${runResult.parsed.denom})`}`, ` claimed vs run: ${declaredPassMatchesActual ? '✓ match' : `⚠ mismatch (claimed ${declaredPass.num}/${declaredPass.denom} ≠ run ${runResult.parsed.num}/${runResult.parsed.denom})`}`));
|
|
10231
10247
|
}
|
|
10232
10248
|
}
|
|
10233
10249
|
}
|
|
@@ -10237,76 +10253,76 @@ function verifyClaimCmd(root, taskId) {
|
|
|
10237
10253
|
log('');
|
|
10238
10254
|
const allFilesOk = filesAllExist;
|
|
10239
10255
|
const testOk = declaredTestCount == null || actualTestCount == null || actualTestCount >= declaredTestCount;
|
|
10240
|
-
log(`##
|
|
10241
|
-
log(` - 파일 모두
|
|
10256
|
+
log(t(`## 종합`, `## Summary`));
|
|
10257
|
+
log(` - ${t('파일 모두 존재', 'all files exist')}: ${allFilesOk ? '✓ pass' : t('✗ FAIL (일부 누락)', '✗ FAIL (some missing)')}`);
|
|
10242
10258
|
// 1.17.4 (UR-0047): 측정 불가는 '통과' 가 아니라 '검증 미수행' — 이전엔 실측 0 인데 ✓ pass(실측≥주장) 모순 표기.
|
|
10243
|
-
log(` - 테스트
|
|
10259
|
+
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')}`);
|
|
10244
10260
|
if (runResult && !runResult.skipped) {
|
|
10245
|
-
log(` - ${runResult.cmd || 'npm test'}
|
|
10246
|
-
if (declaredPass) log(` - 주장과 실행 결과
|
|
10261
|
+
log(` - ${runResult.cmd || 'npm test'} ${t('실행', 'run')}: ${runTestsOk ? '✓ all passed' : '✗ FAIL'}`);
|
|
10262
|
+
if (declaredPass) log(` - ${t('주장과 실행 결과 일치', 'claimed matches run')}: ${declaredPassMatchesActual ? '✓ pass' : t('⚠ 다름', '⚠ differs')}`);
|
|
10247
10263
|
}
|
|
10248
10264
|
// 1.11.2 (UR-0175): optimism+정직성 — done 주장은 기본 게이팅(claimsChecked). 완화: --lenient.
|
|
10249
10265
|
if (claimsChecked) {
|
|
10250
|
-
if (strictOk) log(` - 낙관적 표시 + 정직성 (done 기본): ✓ pass (의심 없음)`);
|
|
10266
|
+
if (strictOk) log(t(` - 낙관적 표시 + 정직성 (done 기본): ✓ pass (의심 없음)`, ` - optimism + honesty (done default): ✓ pass (no suspicion)`));
|
|
10251
10267
|
else {
|
|
10252
|
-
log(` - 낙관적 표시 + 정직성 (done 기본 — 완화: --lenient): ⚠ FAIL (낙관 ${optimismSuspects.length} · 정직성 ${honestyFindings.length})`);
|
|
10253
|
-
for (const s of optimismSuspects) log(` · [${s.kind}] ${s.label}: evidence에 주장 있는데 코드에 호출 흔적
|
|
10254
|
-
for (const f of honestyFindings) log(` · [
|
|
10268
|
+
log(t(` - 낙관적 표시 + 정직성 (done 기본 — 완화: --lenient): ⚠ FAIL (낙관 ${optimismSuspects.length} · 정직성 ${honestyFindings.length})`, ` - optimism + honesty (done default — relax: --lenient): ⚠ FAIL (optimism ${optimismSuspects.length} · honesty ${honestyFindings.length})`));
|
|
10269
|
+
for (const s of optimismSuspects) log(t(` · [${s.kind}] ${s.label}: evidence에 주장 있는데 코드에 호출 흔적 없음`, ` · [${s.kind}] ${s.label}: claimed in evidence but no call trace in code`));
|
|
10270
|
+
for (const f of honestyFindings) log(` · [${t('정직성', 'honesty')}:${f.dim}] ${f.label}: ${f.detail}`);
|
|
10255
10271
|
}
|
|
10256
10272
|
}
|
|
10257
10273
|
// 1.9.302 (UR-0042) + 1.11.2 (UR-0175): git diff 시맨틱 교차검증 — 주장 파일이 실제 git 변경에 있는가. gitClaimOk/gitStrongMismatch 는 상단 공유(done 기본 게이팅, --lenient 완화).
|
|
10258
10274
|
if (gitChanged === null) {
|
|
10259
|
-
log(` - git diff
|
|
10275
|
+
log(` - ${t('git diff 교차검증', 'git diff cross-check')}: ${t('⊘ skip (git repo 아님 — 검증 불가)', '⊘ skip (not a git repo — cannot verify)')}`);
|
|
10260
10276
|
} else if (!gitApplicable) {
|
|
10261
|
-
log(` - git diff
|
|
10277
|
+
log(` - ${t('git diff 교차검증', 'git diff cross-check')}: ${t('⊘ skip (working tree 변경 0 또는 주장 파일 0 — 이미 커밋됐거나 해당 없음)', '⊘ skip (no working-tree changes or no claimed files — already committed or N/A)')}`);
|
|
10262
10278
|
} else {
|
|
10263
|
-
log(` - git diff
|
|
10264
|
-
if (gitStrongMismatch) log(` · 주장한 파일이 working tree/직전커밋 변경에 전무 — 변경이 더 오래전 커밋이거나, 실제로 변경 안 됐을 수 있음(허위완료 의심)${has('--strict-claims') ? ' → FAIL' : ' (advisory — 커밋 후 검증 시 정상일 수 있음)'}`);
|
|
10279
|
+
log(` - ${t('git diff 교차검증', 'git diff cross-check')}: ${gitStrongMismatch ? t('⚠ 불일치', '⚠ mismatch') : '✓'} ${t(`주장 ${files.length}개 중 실제 변경 ${claimedInGit.length}개`, `${claimedInGit.length} of ${files.length} claimed actually changed`)}${claimedNotInGit.length ? t(` · git 변경에 없음: ${claimedNotInGit.slice(0, 5).join(', ')}`, ` · not in git changes: ${claimedNotInGit.slice(0, 5).join(', ')}`) : ''}`);
|
|
10280
|
+
if (gitStrongMismatch) log(` · ${t('주장한 파일이 working tree/직전커밋 변경에 전무 — 변경이 더 오래전 커밋이거나, 실제로 변경 안 됐을 수 있음(허위완료 의심)', 'claimed files are absent from working-tree/last-commit changes — changed in an older commit, or not actually changed (false-done suspected)')}${has('--strict-claims') ? ' → FAIL' : t(' (advisory — 커밋 후 검증 시 정상일 수 있음)', ' (advisory — may be normal when verifying after commit)')}`);
|
|
10265
10281
|
// 1.13.2 (Karpathy 가이드라인 3 "외과적 변경", UR-0030): scope-creep 표면화 — git 변경됐으나 evidence/주장에 없는 파일.
|
|
10266
|
-
if (changedNotClaimed.length) log(` ·
|
|
10282
|
+
if (changedNotClaimed.length) log(` · ${t(`🔬 외과적 변경 점검: git 에 변경됐으나 evidence/주장에 없는 파일 ${changedNotClaimed.length}건: ${changedNotClaimed.slice(0, 5).join(', ')}${changedNotClaimed.length > 5 ? ' …' : ''} — 요청 범위 밖 변경(scope creep)인지 확인 (advisory)`, `🔬 surgical-change check: ${changedNotClaimed.length} file(s) changed in git but not in evidence/claim: ${changedNotClaimed.slice(0, 5).join(', ')}${changedNotClaimed.length > 5 ? ' …' : ''} — check for scope creep (advisory)`)}`);
|
|
10267
10283
|
}
|
|
10268
10284
|
// 1.9.309 (UR-0048): done 주장 evidence 완전성 — 기본 강제(상단 pre-compute). --lenient 로 opt-out.
|
|
10269
10285
|
if (mustHaveEvidence) {
|
|
10270
|
-
log(` - evidence 완전성 (done 기본 강제): ${evidenceQualityOk ? '✓ pass (파일+테스트 근거 있음)' : `✗ FAIL (누락: ${evq.missing.join(', ')})`}`);
|
|
10271
|
-
if (!evidenceQualityOk) log(` · done 주장은 수정 파일 경로 + 테스트명/개수 가 evidence 에 있어야 함 (테스트 통과만으로는 불충분). 완화: --lenient`);
|
|
10286
|
+
log(` - ${t('evidence 완전성 (done 기본 강제)', 'evidence completeness (done default)')}: ${evidenceQualityOk ? t('✓ pass (파일+테스트 근거 있음)', '✓ pass (file + test evidence present)') : `✗ FAIL ${t(`(누락: ${evq.missing.join(', ')})`, `(missing: ${evq.missing.join(', ')})`)}`}`);
|
|
10287
|
+
if (!evidenceQualityOk) log(` · ${t('done 주장은 수정 파일 경로 + 테스트명/개수 가 evidence 에 있어야 함 (테스트 통과만으로는 불충분). 완화: --lenient', 'a done claim needs changed-file paths + test name/count in evidence (passing tests alone is insufficient). relax: --lenient')}`);
|
|
10272
10288
|
}
|
|
10273
10289
|
// 1.17.3 (UR-0046): 구현 실체(스텁) + 테스트-구현 연결 — Attack C(주석뿐 구현+assert(true)) 차단.
|
|
10274
10290
|
if (stubFiles.length) {
|
|
10275
|
-
log(` - 구현 실체 (done 기본):
|
|
10291
|
+
log(` - ${t('구현 실체 (done 기본)', 'implementation substance (done default)')}: ${t(`✗ FAIL — 주장된 구현 파일이 주석/빈껍데기뿐: ${stubFiles.slice(0, 5).join(', ')} (비주석 코드 0줄 또는 빈 export 껍데기)`, `✗ FAIL — claimed impl files are comments/empty shells only: ${stubFiles.slice(0, 5).join(', ')} (0 non-comment code lines or empty export shell)`)}`);
|
|
10276
10292
|
} else if (claimsChecked && _vcImpl.length) {
|
|
10277
|
-
log(` - 구현 실체 (done 기본): ✓ pass (주장 구현 파일에 실코드 존재)`);
|
|
10293
|
+
log(` - ${t('구현 실체 (done 기본)', 'implementation substance (done default)')}: ${t('✓ pass (주장 구현 파일에 실코드 존재)', '✓ pass (real code present in claimed impl files)')}`);
|
|
10278
10294
|
}
|
|
10279
10295
|
if (testLinkOk === false) {
|
|
10280
|
-
log(` - 테스트-구현
|
|
10296
|
+
log(` - ${t('테스트-구현 연결', 'test-impl link')}: ${t(`⚠ 주장된 테스트(${_vcTests.slice(0, 3).join(', ')})가 구현 파일을 참조하지 않음 — 빈 테스트(assert(true)) 의심`, `⚠ claimed test(s) (${_vcTests.slice(0, 3).join(', ')}) do not reference the impl file — empty test (assert(true)) suspected`)}${has('--strict-claims') ? ' → FAIL' : t(' (advisory — --strict-claims 시 FAIL)', ' (advisory — FAIL under --strict-claims)')}`);
|
|
10281
10297
|
} else if (testLinkOk === true && claimsChecked) {
|
|
10282
|
-
log(` - 테스트-구현
|
|
10298
|
+
log(` - ${t('테스트-구현 연결', 'test-impl link')}: ${t('✓ pass (테스트가 구현을 참조)', '✓ pass (test references the impl)')}`);
|
|
10283
10299
|
}
|
|
10284
10300
|
const overallFail = !allFilesOk || !testOk || (runResult && !runResult.skipped && !runTestsOk) || (claimsChecked && !strictOk) || !evidenceQualityOk || !gitClaimOk || (claimsChecked && stubFiles.length > 0) || (has('--strict-claims') && testLinkOk === false);
|
|
10285
10301
|
// 1.9.287: 정직한 한계 고지 — 테스트 통과 ≠ 의미적 구현 정확성
|
|
10286
10302
|
if (claimsChecked || mustHaveEvidence) {
|
|
10287
10303
|
log('');
|
|
10288
|
-
log(` ℹ 한계: 테스트 통과는 "의미적 구현 정확성"을 보장하지 않음 — evidence 가 해당 주장(수정 파일/테스트)을 직접 링크해야
|
|
10304
|
+
log(t(` ℹ 한계: 테스트 통과는 "의미적 구현 정확성"을 보장하지 않음 — evidence 가 해당 주장(수정 파일/테스트)을 직접 링크해야 신뢰도↑.`, ` ℹ Limit: passing tests do not guarantee "semantic correctness" — evidence should directly link the claimed files/tests for higher confidence.`));
|
|
10289
10305
|
// 1.19.2 (UR-0003 렌즈 완전판 v2): 완료-검증 순간에 분야별 자기질문 advisory — 주장 파일 확장자 기반(결정적).
|
|
10290
10306
|
// 기계검증(파일/테스트/스텁)을 통과해도 "사람이 보기에 좋은가"는 별개 → AI 가 스스로 답하도록 권장(advisory, 게이트 아님).
|
|
10291
10307
|
const _lensDoms = _lensDomainsForFiles(files);
|
|
10292
10308
|
if (_lensDoms.length) {
|
|
10293
10309
|
const _lensCat = _effectiveLensCatalog(root); // 1.19.3: 프로젝트 커스텀 질문도 포함
|
|
10294
10310
|
log('');
|
|
10295
|
-
log(` 🧭 품질 렌즈 (완료 선언 전 자문 — advisory, 게이트 아님):`);
|
|
10311
|
+
log(t(` 🧭 품질 렌즈 (완료 선언 전 자문 — advisory, 게이트 아님):`, ` 🧭 Quality lens (self-ask before declaring done — advisory, not a gate):`));
|
|
10296
10312
|
for (const d of _lensDoms) {
|
|
10297
10313
|
const l = _lensCat[d];
|
|
10298
10314
|
if (l) log(` · ${d}(${l.title}): ${l.questions[0]}`);
|
|
10299
10315
|
}
|
|
10300
|
-
log(` → 전체
|
|
10316
|
+
log(` ${t('→ 전체 질문', '→ full questions')}: leerness lens ${_lensDoms[0]}`);
|
|
10301
10317
|
}
|
|
10302
10318
|
}
|
|
10303
10319
|
if (overallFail) {
|
|
10304
10320
|
log('');
|
|
10305
|
-
log(` ⚠ evidence 주장과 실제가 일치하지 않음 — task 상태 재검토
|
|
10321
|
+
log(t(` ⚠ evidence 주장과 실제가 일치하지 않음 — task 상태 재검토 권장`, ` ⚠ evidence claim does not match reality — review the task status`));
|
|
10306
10322
|
return process.exit(1);
|
|
10307
10323
|
}
|
|
10308
10324
|
log('');
|
|
10309
|
-
log(` ✓ evidence 주장이 실제 파일·테스트${runResult && !runResult.skipped ? '·실행 결과' : ''}와
|
|
10325
|
+
log(t(` ✓ evidence 주장이 실제 파일·테스트${runResult && !runResult.skipped ? '·실행 결과' : ''}와 일치`, ` ✓ evidence claim matches actual files·tests${runResult && !runResult.skipped ? '·run results' : ''}`));
|
|
10310
10326
|
}
|
|
10311
10327
|
|
|
10312
10328
|
// 1.9.22: orchestrate — Ollama 로컬 LLM으로 best-of-N 멀티 에이전트 시뮬
|