leerness 1.36.109 → 1.36.111
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/README.md +4 -4
- package/bin/leerness.js +194 -109
- package/lib/analyzers.js +68 -3
- package/package.json +1 -1
- package/scripts/e2e.js +148 -0
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ MIT
|
|
|
122
122
|
<!-- leerness:project-readme:start -->
|
|
123
123
|
## Leerness Project Harness
|
|
124
124
|
|
|
125
|
-
이 프로젝트는 Leerness v1.36.
|
|
125
|
+
이 프로젝트는 Leerness v1.36.111 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
126
126
|
|
|
127
127
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
128
128
|
|
|
@@ -176,7 +176,7 @@ leerness memory restore decision <date|title>
|
|
|
176
176
|
|
|
177
177
|
### MCP server (외부 AI 통합)
|
|
178
178
|
|
|
179
|
-
Leerness v1.36.
|
|
179
|
+
Leerness v1.36.111는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **89개 도구**를 노출:
|
|
180
180
|
|
|
181
181
|
```jsonc
|
|
182
182
|
// 카테고리별
|
|
@@ -197,7 +197,7 @@ Leerness v1.36.109는 stdio JSON-RPC MCP server를 내장합니다 — Claude Co
|
|
|
197
197
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
198
198
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) 회귀 테스트 갱신 → 4) 전체 e2e 스위트 통과 → 5) npm publish + git tag → 6) main push → 7) session close → 8) 다음 라운드 예약.
|
|
199
199
|
|
|
200
|
-
현재 누적: **v1.9.x → 1.36.
|
|
200
|
+
현재 누적: **v1.9.x → 1.36.111 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
|
|
201
201
|
|
|
202
202
|
### 성능 가이드
|
|
203
203
|
|
|
@@ -235,5 +235,5 @@ leerness release pack --close --auto-main-push
|
|
|
235
235
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
236
236
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
237
237
|
|
|
238
|
-
Last synced by Leerness v1.36.
|
|
238
|
+
Last synced by Leerness v1.36.111: 2026-08-10
|
|
239
239
|
<!-- leerness:project-readme:end -->
|
package/bin/leerness.js
CHANGED
|
@@ -34,7 +34,7 @@ const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE
|
|
|
34
34
|
const { tokenizeForRank: _tokenizeForRank, expandQuery: _expandQuery, scoreHits: _scoreHits, suggestTerms: _suggestTerms } = require('../lib/search-core'); // 1.36.23: memory search 랭킹 코어(순수·0-deps)
|
|
35
35
|
const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태 무결성 (audit/health/check 공유)
|
|
36
36
|
|
|
37
|
-
const VERSION = '1.36.
|
|
37
|
+
const VERSION = '1.36.111';
|
|
38
38
|
|
|
39
39
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
40
40
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -6430,7 +6430,7 @@ function _selfTestCases() {
|
|
|
6430
6430
|
return clar && shape && noDbUrl && reqOk && guards && uiOk && _p0013LibraryOk() && _p0088CurrentStatePreserved()
|
|
6431
6431
|
&& _p0101SurfaceOk() && _p0101SkillIdOk() && _p0101PathStrictOk() && _p0102HonestyOk()
|
|
6432
6432
|
&& _p0103FlagsOk() && _p0103JsonOk() && _p0103RootOk() && _p0104UsageOk()
|
|
6433
|
-
&& _p0014SecretBaselineOk() && _p0104ReminderOk() && _p0015ModeOk() && _p0097AsyncLockOk() && _p0109ClaimGateOk();
|
|
6433
|
+
&& _p0014SecretBaselineOk() && _p0104ReminderOk() && _p0015ModeOk() && _p0097AsyncLockOk() && _p0109ClaimGateOk() && _p0110HonestyOk();
|
|
6434
6434
|
} },
|
|
6435
6435
|
{ name: '시크릿 스캐너 F-06 (1.36.56, 외부감사): 무명 확장자 소형 텍스트 스캔(이진 NUL 제외) + 같은 토큰 중복 보고 dedupe — 행위검사', run: () => {
|
|
6436
6436
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_sc56_'));
|
|
@@ -8110,6 +8110,60 @@ function _p0015ModeOk() {
|
|
|
8110
8110
|
// 결정적으로 밟는다 — `typeof out.then === 'function'` 이라는 판별 조건이 동일하다.
|
|
8111
8111
|
// 1.36.109 (완료게이트 사냥): 두 결함을 **순수 호출**로 고정한다. 둘 다 양방향으로 단언한다 —
|
|
8112
8112
|
// 막아야 할 것을 막는지, 그리고 통과해야 할 것이 계속 통과하는지. false-BLOCK 은 false-PASS 보다 나쁘다.
|
|
8113
|
+
// 1.36.110 (T-0099): 미검증 자백/헤지 탐지 — **언어 대칭**과 **오탐 0** 을 순수 호출로 고정한다.
|
|
8114
|
+
// 기준선은 재현율 1/16(6%), 한국어 0/8 이었다. 같은 뜻의 주장이 표기 언어에 따라 갈리면 판정이 아니라 우연이다.
|
|
8115
|
+
// 재현율보다 FP 가 먼저다 — 정직한 주장이 막히면 사용자는 게이트를 끈다. 실제 done 이력 139건 발화 0 으로 확인했다.
|
|
8116
|
+
function _p0110HonestyOk() {
|
|
8117
|
+
try {
|
|
8118
|
+
const an = require('../lib/analyzers');
|
|
8119
|
+
const F = (t, done) => an._epistemicHonestyCheck(t, done ? { claimedDone: true } : undefined).findings;
|
|
8120
|
+
const high = (t, done) => F(t, done).filter(f => f.severity === 'high').length > 0;
|
|
8121
|
+
// 이 라운드가 추가한 것은 **권고**다 — 차단하지 않는다. 가드도 그 계약을 그대로 단언한다.
|
|
8122
|
+
const hit = (t, done) => F(t, done).some(f => f.dim === 'unverified-admission');
|
|
8123
|
+
// ① 언어 대칭 — 같은 뜻의 ko/en 쌍이 같은 판정을 받아야 한다(1차 강화에서 4쌍이 갈렸고,
|
|
8124
|
+
// 그중 하나는 한국어 음절 합성 때문이었다: `듯하다` 는 잡히는데 `듯합니다` 는 안 잡혔다).
|
|
8125
|
+
// 이 라운드가 **권고로** 잡는 것 — 직접적인 미검증 자백. 두 언어가 같은 판정을 받아야 한다.
|
|
8126
|
+
const pairs = [
|
|
8127
|
+
['테스트는 아직 안 돌렸지만 완료했습니다', 'Done, but not tested yet.'],
|
|
8128
|
+
['로직 반영했는데 엣지 케이스는 안 봤습니다.', 'Logic applied; edge cases unchecked.'],
|
|
8129
|
+
];
|
|
8130
|
+
const symmetric = pairs.every(([ko, en]) => hit(ko, true) === hit(en, true) && hit(ko, true) === true);
|
|
8131
|
+
// ①-c **차단하지 않는다** — 이 신호로 done 을 막지 않기로 한 결정을 고정한다.
|
|
8132
|
+
// 차단으로 올리려면 산문 파싱이 아니라 도구 자신의 실행 증거를 근거로 삼아야 한다(별도 과제).
|
|
8133
|
+
const neverBlocks = pairs.every(([ko, en]) => high(ko, true) === false && high(en, true) === false);
|
|
8134
|
+
// 기존 차단 차원(추정 표현)은 **그대로**여야 한다. 한국어 음절 합성 회귀를 여기서 고정한다 —
|
|
8135
|
+
// `듯\s*(?:하|싶)` 는 "듯하다" 는 잡지만 "듯합니다"(하+ㅂ→합)는 못 잡아 ko/en 이 갈렸다.
|
|
8136
|
+
const highPairs = [['해결된 듯합니다. 다음에 확인하겠습니다', 'Looks resolved. I will confirm next time.']];
|
|
8137
|
+
const highSymmetric = highPairs.every(([ko, en]) => high(ko, true) === true && high(en, true) === true);
|
|
8138
|
+
// ①-b **의도적으로 잡지 않는 것** — 미래 범위 표현. "추후 확인 예정" 은 (a) 이 작업을 검증 안 했다 와
|
|
8139
|
+
// (b) 앞으로 더 볼 게 남았다 를 모두 뜻할 수 있어 정규식으로 가를 수 없다. 자체 FP 헌트에서 이 표현이
|
|
8140
|
+
// 문서 수정·설정 변경·의존성 bump·revert 를 막았다(전부 정직한 완료 주장). 애매하면 통과시킨다.
|
|
8141
|
+
const futureScopeAllowed = [
|
|
8142
|
+
'docs/guide.md 갱신. 오탈자가 더 있을 수 있어 추후 확인 예정.',
|
|
8143
|
+
'package.json 의존성 3개 bump. 호환성은 추후 확인.',
|
|
8144
|
+
'Bumped deps in package.json. Compatibility will be checked later.',
|
|
8145
|
+
].every(t => hit(t, true) === false);
|
|
8146
|
+
// ② 오탐 0 — 근거를 댄 정직한 주장은 어떤 표현을 써도 통과한다. `!hasSource` 가 방어선이다.
|
|
8147
|
+
const honest = [
|
|
8148
|
+
'src/queue.ts 재작성. jest 88 passed, 0 failed.',
|
|
8149
|
+
'Rewrote src/queue.ts. jest: 88 passed, 0 failed.',
|
|
8150
|
+
'src/a.js 구현. 테스트 20/20 통과. 성능 개선 효과는 추후 측정 예정.', // 헤지 낱말 + 근거 → 통과해야 한다
|
|
8151
|
+
'오타 수정만 했습니다. 검증 필요 없는 주석 변경. src/c.js', // "필요 없는" 은 반대 의미
|
|
8152
|
+
'Comment typo only in src/c.js — needs no verification.',
|
|
8153
|
+
// 러너 이름을 열거하지 않는다는 계약 — 실행했는데 막히면 명백한 오차단이다(자체 FP 헌트가 6건 잡았다).
|
|
8154
|
+
'app/src/Main.kt 구현. ./gradlew test 통과. 엣지 케이스는 안 봤습니다.',
|
|
8155
|
+
'Program.cs 구현. dotnet test 실행 완료. 통합 시나리오는 아직 확인 못 했습니다.',
|
|
8156
|
+
'app/models/user.rb 구현. bundle exec rspec 실행. 엣지 케이스는 안 봤습니다.',
|
|
8157
|
+
'src/x.ts 구현. ./scripts/verify.sh 실행해 통과. 회귀는 아직 안 봤습니다.',
|
|
8158
|
+
];
|
|
8159
|
+
const noFalseBlock = honest.every(t => hit(t, true) === false);
|
|
8160
|
+
// ③ claimedDone 은 **호출부가 아는 사실**을 받는 옵션이다 — 기본값 동작은 바뀌지 않아야 한다
|
|
8161
|
+
// (독립 표면 `honesty-check --text` 가 갑자기 더 엄해지면 그건 이 라운드가 약속한 범위 밖이다).
|
|
8162
|
+
const optIn = hit('로직 반영했는데 엣지 케이스는 안 봤습니다.', true) === true
|
|
8163
|
+
&& hit('엣지 케이스는 안 봤습니다.', false) === false; // 산문에 완료 낱말이 없으면 기본값에선 안 잡는다
|
|
8164
|
+
return symmetric && neverBlocks && highSymmetric && futureScopeAllowed && noFalseBlock && optIn;
|
|
8165
|
+
} catch { return false; }
|
|
8166
|
+
}
|
|
8113
8167
|
function _p0109ClaimGateOk() {
|
|
8114
8168
|
try {
|
|
8115
8169
|
const an = require('../lib/analyzers');
|
|
@@ -8800,141 +8854,160 @@ function commandsCmd(root) {
|
|
|
8800
8854
|
const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
8801
8855
|
const cats = {
|
|
8802
8856
|
status: [
|
|
8803
|
-
{ cmd: 'handoff [path] [--json] [--pulse] [--compact] [--quiet]', desc: '세션 시작 컨텍스트 + 7 통합 필드' },
|
|
8804
|
-
{ cmd: 'health [path] [--json] [--strict]', desc: '종합 헬스 + drift/security/skill/memory/featureGraph/roundHistory/milestones' },
|
|
8805
|
-
{ cmd: 'pulse [path] [--json]', desc: '한 줄 종합 요약 (1.9.231)' },
|
|
8806
|
-
{ cmd: 'round-history [path] [--json]', desc: 'git tag 기반 자율 라운드 통계 (1.9.226)' },
|
|
8807
|
-
{ cmd: 'milestones [path] [--json]', desc: '도달 마일스톤 + 다음 ETA (1.9.229)' },
|
|
8808
|
-
{ cmd: 'session-resume [path] [--auto-fix] [--json]', desc: '비정상 종료 감지 + 자율 재개 (1.9.220/222)' },
|
|
8809
|
-
{ cmd: 'which', desc: '활성 provider + 경로 진단' },
|
|
8810
|
-
{ cmd: 'memory status [path] [--json]', desc: 'T/D/R/P/L Memory Surface (1.9.114)' }
|
|
8857
|
+
{ cmd: 'handoff [path] [--json] [--pulse] [--compact] [--quiet]', desc: '세션 시작 컨텍스트 + 7 통합 필드', descEn: 'session-start context + 7 integrated fields' },
|
|
8858
|
+
{ cmd: 'health [path] [--json] [--strict]', desc: '종합 헬스 + drift/security/skill/memory/featureGraph/roundHistory/milestones', descEn: 'overall health + drift/security/skill/memory/featureGraph/roundHistory/milestones' },
|
|
8859
|
+
{ cmd: 'pulse [path] [--json]', desc: '한 줄 종합 요약 (1.9.231)', descEn: 'one-line overall summary (1.9.231)' },
|
|
8860
|
+
{ cmd: 'round-history [path] [--json]', desc: 'git tag 기반 자율 라운드 통계 (1.9.226)', descEn: 'autonomous round stats from git tags (1.9.226)' },
|
|
8861
|
+
{ cmd: 'milestones [path] [--json]', desc: '도달 마일스톤 + 다음 ETA (1.9.229)', descEn: 'reached milestones + next ETA (1.9.229)' },
|
|
8862
|
+
{ cmd: 'session-resume [path] [--auto-fix] [--json]', desc: '비정상 종료 감지 + 자율 재개 (1.9.220/222)', descEn: 'detect abnormal shutdown + autonomous resume (1.9.220/222)' },
|
|
8863
|
+
{ cmd: 'which', desc: '활성 provider + 경로 진단', descEn: 'active provider + path diagnosis' },
|
|
8864
|
+
{ cmd: 'memory status [path] [--json]', desc: 'T/D/R/P/L Memory Surface (1.9.114)', descEn: 'T/D/R/P/L memory surface (1.9.114)' }
|
|
8811
8865
|
],
|
|
8812
8866
|
task: [
|
|
8813
|
-
{ cmd: 'task add "<request>"', desc: 'task 추가 (자동 dedup 1.9.212, review trigger 1.9.177)' },
|
|
8814
|
-
{ cmd: 'task update <id> --status <S>', desc: 'task 상태 갱신' },
|
|
8815
|
-
{ cmd: 'task drop <id>', desc: 'task 드롭' },
|
|
8816
|
-
{ cmd: 'task export --to <path>', desc: 'TodoWrite JSON 형식 export (1.9.60)' },
|
|
8817
|
-
{ cmd: 'next-action take|list [--json]', desc: 'next-action queue (1.9.201)' }
|
|
8867
|
+
{ cmd: 'task add "<request>"', desc: 'task 추가 (자동 dedup 1.9.212, review trigger 1.9.177)', descEn: 'add a task (auto-dedup 1.9.212, review trigger 1.9.177)' },
|
|
8868
|
+
{ cmd: 'task update <id> --status <S>', desc: 'task 상태 갱신', descEn: 'update task status' },
|
|
8869
|
+
{ cmd: 'task drop <id>', desc: 'task 드롭', descEn: 'drop a task' },
|
|
8870
|
+
{ cmd: 'task export --to <path>', desc: 'TodoWrite JSON 형식 export (1.9.60)', descEn: 'export in TodoWrite JSON format (1.9.60)' },
|
|
8871
|
+
{ cmd: 'next-action take|list [--json]', desc: 'next-action queue (1.9.201)', descEn: 'next-action queue (1.9.201)' }
|
|
8818
8872
|
],
|
|
8819
8873
|
memory: [
|
|
8820
|
-
{ cmd: 'decision add|list|drop <id>', desc: 'decisions.md CRUD (1.9.108/118/125)' },
|
|
8821
|
-
{ cmd: 'rule add|list|pause|resume|remove', desc: 'rules.md CRUD (1.9.8/109)' },
|
|
8822
|
-
{ cmd: 'plan add|list <M-id>', desc: 'plan.md milestone CRUD (1.9.110/119)' },
|
|
8823
|
-
{ cmd: 'lesson save|list|drop', desc: 'lessons.md CRUD (1.9.112/117/124)' }
|
|
8874
|
+
{ cmd: 'decision add|list|drop <id>', desc: 'decisions.md CRUD (1.9.108/118/125)', descEn: 'decisions.md CRUD (1.9.108/118/125)' },
|
|
8875
|
+
{ cmd: 'rule add|list|pause|resume|remove', desc: 'rules.md CRUD (1.9.8/109)', descEn: 'rules.md CRUD (1.9.8/109)' },
|
|
8876
|
+
{ cmd: 'plan add|list <M-id>', desc: 'plan.md milestone CRUD (1.9.110/119)', descEn: 'plan.md milestone CRUD (1.9.110/119)' },
|
|
8877
|
+
{ cmd: 'lesson save|list|drop', desc: 'lessons.md CRUD (1.9.112/117/124)', descEn: 'lessons.md CRUD (1.9.112/117/124)' }
|
|
8824
8878
|
],
|
|
8825
8879
|
audit: [
|
|
8826
|
-
{ cmd: 'audit [path] [--fix] [--strict] [--json]', desc: '메타 + 보안 + npm CVE 통합 감사' },
|
|
8827
|
-
{ cmd: 'drift check [path] [--auto-fix] [--json]', desc: 'drift 신호 + 자동 회복 (1.9.37/82/225)' },
|
|
8828
|
-
{ cmd: 'scan secrets [path]', desc: '시크릿 탐지' },
|
|
8829
|
-
{ cmd: 'encoding check [path]', desc: '인코딩 검증' },
|
|
8830
|
-
{ cmd: 'lazy detect [path] [--json]', desc: '게으른 작업 감지 (1.9.101)' },
|
|
8831
|
-
{ cmd: 'verify-claim <T-ID|--all> [--run-tests] [--test-cmd "<명령>"] [--strict-claims] [--require-evidence]', desc: '주장 검증 (1.9.18~26) — --all: 모든 done 주장 일괄 검증(CI·스케일, 1.33.2) · --require-evidence: done 주장에 파일+테스트 근거 강제 (1.9.287) · --test-cmd: 비-JS 테스트 명령 (1.17.2)' },
|
|
8832
|
-
{ cmd: `lens [${_lensDomainList()}] [--json]`, desc: '분야별 자기질문 품질 렌즈 + 분야간 인과관계 (1.18.3 · database/contract/recovery/observability 는 심화, axes 는 8축 경량 점검)' },
|
|
8880
|
+
{ cmd: 'audit [path] [--fix] [--strict] [--json]', desc: '메타 + 보안 + npm CVE 통합 감사', descEn: 'combined meta + security + npm CVE audit' },
|
|
8881
|
+
{ cmd: 'drift check [path] [--auto-fix] [--json]', desc: 'drift 신호 + 자동 회복 (1.9.37/82/225)', descEn: 'drift signals + auto-recovery (1.9.37/82/225)' },
|
|
8882
|
+
{ cmd: 'scan secrets [path]', desc: '시크릿 탐지', descEn: 'secret detection' },
|
|
8883
|
+
{ cmd: 'encoding check [path]', desc: '인코딩 검증', descEn: 'encoding verification' },
|
|
8884
|
+
{ cmd: 'lazy detect [path] [--json]', desc: '게으른 작업 감지 (1.9.101)', descEn: 'detect lazy/incomplete work (1.9.101)' },
|
|
8885
|
+
{ cmd: 'verify-claim <T-ID|--all> [--run-tests] [--test-cmd "<명령>"] [--strict-claims] [--require-evidence]', cmdEn: 'verify-claim <T-ID|--all> [--run-tests] [--test-cmd "<command>"] [--strict-claims] [--require-evidence]', desc: '주장 검증 (1.9.18~26) — --all: 모든 done 주장 일괄 검증(CI·스케일, 1.33.2) · --require-evidence: done 주장에 파일+테스트 근거 강제 (1.9.287) · --test-cmd: 비-JS 테스트 명령 (1.17.2)', descEn: 'verify a claim (1.9.18~26) — --all: verify every done claim at once (CI/scale, 1.33.2) · --require-evidence: require file + test evidence on done claims (1.9.287) · --test-cmd: non-JS test command (1.17.2)' },
|
|
8886
|
+
{ cmd: `lens [${_lensDomainList()}] [--json]`, desc: '분야별 자기질문 품질 렌즈 + 분야간 인과관계 (1.18.3 · database/contract/recovery/observability 는 심화, axes 는 8축 경량 점검)', descEn: 'per-domain self-question quality lenses + cross-domain causality (1.18.3 · database/contract/recovery/observability are deep dives, axes is a light 8-axis check)' },
|
|
8833
8887
|
// 1.36.101 (명령 표면 감사): 1.36.98 에서 디스패처에만 붙이고 commands/--help 에 등재하지 않아 '있는데 안 보이는' 명령이었다.
|
|
8834
|
-
{ cmd: 'library [show|page] [path] [--json] [--ai]', desc: '재사용 인벤토리 — 컴포넌트·디자인 토큰을 저장소에서 추출. page: 오프라인 단일 HTML · --ai: 에이전트용 압축 JSON (1.36.98 P-0013)' },
|
|
8835
|
-
{ cmd: 'optimism-check <T-ID>', desc: '낙관적 API 감지 (1.9.26)' },
|
|
8836
|
-
{ cmd: 'requests audit|list|complete|drop|auto-complete', desc: '사용자 요청 추적 (1.9.207/223)' },
|
|
8837
|
-
{ cmd: 'pre-wake-audit [path] [--last]', desc: 'sleep 전 점검 (1.9.209)' },
|
|
8838
|
-
{ cmd: 'idempotency audit', desc: '멱등성 위반 탐지 (1.9.212)' }
|
|
8888
|
+
{ cmd: 'library [show|page] [path] [--json] [--ai]', desc: '재사용 인벤토리 — 컴포넌트·디자인 토큰을 저장소에서 추출. page: 오프라인 단일 HTML · --ai: 에이전트용 압축 JSON (1.36.98 P-0013)', descEn: 'reuse inventory — extracts components and design tokens from the repo. page: offline single HTML · --ai: compact JSON for agents (1.36.98 P-0013)' },
|
|
8889
|
+
{ cmd: 'optimism-check <T-ID>', desc: '낙관적 API 감지 (1.9.26)', descEn: 'detect optimistic API claims (1.9.26)' },
|
|
8890
|
+
{ cmd: 'requests audit|list|complete|drop|auto-complete', desc: '사용자 요청 추적 (1.9.207/223)', descEn: 'track user requests (1.9.207/223)' },
|
|
8891
|
+
{ cmd: 'pre-wake-audit [path] [--last]', desc: 'sleep 전 점검 (1.9.209)', descEn: 'pre-sleep checks (1.9.209)' },
|
|
8892
|
+
{ cmd: 'idempotency audit', desc: '멱등성 위반 탐지 (1.9.212)', descEn: 'detect idempotency violations (1.9.212)' }
|
|
8839
8893
|
],
|
|
8840
8894
|
workflow: [
|
|
8841
|
-
{ cmd: 'session close [path] [--json] [--auto-apply-delivered]', desc: '세션 마감 + 9 카테고리 + 자동 통합' },
|
|
8842
|
-
{ cmd: 'resume [path]', desc: 'auto-resume-plan 적용 (1.9.203)' },
|
|
8843
|
-
{ cmd: 'route <task-type>', desc: '작업 유형 분류 (11종)' },
|
|
8844
|
-
{ cmd: 'agents list|check|quota|dispatch|multi|recommend', desc: '외부 AI CLI 오케스트레이션 (dispatch --role 모델 라우팅 1.9.270)' },
|
|
8845
|
-
{ cmd: 'roles list|set|unset|catalog|suggest|verify', desc: '모델별 역할 부여 (코딩/검수/지휘/디자인/디버그/설계/분배) — 1.9.270' },
|
|
8846
|
-
{ cmd: 'capabilities [--json]', desc: '권한·보안 표면 공개 (무엇을 하는지 + opt-out + 주의 명령) — 1.9.272' },
|
|
8847
|
-
{ cmd: 'state show|start|record|verify|handoff', desc: '.leerness/ JSON 상태 substrate (에이전트 간 인수인계 표준) — 1.9.278' },
|
|
8848
|
-
{ cmd: 'adapter <tool>|list [--dry-run]', desc: '도구별 지침/.mcp.json 선택 생성 (claude/cursor/codex/goose/...) — 1.9.280' },
|
|
8849
|
-
{ cmd: 'ci init [path] [--force]', desc: 'PR 마다 leerness gate 실행하는 GitHub Actions 워크플로 생성 (.github/workflows/leerness-gate.yml) — 1.9.444' },
|
|
8850
|
-
{ cmd: 'export|prompt --target <agent>', desc: 'adapter 별칭 — 도구별 지침/계약 파일 생성 (claude/cursor/codex/agents-md/...) — 1.9.448' },
|
|
8851
|
-
{ cmd: 'glossary [build|show] [--lang ko|en|both] [--json]', desc: '의존성 용어집 — package.json/requirements 라이브러리를 비개발자용 한 줄 설명으로 (.harness/glossary.md) — 1.11.4' },
|
|
8852
|
-
{ cmd: 'policy show|set|check', desc: '권한 등급 (read-only…publish) — opt-in enforced (위험 명령 차단) — 1.9.281' },
|
|
8853
|
-
{ cmd: 'reuse-check "<기능>"', desc: '외부 OSS 빌드 vs 재사용 결정 게이트 (오프라인 카테고리+체크리스트) — 1.9.285' },
|
|
8854
|
-
{ cmd: 'skill impact', desc: '스킬 설치 영향 경량 상관추적 (사용 빈도 ↔ 검증 통과율, advisory) — 1.9.286' },
|
|
8855
|
-
{ cmd: 'release channel [--json]', desc: '릴리스 채널 정책 (latest 안정 / next 실험 / 버전 고정) — 1.9.275' },
|
|
8856
|
-
{ cmd: 'slash-commands [agent] [--json --record --detect --refresh]', desc: 'CLI 에이전트 슬래시 명령 레지스트리 + --help probe 자동 갱신 (1.9.265~267, UR-0021)' },
|
|
8857
|
-
{ cmd: 'review-request "<request>"', desc: '사용자 요청 사전 검토 (1.9.176)' },
|
|
8858
|
-
{ cmd: 'clarify "<사용자 요청>" [--json]', desc: '요청 모호성 신호 감지 → 사용자에게 물을 질문 생성 (추측 구현 방지) — 1.36.51 UR-0061' },
|
|
8859
|
-
{ cmd: 'tech [--json]', desc: '기술 프로필 — 개발 언어·연결 서비스 자동 감지 + 마이그레이션/언어전환 이력, 그래프 🛠 탭 표시 — 1.36.53 UR-0062' },
|
|
8860
|
-
{ cmd: 'integrity check [--repair] [--json]', desc: 'managed 정책-문서 12종 무결성(부재/H1 상실/절단) 점검 — --repair: archive 대피 후 템플릿 재생성 — 1.36.57 감사 F-04' },
|
|
8861
|
-
{ cmd: 'referee add|verify|list|show|drop', desc: '검증기 캘리브레이션 — 신뢰 전에 탐지력 증명(known-good 통과 + 망가뜨린 known-bad 를 기대 사유로 거부). verify-claim --referee / gate 게이팅 — 1.36.80 P-0001' },
|
|
8862
|
-
{ cmd: 'preview add|list|show|approve|revise|mockup|verify', desc: '신규 기능 미리보기 승인 워크플로 — approve 전 코드 작성 금지 계약. mockup <P-ID> [--target <파일>] [--force]: 프로젝트의 실제 토큰·컴포넌트·대상 화면 클래스를 심은 시안 · approve 시 그 시안이 계약으로 고정됨 · verify <P-ID> --files a,b: 구현이 계약을 지켰는지 존재 대조(시각적 일치는 보증하지 않음) — 1.36.51 UR-0061 · 1.36.75 UR-0066 · 1.36.99/1.36.100 P-0012' },
|
|
8863
|
-
{ cmd: 'dashboard [path] [--port N] [--timeout SEC] [--json]', desc: '.harness 읽기 전용 대시보드 — 토글·task·마일스톤·미리보기·라우팅기록·bugfix probe 를 localhost 에서 열람. 설정 변경·게이트 판정은 하지 않는다(CLI 가 판정). --json 은 서버 없이 같은 스냅샷 출력. 정적 단일파일 뷰(leerness.html)는 leerness graph --html 로 그대로 — 1.36.90 P-0006' },
|
|
8864
|
-
{ cmd: 'agents route "<작업>" [--tier tiny|normal|high-risk] [--confirm --approved-by "<승인자>"] [--log]', desc: '작업 난이도 판정(결정적 규칙) → 추상 역할(architect/commander/coder/reviewer) 배치 제안. 모델명 하드코딩 없이 `roles set` 매핑을 쓰고, 실행은 명시 확인 필요. 토글 기본 OFF: leerness toggle set difficulty-routing on — 1.36.89 P-0007' },
|
|
8865
|
-
{ cmd: 'bugfix start|receipt|drop|list', desc: 'bugfix 완료 영수증 — 수정 전 재현 probe 를 등록(2회 연속 실패해야 등록)하고, done 전이에서 통과 + 근본원인/형제범위 영수증을 요구. 토글 기본 OFF: leerness toggle set bugfix-receipt on — 1.36.87 P-0005' },
|
|
8866
|
-
{ cmd: 'review <file> --persona <ids>', desc: '페르소나 리뷰 (1.9.29)' },
|
|
8867
|
-
{ cmd: 'brainstorm "<topic>" [--include-code]', desc: '워크스페이스 회수 + 코드 grep' }
|
|
8895
|
+
{ cmd: 'session close [path] [--json] [--auto-apply-delivered]', desc: '세션 마감 + 9 카테고리 + 자동 통합', descEn: 'close the session + 9 categories + auto integration' },
|
|
8896
|
+
{ cmd: 'resume [path]', desc: 'auto-resume-plan 적용 (1.9.203)', descEn: 'apply the auto-resume plan (1.9.203)' },
|
|
8897
|
+
{ cmd: 'route <task-type>', desc: '작업 유형 분류 (11종)', descEn: 'classify the task type (11 kinds)' },
|
|
8898
|
+
{ cmd: 'agents list|check|quota|dispatch|multi|recommend', desc: '외부 AI CLI 오케스트레이션 (dispatch --role 모델 라우팅 1.9.270)', descEn: 'orchestrate external AI CLIs (dispatch --role model routing 1.9.270)' },
|
|
8899
|
+
{ cmd: 'roles list|set|unset|catalog|suggest|verify', desc: '모델별 역할 부여 (코딩/검수/지휘/디자인/디버그/설계/분배) — 1.9.270', descEn: 'assign roles per model (coding/review/command/design/debug/architecture/dispatch) — 1.9.270' },
|
|
8900
|
+
{ cmd: 'capabilities [--json]', desc: '권한·보안 표면 공개 (무엇을 하는지 + opt-out + 주의 명령) — 1.9.272', descEn: 'disclose the permission/security surface (what it does + opt-outs + commands to watch) — 1.9.272' },
|
|
8901
|
+
{ cmd: 'state show|start|record|verify|handoff', desc: '.leerness/ JSON 상태 substrate (에이전트 간 인수인계 표준) — 1.9.278', descEn: '.leerness/ JSON state substrate (cross-agent handoff standard) — 1.9.278' },
|
|
8902
|
+
{ cmd: 'adapter <tool>|list [--dry-run]', desc: '도구별 지침/.mcp.json 선택 생성 (claude/cursor/codex/goose/...) — 1.9.280', descEn: 'generate per-tool instructions/.mcp.json selectively (claude/cursor/codex/goose/...) — 1.9.280' },
|
|
8903
|
+
{ cmd: 'ci init [path] [--force]', desc: 'PR 마다 leerness gate 실행하는 GitHub Actions 워크플로 생성 (.github/workflows/leerness-gate.yml) — 1.9.444', descEn: 'create a GitHub Actions workflow that runs leerness gate on every PR (.github/workflows/leerness-gate.yml) — 1.9.444' },
|
|
8904
|
+
{ cmd: 'export|prompt --target <agent>', desc: 'adapter 별칭 — 도구별 지침/계약 파일 생성 (claude/cursor/codex/agents-md/...) — 1.9.448', descEn: 'adapter alias — generate per-tool instruction/contract files (claude/cursor/codex/agents-md/...) — 1.9.448' },
|
|
8905
|
+
{ cmd: 'glossary [build|show] [--lang ko|en|both] [--json]', desc: '의존성 용어집 — package.json/requirements 라이브러리를 비개발자용 한 줄 설명으로 (.harness/glossary.md) — 1.11.4', descEn: 'dependency glossary — one-line plain-language descriptions of package.json/requirements libraries (.harness/glossary.md) — 1.11.4' },
|
|
8906
|
+
{ cmd: 'policy show|set|check', desc: '권한 등급 (read-only…publish) — opt-in enforced (위험 명령 차단) — 1.9.281', descEn: 'permission tiers (read-only…publish) — opt-in enforcement (blocks risky commands) — 1.9.281' },
|
|
8907
|
+
{ cmd: 'reuse-check "<기능>"', cmdEn: 'reuse-check "<capability>"', desc: '외부 OSS 빌드 vs 재사용 결정 게이트 (오프라인 카테고리+체크리스트) — 1.9.285', descEn: 'build-vs-reuse decision gate for external OSS (offline categories + checklist) — 1.9.285' },
|
|
8908
|
+
{ cmd: 'skill impact', desc: '스킬 설치 영향 경량 상관추적 (사용 빈도 ↔ 검증 통과율, advisory) — 1.9.286', descEn: 'lightweight correlation of skill installs (usage frequency vs verification pass rate, advisory) — 1.9.286' },
|
|
8909
|
+
{ cmd: 'release channel [--json]', desc: '릴리스 채널 정책 (latest 안정 / next 실험 / 버전 고정) — 1.9.275', descEn: 'release channel policy (latest stable / next experimental / pinned) — 1.9.275' },
|
|
8910
|
+
{ cmd: 'slash-commands [agent] [--json --record --detect --refresh]', desc: 'CLI 에이전트 슬래시 명령 레지스트리 + --help probe 자동 갱신 (1.9.265~267, UR-0021)', descEn: 'slash-command registry for CLI agents + auto refresh via --help probe (1.9.265~267, UR-0021)' },
|
|
8911
|
+
{ cmd: 'review-request "<request>"', desc: '사용자 요청 사전 검토 (1.9.176)', descEn: 'pre-review a user request (1.9.176)' },
|
|
8912
|
+
{ cmd: 'clarify "<사용자 요청>" [--json]', cmdEn: 'clarify "<user request>" [--json]', desc: '요청 모호성 신호 감지 → 사용자에게 물을 질문 생성 (추측 구현 방지) — 1.36.51 UR-0061', descEn: 'detect ambiguity signals in a request → generate questions to ask the user (prevents guess-implementation) — 1.36.51 UR-0061' },
|
|
8913
|
+
{ cmd: 'tech [--json]', desc: '기술 프로필 — 개발 언어·연결 서비스 자동 감지 + 마이그레이션/언어전환 이력, 그래프 🛠 탭 표시 — 1.36.53 UR-0062', descEn: 'tech profile — auto-detect languages and connected services + migration/language-switch history, shown on the graph 🛠 tab — 1.36.53 UR-0062' },
|
|
8914
|
+
{ cmd: 'integrity check [--repair] [--json]', desc: 'managed 정책-문서 12종 무결성(부재/H1 상실/절단) 점검 — --repair: archive 대피 후 템플릿 재생성 — 1.36.57 감사 F-04', descEn: 'integrity check for the 12 managed policy documents (missing/lost H1/truncated) — --repair: archive the old copy then regenerate from template — 1.36.57 audit F-04' },
|
|
8915
|
+
{ cmd: 'referee add|verify|list|show|drop', desc: '검증기 캘리브레이션 — 신뢰 전에 탐지력 증명(known-good 통과 + 망가뜨린 known-bad 를 기대 사유로 거부). verify-claim --referee / gate 게이팅 — 1.36.80 P-0001', descEn: 'verifier calibration — prove detection power before trusting it (passes known-good, rejects a broken known-bad for the expected reason). Gates verify-claim --referee / gate — 1.36.80 P-0001' },
|
|
8916
|
+
{ cmd: 'preview add|list|show|approve|revise|mockup|verify', desc: '신규 기능 미리보기 승인 워크플로 — approve 전 코드 작성 금지 계약. mockup <P-ID> [--target <파일>] [--force]: 프로젝트의 실제 토큰·컴포넌트·대상 화면 클래스를 심은 시안 · approve 시 그 시안이 계약으로 고정됨 · verify <P-ID> --files a,b: 구현이 계약을 지켰는지 존재 대조(시각적 일치는 보증하지 않음) — 1.36.51 UR-0061 · 1.36.75 UR-0066 · 1.36.99/1.36.100 P-0012', descEn: 'preview-approval workflow for new features — contract: no code before approve. mockup <P-ID> [--target <file>] [--force]: a draft seeded with the project\'s real tokens/components/target-screen classes · approve freezes that draft as the contract · verify <P-ID> --files a,b: existence check that the implementation kept the contract (visual match is NOT guaranteed) — 1.36.51 UR-0061 · 1.36.75 UR-0066 · 1.36.99/1.36.100 P-0012' },
|
|
8917
|
+
{ cmd: 'dashboard [path] [--port N] [--timeout SEC] [--json]', desc: '.harness 읽기 전용 대시보드 — 토글·task·마일스톤·미리보기·라우팅기록·bugfix probe 를 localhost 에서 열람. 설정 변경·게이트 판정은 하지 않는다(CLI 가 판정). --json 은 서버 없이 같은 스냅샷 출력. 정적 단일파일 뷰(leerness.html)는 leerness graph --html 로 그대로 — 1.36.90 P-0006', descEn: 'read-only .harness dashboard — browse toggles/tasks/milestones/previews/routing history/bugfix probes on localhost. It never changes settings or makes gate verdicts (the CLI does). --json prints the same snapshot without a server. The static single-file view (leerness.html) is still leerness graph --html — 1.36.90 P-0006' },
|
|
8918
|
+
{ cmd: 'agents route "<작업>" [--tier tiny|normal|high-risk] [--confirm --approved-by "<승인자>"] [--log]', cmdEn: 'agents route "<task>" [--tier tiny|normal|high-risk] [--confirm --approved-by "<approver>"] [--log]', desc: '작업 난이도 판정(결정적 규칙) → 추상 역할(architect/commander/coder/reviewer) 배치 제안. 모델명 하드코딩 없이 `roles set` 매핑을 쓰고, 실행은 명시 확인 필요. 토글 기본 OFF: leerness toggle set difficulty-routing on — 1.36.89 P-0007', descEn: 'judge task difficulty (deterministic rules) → propose an abstract-role assignment (architect/commander/coder/reviewer). Uses your `roles set` mapping instead of hardcoded model names, and execution needs explicit confirmation. Toggle is OFF by default: leerness toggle set difficulty-routing on — 1.36.89 P-0007' },
|
|
8919
|
+
{ cmd: 'bugfix start|receipt|drop|list', desc: 'bugfix 완료 영수증 — 수정 전 재현 probe 를 등록(2회 연속 실패해야 등록)하고, done 전이에서 통과 + 근본원인/형제범위 영수증을 요구. 토글 기본 OFF: leerness toggle set bugfix-receipt on — 1.36.87 P-0005', descEn: 'bugfix completion receipt — register a reproduction probe before the fix (it must fail twice in a row to register), then require it to pass at the done transition plus a root-cause/sibling-scope receipt. Toggle is OFF by default: leerness toggle set bugfix-receipt on — 1.36.87 P-0005' },
|
|
8920
|
+
{ cmd: 'review <file> --persona <ids>', desc: '페르소나 리뷰 (1.9.29)', descEn: 'persona-based review (1.9.29)' },
|
|
8921
|
+
{ cmd: 'brainstorm "<topic>" [--include-code]', desc: '워크스페이스 회수 + 코드 grep', descEn: 'recall from the workspace + grep the code' }
|
|
8868
8922
|
],
|
|
8869
8923
|
release: [
|
|
8870
|
-
{ cmd: 'release sync-main [path]', desc: 'main merge + push + 자동 npm publish (1.9.140/178)' },
|
|
8871
|
-
{ cmd: 'release pack', desc: 'tarball 생성' },
|
|
8872
|
-
{ cmd: 'release publish', desc: 'GitHub release 발행' },
|
|
8873
|
-
{ cmd: 'release channel [path] [--json]', desc: '릴리스 채널 정책 (stable/next/고정) — 1.9.275' },
|
|
8874
|
-
{ cmd: 'release cadence [path] [--json]', desc: '릴리스 빈도 진단 + 권장 (외부리뷰 케이던스 가시화) — UR-0074, 1.9.374' }
|
|
8924
|
+
{ cmd: 'release sync-main [path]', desc: 'main merge + push + 자동 npm publish (1.9.140/178)', descEn: 'merge to main + push + auto npm publish (1.9.140/178)' },
|
|
8925
|
+
{ cmd: 'release pack', desc: 'tarball 생성', descEn: 'build the tarball' },
|
|
8926
|
+
{ cmd: 'release publish', desc: 'GitHub release 발행', descEn: 'publish a GitHub release' },
|
|
8927
|
+
{ cmd: 'release channel [path] [--json]', desc: '릴리스 채널 정책 (stable/next/고정) — 1.9.275', descEn: 'release channel policy (stable/next/pinned) — 1.9.275' },
|
|
8928
|
+
{ cmd: 'release cadence [path] [--json]', desc: '릴리스 빈도 진단 + 권장 (외부리뷰 케이던스 가시화) — UR-0074, 1.9.374', descEn: 'release cadence diagnosis + recommendation — UR-0074, 1.9.374' }
|
|
8875
8929
|
],
|
|
8876
8930
|
skill: [
|
|
8877
|
-
{ cmd: 'skill list|info|learn|use|optimize|match|suggest', desc: 'skill 카탈로그 CRUD' },
|
|
8878
|
-
{ cmd: 'skill install|install-top|publish|search|discover', desc: 'skill 배포 + 검색' }
|
|
8931
|
+
{ cmd: 'skill list|info|learn|use|optimize|match|suggest', desc: 'skill 카탈로그 CRUD', descEn: 'skill catalog CRUD' },
|
|
8932
|
+
{ cmd: 'skill install|install-top|publish|search|discover', desc: 'skill 배포 + 검색', descEn: 'skill distribution + search' }
|
|
8879
8933
|
],
|
|
8880
8934
|
bridge: [
|
|
8881
|
-
{ cmd: 'web check|screenshot|extract <url>', desc: 'playwright bridge (opt-in, 1.9.165)' },
|
|
8882
|
-
{ cmd: 'pc check|click|type|screenshot', desc: 'robotjs/nut-tree bridge (opt-in, 1.9.166)' },
|
|
8883
|
-
{ cmd: 'lsp check|symbols|references', desc: 'LSP 어댑터 (1.9.167)' }
|
|
8935
|
+
{ cmd: 'web check|screenshot|extract <url>', desc: 'playwright bridge (opt-in, 1.9.165)', descEn: 'playwright bridge (opt-in, 1.9.165)' },
|
|
8936
|
+
{ cmd: 'pc check|click|type|screenshot', desc: 'robotjs/nut-tree bridge (opt-in, 1.9.166)', descEn: 'robotjs/nut-tree bridge (opt-in, 1.9.166)' },
|
|
8937
|
+
{ cmd: 'lsp check|symbols|references', desc: 'LSP 어댑터 (1.9.167)', descEn: 'LSP adapter (1.9.167)' }
|
|
8884
8938
|
],
|
|
8885
8939
|
config: [
|
|
8886
|
-
{ cmd: 'init [path]', desc: '워크스페이스 초기화' },
|
|
8887
|
-
{ cmd: 'migrate [path] [--dry-run]', desc: '업데이트 마이그레이션' },
|
|
8888
|
-
{ cmd: 'update [--check|--yes|--force]', desc: '자가 업데이트' },
|
|
8889
|
-
{ cmd: 'wakeup-interval get|set|auto|history|record', desc: 'adaptive wakeup (1.9.210)' },
|
|
8890
|
-
{ cmd: 'workspace-dir get|guide', desc: '워크스페이스 디렉토리 (1.9.211)' },
|
|
8891
|
-
{ cmd: 'parent detect|adopt [--select <kinds>] [--apply]', desc: '상위 leerness 부모 탐지 + 자산 게이트형 adopt (1.30.2~3)' },
|
|
8892
|
-
{ cmd: 'intent classify|expand|domains "<request>"', desc: '의도 파악 + scope (1.9.213)' },
|
|
8893
|
-
{ cmd: 'constraints list|check|add', desc: '플랫폼/API 제약 (1.9.208)' },
|
|
8894
|
-
{ cmd: 'provider list|add|remove|sync', desc: 'Provider Registry (1.9.157~160)' },
|
|
8895
|
-
{ cmd: 'commands [--json]', desc: '전체 명령 카테고리 목록 (1.9.233)' },
|
|
8896
|
-
{ cmd: 'migrate audit|apply|plan [path] [--json] [--yes]', desc: '크로스버전 마이그레이션 진단/적용/플랜 (UR-0075, 1.9.356~358)' },
|
|
8897
|
-
{ cmd: 'migrate --guide', desc: 'AI 에이전트용 크로스버전 마이그레이션 가이드 (1.9.355)' },
|
|
8898
|
-
{ cmd: 'doctor [--json]', desc: '설치/환경 진단 (1.9.315)' },
|
|
8899
|
-
{ cmd: 'selftest [--json]', desc: '코어 함수 무결성 자가 검증 (1.9.258)' },
|
|
8940
|
+
{ cmd: 'init [path]', desc: '워크스페이스 초기화', descEn: 'initialize the workspace' },
|
|
8941
|
+
{ cmd: 'migrate [path] [--dry-run]', desc: '업데이트 마이그레이션', descEn: 'update migration' },
|
|
8942
|
+
{ cmd: 'update [--check|--yes|--force]', desc: '자가 업데이트', descEn: 'self-update' },
|
|
8943
|
+
{ cmd: 'wakeup-interval get|set|auto|history|record', desc: 'adaptive wakeup (1.9.210)', descEn: 'adaptive wakeup interval (1.9.210)' },
|
|
8944
|
+
{ cmd: 'workspace-dir get|guide', desc: '워크스페이스 디렉토리 (1.9.211)', descEn: 'workspace directory (1.9.211)' },
|
|
8945
|
+
{ cmd: 'parent detect|adopt [--select <kinds>] [--apply]', desc: '상위 leerness 부모 탐지 + 자산 게이트형 adopt (1.30.2~3)', descEn: 'detect a parent leerness above + gated asset adoption (1.30.2~3)' },
|
|
8946
|
+
{ cmd: 'intent classify|expand|domains "<request>"', desc: '의도 파악 + scope (1.9.213)', descEn: 'infer intent + scope (1.9.213)' },
|
|
8947
|
+
{ cmd: 'constraints list|check|add', desc: '플랫폼/API 제약 (1.9.208)', descEn: 'platform/API constraints (1.9.208)' },
|
|
8948
|
+
{ cmd: 'provider list|add|remove|sync', desc: 'Provider Registry (1.9.157~160)', descEn: 'provider registry (1.9.157~160)' },
|
|
8949
|
+
{ cmd: 'commands [--json]', desc: '전체 명령 카테고리 목록 (1.9.233)', descEn: 'full command catalog by category (1.9.233)' },
|
|
8950
|
+
{ cmd: 'migrate audit|apply|plan [path] [--json] [--yes]', desc: '크로스버전 마이그레이션 진단/적용/플랜 (UR-0075, 1.9.356~358)', descEn: 'cross-version migration diagnose/apply/plan (UR-0075, 1.9.356~358)' },
|
|
8951
|
+
{ cmd: 'migrate --guide', desc: 'AI 에이전트용 크로스버전 마이그레이션 가이드 (1.9.355)', descEn: 'cross-version migration guide for AI agents (1.9.355)' },
|
|
8952
|
+
{ cmd: 'doctor [--json]', desc: '설치/환경 진단 (1.9.315)', descEn: 'install/environment diagnosis (1.9.315)' },
|
|
8953
|
+
{ cmd: 'selftest [--json]', desc: '코어 함수 무결성 자가 검증 (1.9.258)', descEn: 'self-verify core function integrity (1.9.258)' },
|
|
8900
8954
|
// 1.36.101 (명령 표면 감사): 둘 다 실행되고 문서에도 있는데 이 목록에만 없었다 — '있는데 안 보이는 명령'.
|
|
8901
|
-
{ cmd: 'gate [path]', desc: 'CI 한 번 호출 게이트 — verify + audit + scan + encoding + lazy' },
|
|
8902
|
-
{ cmd: 'toggle get|set|list <기능> [on|off]', desc: '기능 토글 (gate/lens/auto-graph/delegation-brief) — leerness.html ⚙ 탭과 연동 (1.36.30)' },
|
|
8903
|
-
{ cmd: 'install-safety [--json]', desc: '설치 안전 프로필 — 0 deps/0 install-script (1.9.359)' },
|
|
8904
|
-
{ cmd: 'env check|sync|detect [path]', desc: '셸/인코딩 환경 진단 (1.9.241)' },
|
|
8905
|
-
{ cmd: 'shell-guard "<command>"', desc: '셸 호환성 정적 분석 (1.9.260)' },
|
|
8906
|
-
{ cmd: 'path-setup [--apply]', desc: 'CLI PATH 자동 등록 (1.9.254)' }
|
|
8955
|
+
{ cmd: 'gate [path]', desc: 'CI 한 번 호출 게이트 — verify + audit + scan + encoding + lazy', descEn: 'single-call CI gate — verify + audit + scan + encoding + lazy' },
|
|
8956
|
+
{ cmd: 'toggle get|set|list <기능> [on|off]', cmdEn: 'toggle get|set|list <feature> [on|off]', desc: '기능 토글 (gate/lens/auto-graph/delegation-brief) — leerness.html ⚙ 탭과 연동 (1.36.30)', descEn: 'feature toggles (gate/lens/auto-graph/delegation-brief) — synced with the leerness.html ⚙ tab (1.36.30)' },
|
|
8957
|
+
{ cmd: 'install-safety [--json]', desc: '설치 안전 프로필 — 0 deps/0 install-script (1.9.359)', descEn: 'install safety profile — 0 deps / 0 install scripts (1.9.359)' },
|
|
8958
|
+
{ cmd: 'env check|sync|detect [path]', desc: '셸/인코딩 환경 진단 (1.9.241)', descEn: 'shell/encoding environment diagnosis (1.9.241)' },
|
|
8959
|
+
{ cmd: 'shell-guard "<command>"', desc: '셸 호환성 정적 분석 (1.9.260)', descEn: 'static shell-compatibility analysis (1.9.260)' },
|
|
8960
|
+
{ cmd: 'path-setup [--apply]', desc: 'CLI PATH 자동 등록 (1.9.254)', descEn: 'register the CLI on PATH automatically (1.9.254)' }
|
|
8907
8961
|
],
|
|
8908
8962
|
ops: [
|
|
8909
|
-
{ cmd: 'feature add|link|impact|list|show', desc: '기능 그래프(feature-graph) 추적' },
|
|
8910
|
-
{ cmd: 'permissions list|set', desc: 'agent 권한 모드 (1.9.174)' },
|
|
8911
|
-
{ cmd: 'capabilities [--json]', desc: '권한·보안 표면 공개 (1.9.272)' },
|
|
8912
|
-
{ cmd: 'creds list|register|check|refresh', desc: '크리덴셜 메타 추적 (값 미저장)' },
|
|
8913
|
-
{ cmd: 'incident list|show|handle', desc: '인시던트 관리' },
|
|
8914
|
-
{ cmd: 'webhook serve', desc: '웹훅 수신 서버' },
|
|
8915
|
-
{ cmd: 'deploy auto', desc: '배포 자동화' },
|
|
8916
|
-
{ cmd: 'runs list|show', desc: '실행 이력' },
|
|
8917
|
-
{ cmd: 'whats-new [path]', desc: '최근 버전 변경 요약' },
|
|
8918
|
-
{ cmd: 'team list|add|show|remove|preview|deploy <id> [--name --purpose --personas --members --schedule --task --deploy --yes]', desc: '에이전트 팀 정의 + preview(dry-run) + deploy(2중 게이트) — UR-0073 A~D, opt-in · 배포는 --yes+LEERNESS_TEAM_DEPLOY=1' }
|
|
8963
|
+
{ cmd: 'feature add|link|impact|list|show', desc: '기능 그래프(feature-graph) 추적', descEn: 'feature-graph tracking' },
|
|
8964
|
+
{ cmd: 'permissions list|set', desc: 'agent 권한 모드 (1.9.174)', descEn: 'agent permission mode (1.9.174)' },
|
|
8965
|
+
{ cmd: 'capabilities [--json]', desc: '권한·보안 표면 공개 (1.9.272)', descEn: 'disclose the permission/security surface (1.9.272)' },
|
|
8966
|
+
{ cmd: 'creds list|register|check|refresh', desc: '크리덴셜 메타 추적 (값 미저장)', descEn: 'credential metadata tracking (values never stored)' },
|
|
8967
|
+
{ cmd: 'incident list|show|handle', desc: '인시던트 관리', descEn: 'incident management' },
|
|
8968
|
+
{ cmd: 'webhook serve', desc: '웹훅 수신 서버', descEn: 'webhook receiver server' },
|
|
8969
|
+
{ cmd: 'deploy auto', desc: '배포 자동화', descEn: 'deployment automation' },
|
|
8970
|
+
{ cmd: 'runs list|show', desc: '실행 이력', descEn: 'run history' },
|
|
8971
|
+
{ cmd: 'whats-new [path]', desc: '최근 버전 변경 요약', descEn: 'summary of recent version changes' },
|
|
8972
|
+
{ cmd: 'team list|add|show|remove|preview|deploy <id> [--name --purpose --personas --members --schedule --task --deploy --yes]', desc: '에이전트 팀 정의 + preview(dry-run) + deploy(2중 게이트) — UR-0073 A~D, opt-in · 배포는 --yes+LEERNESS_TEAM_DEPLOY=1', descEn: 'define agent teams + preview (dry-run) + deploy (double gate) — UR-0073 A~D, opt-in · deploy needs --yes + LEERNESS_TEAM_DEPLOY=1' }
|
|
8919
8973
|
]
|
|
8920
8974
|
};
|
|
8975
|
+
// 1.36.111 (T-0092): 영어 모드에서 이 명령이 **출력 218줄 중 105줄**을 한국어로 냈다 — 단일 최대 누출원이었다.
|
|
8976
|
+
// leerness 는 한국어 우선 도구가 아니다. 카탈로그에 descEn 을 두고 여기서 고른다(없으면 desc 로 폴백 —
|
|
8977
|
+
// 새 항목이 번역 없이 들어와도 목록에서 사라지지 않게. 누락은 e2e 래칫이 잡는다).
|
|
8978
|
+
// ⚠ 선언은 `--json` 조기 반환 **앞**에 있어야 한다 — 뒤에 두면 JSON 경로에서 TDZ 로 죽는다(작성 중 실측).
|
|
8979
|
+
const _cL = _uiLang(root) === 'en';
|
|
8980
|
+
const ct = (ko, en) => (_cL ? en : ko);
|
|
8981
|
+
// 1.36.111 (codex 검수 P1, 재현됨): 카탈로그를 그대로 직렬화하면 `descEn`/`cmdEn` 이 payload 에 실려
|
|
8982
|
+
// **70.5% 커진다**(12,786 → 21,795 B · ~3,875 → ~6,605 tok, 실측). 이 payload 는 MCP 로 에이전트에 나가므로
|
|
8983
|
+
// 그대로 컨텍스트 비용이다. 항목 스키마는 종전과 같은 `{cmd, desc}` 로 유지하고 **값만** 언어에 맞춘다.
|
|
8984
|
+
// 검수의 "기계 계약을 암묵적으로 로케일에 흔들지 마라" 지적은 타당하다 — 그래서 흔들리는 사실을
|
|
8985
|
+
// `lang` 필드로 **명시**한다. 소비자는 어느 언어의 payload 인지 알 수 있고, 크기는 늘지 않는다.
|
|
8921
8986
|
if (has('--json')) {
|
|
8922
|
-
|
|
8987
|
+
const _lang = _cL ? 'en' : 'ko';
|
|
8988
|
+
const _localized = Object.fromEntries(Object.entries(cats).map(([k, list]) => [k, list.map(c => ({
|
|
8989
|
+
cmd: _cL ? (c.cmdEn || c.cmd) : c.cmd,
|
|
8990
|
+
desc: _cL ? (c.descEn || c.desc) : c.desc,
|
|
8991
|
+
}))]));
|
|
8992
|
+
log(JSON.stringify({ version: VERSION, lang: _lang, totalCommands: Object.values(cats).reduce((s, a) => s + a.length, 0), categories: _localized }, null, 2));
|
|
8923
8993
|
return;
|
|
8924
8994
|
}
|
|
8925
|
-
log(cy(`# leerness commands (1.9.233) — 전체 CLI 명령 카테고리
|
|
8995
|
+
log(cy(ct(`# leerness commands (1.9.233) — 전체 CLI 명령 카테고리 목록`, `# leerness commands (1.9.233) — full CLI command catalog by category`)));
|
|
8926
8996
|
log('');
|
|
8927
8997
|
for (const [catName, list] of Object.entries(cats)) {
|
|
8928
|
-
|
|
8998
|
+
// 단위 표기도 언어별로 — 여기서 `개` 를 통째로 뺐다가 한국어 출력이 바뀌는 회귀를 냈다(직전 커밋과 바이트 비교로 잡음).
|
|
8999
|
+
log(gr(ct(`## ${catName} (${list.length}개)`, `## ${catName} (${list.length})`)));
|
|
8929
9000
|
list.forEach(c => {
|
|
8930
|
-
|
|
8931
|
-
log(
|
|
9001
|
+
// 자리표시자도 표면이다 — `<명령>`·`<기능>` 같은 한국어 placeholder 가 영어 사용법에 남으면 그대로 누출이다.
|
|
9002
|
+
log(` • ${_cL ? (c.cmdEn || c.cmd) : c.cmd}`);
|
|
9003
|
+
log(dm(` ${_cL ? (c.descEn || c.desc) : c.desc}`));
|
|
8932
9004
|
});
|
|
8933
9005
|
log('');
|
|
8934
9006
|
}
|
|
8935
|
-
|
|
8936
|
-
log(dm(` →
|
|
8937
|
-
log(dm(` →
|
|
9007
|
+
const _tot = Object.values(cats).reduce((s, a) => s + a.length, 0);
|
|
9008
|
+
log(dm(ct(` → 총 ${_tot} 명령 (${Object.keys(cats).length} 카테고리)`, ` → ${_tot} commands in ${Object.keys(cats).length} categories`)));
|
|
9009
|
+
log(dm(ct(` → MCP 도구: ${_mcpToolCount()} (외부 AI 노출)`, ` → MCP tools: ${_mcpToolCount()} (exposed to external AI)`))); // 1.9.315 (UR-0054): 하드코딩 65 → 동적
|
|
9010
|
+
log(dm(ct(` → 빠른 시작: leerness pulse | handoff | health`, ` → quick start: leerness pulse | handoff | health`)));
|
|
8938
9011
|
}
|
|
8939
9012
|
|
|
8940
9013
|
// 1.9.374 (UR-0074): release cadence — 릴리스 빈도 진단 + 권장 (외부리뷰 반복 지적 "릴리스 케이던스 과다" 가시화). git tag 재사용, 읽기 전용.
|
|
@@ -15513,11 +15586,17 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
15513
15586
|
// 판정 기준은 **실행 확증**이어야 한다 — 실제로 돌았고 실패하지 않았는가(allPassed = exit 0 이며 파싱됐다면 전부 통과).
|
|
15514
15587
|
const _runProven = !!(runResult && !runResult.skipped && runResult.allPassed);
|
|
15515
15588
|
const unverifiableClaim = claimsChecked && files.length === 0 && !_runProven;
|
|
15516
|
-
let optimismSuspects = [], honestyFindings = [], strictOk = true;
|
|
15589
|
+
let optimismSuspects = [], honestyFindings = [], honestyAdvisory = [], strictOk = true;
|
|
15517
15590
|
if (claimsChecked) {
|
|
15518
15591
|
const codeText = _scanCodeForPatterns(root);
|
|
15519
15592
|
optimismSuspects = _detectOptimism(evidence, codeText);
|
|
15520
|
-
|
|
15593
|
+
// 1.36.110 (T-0099): 여기 도달했다는 것은 이미 완료 주장(done/--require-evidence/--strict-claims)이라는 뜻이다.
|
|
15594
|
+
// 그 사실을 넘겨 산문에서 완료 낱말을 다시 찾는 중복 요구를 없앤다(그것이 재현율 병목이었다).
|
|
15595
|
+
const _hAll = _epistemicHonestyCheck(evidence, { claimedDone: true }).findings;
|
|
15596
|
+
honestyFindings = _hAll.filter(f => f.severity === 'high');
|
|
15597
|
+
// 미검증 자백은 **권고**다 — 게이팅하지 않는다(왜 그렇게 정했는지는 lib/analyzers.js 주석 참조).
|
|
15598
|
+
// 다만 보이지 않으면 탐지한 의미가 없으므로 사람 출력과 --json 에 함께 싣는다.
|
|
15599
|
+
honestyAdvisory = _hAll.filter(f => f.severity !== 'high');
|
|
15521
15600
|
strictOk = optimismSuspects.length === 0 && honestyFindings.length === 0;
|
|
15522
15601
|
}
|
|
15523
15602
|
const gitStrongMismatch = gitApplicable && claimedInGit.length === 0; // 변경 있는데 주장 파일이 git 변경에 전무
|
|
@@ -15574,7 +15653,8 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
15574
15653
|
},
|
|
15575
15654
|
stubFiles: stubFiles.slice(0, 10),
|
|
15576
15655
|
evidence: { required: mustHaveEvidence, ...evq },
|
|
15577
|
-
|
|
15656
|
+
// 1.36.110: `advisory` 는 **게이팅하지 않는** 신호다(ok 에 영향 없음). 소비자가 경계를 오인하지 않도록 키를 분리한다.
|
|
15657
|
+
claims: !claimsChecked ? null : { ok: strictOk, optimism: optimismSuspects.map(s => ({ kind: s.kind, label: s.label })), honesty: honestyFindings.map(f => ({ dim: f.dim, label: f.label })), advisory: honestyAdvisory.map(f => ({ dim: f.dim, label: f.label, severity: f.severity })) },
|
|
15578
15658
|
git: gitChanged === null ? { applicable: false, reason: 'not-a-git-repo' } : (!gitApplicable ? { applicable: false, reason: 'no-working-changes-or-no-claimed-files' } : { applicable: true, claimedInGit: claimedInGit.length, claimedNotInGit, strongMismatch: gitStrongMismatch, changedNotClaimed }),
|
|
15579
15659
|
scopeCreep: !gitApplicable ? null : { count: changedNotClaimed.length, files: changedNotClaimed.slice(0, 10) } // 1.13.2 (Karpathy 외과적 변경): 요청/주장 밖 변경 파일(advisory)
|
|
15580
15660
|
};
|
|
@@ -15673,6 +15753,11 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
15673
15753
|
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`));
|
|
15674
15754
|
for (const f of honestyFindings) log(` · [${t('정직성', 'honesty')}:${f.dim}] ${f.label}: ${f.detail}`);
|
|
15675
15755
|
}
|
|
15756
|
+
// 1.36.110 (T-0099): 권고는 통과/실패와 **무관하게** 보여 준다 — 안 보이면 탐지한 의미가 없다.
|
|
15757
|
+
// 차단하지 않는 이유(오차단 위험·우회 가능성)는 lib/analyzers.js 에 적었다.
|
|
15758
|
+
for (const f of honestyAdvisory) {
|
|
15759
|
+
log(t(` - ⓘ 권고(차단 아님) [${f.dim}] ${f.label}: ${f.detail}`, ` - ⓘ advisory (non-blocking) [${f.dim}] ${f.label}: ${f.detail}`));
|
|
15760
|
+
}
|
|
15676
15761
|
}
|
|
15677
15762
|
// 1.9.302 (UR-0042) + 1.11.2 (UR-0175): git diff 시맨틱 교차검증 — 주장 파일이 실제 git 변경에 있는가. gitClaimOk/gitStrongMismatch 는 상단 공유(done 기본 게이팅, --lenient 완화).
|
|
15678
15763
|
if (gitChanged === null) {
|
package/lib/analyzers.js
CHANGED
|
@@ -94,8 +94,15 @@ function _parseEvidenceStats(text) {
|
|
|
94
94
|
|
|
95
95
|
// 1.9.305 (사용자 명시): AI 인식론적 정직성 점검 — 모르는 걸 아는 척 / 정보 미수집 / 미검증 섣부른 판단 휴리스틱 탐지.
|
|
96
96
|
// 순수 함수(텍스트→findings). 휴리스틱 advisory — 단정/추정/외부참조 표현 vs 근거·수집 흔적 대조. opt-in 점검용.
|
|
97
|
-
|
|
97
|
+
// 1.36.110 (T-0099): `opts.claimedDone` — 호출부가 **이미 완료 주장임을 아는** 경우.
|
|
98
|
+
// verify-claim 은 status 가 done 인 행에만 이 검사를 돌린다(claimsChecked). 그 문맥에서
|
|
99
|
+
// "산문에 완료 낱말이 있는가" 를 다시 요구하는 건 중복이고, 실제로 그것이 재현율의 병목이었다 —
|
|
100
|
+
// 홀드아웃에서 놓친 6건 중 5건이 결론 어휘 미등록("마쳤/잡은/shipped/applied/gone")이었다.
|
|
101
|
+
// 완료 동의어를 두 언어로 계속 채우는 건 끝이 없다(휴리스틱 토끼굴). 호출부가 아는 사실을 받는다.
|
|
102
|
+
// 기본값은 종전 그대로 — `honesty-check --text` 같은 독립 표면의 동작은 바뀌지 않는다.
|
|
103
|
+
function _epistemicHonestyCheck(text, opts) {
|
|
98
104
|
const t = String(text || '');
|
|
105
|
+
const claimedDone = !!(opts && opts.claimedDone);
|
|
99
106
|
const findings = [];
|
|
100
107
|
// 공통: 근거/출처 흔적 (파일경로·URL·테스트결과·Exit·문서·api-skill·인용·조회 흔적)
|
|
101
108
|
const hasSource = /(?:[\w./-]+\.(?:js|ts|tsx|jsx|py|go|rs|rb|md|json|ya?ml|toml|sql|sh)\b)|https?:\/\/|\bExit\s*[:=]|\d+\s*\/\s*\d+\s*(?:통과|passed)|\b(?:passed|passing)\b|근거[::]|출처[::]|api-skill|공식\s*문서|문서\s*(?:확인|참조|에\s*따르면)|읽었|조회(?:함|했|함\b)|확인(?:함|했|됨)|grep|로그[::]/i.test(t);
|
|
@@ -103,9 +110,67 @@ function _epistemicHonestyCheck(text) {
|
|
|
103
110
|
const definitive = /(반드시|항상|언제나|무조건|확실(?:히|함|하게)|당연히|틀림없|100\s*%|always|never|guaranteed|definitely|obviously|certainly)/i.test(t);
|
|
104
111
|
if (definitive && !hasSource) findings.push({ dim: 'pretend-knowledge', severity: 'high', label: '근거 없는 단정', detail: '단정적 표현이 있으나 근거/출처(파일·문서·테스트·로그)가 없음 — 모르는 정보를 아는 척할 위험.' });
|
|
105
112
|
// 차원2: 미검증 섣부른 판단 — 추정 표현 + 완료/성공 결론인데 근거 없음
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
// 1.36.110 (T-0099): 이 판정은 **언어에 상관없이** 같아야 하는데 실측 재현율이 영어 2/4 · 한국어 0/8 이었다.
|
|
114
|
+
// 같은 뜻의 주장이 표기 언어에 따라 통과/차단이 갈리면 그건 판정이 아니라 우연이다
|
|
115
|
+
// (`should work, will test later` 는 잡고 `아마 동작할 겁니다. 나중에 테스트 예정.` 은 통과시켰다).
|
|
116
|
+
// 측정 결과 원인은 둘이었다:
|
|
117
|
+
// (a) 결론 어휘가 좁다 — `작동` 은 있는데 `동작` 이 없고, `수정했`·`반영`·`적용` 이 빠져 있었다.
|
|
118
|
+
// (b) **미검증 자백** 신호가 아예 없었다 — "테스트는 아직 안 돌렸지만 완료했습니다" 에는 추정 표현이
|
|
119
|
+
// 하나도 없다. 이건 헤지가 아니라 **자기모순**이라 헤지보다 강한 신호다.
|
|
120
|
+
// 강화 전에 FP 코퍼스를 먼저 고정했다(정직한 주장 무탐지 100%). `!hasSource` 가 그 방어선이고,
|
|
121
|
+
// 근거를 댄 주장은 어떤 표현을 쓰든 통과한다 — "20/20 통과 … 성능은 추후 측정 예정" 같은 정직한 문장이 그 예다.
|
|
122
|
+
// ⚠ 세 목록은 **언어 대칭**을 유지해야 한다. 한쪽에만 표현을 더하면 같은 뜻의 주장이 표기 언어에 따라
|
|
123
|
+
// 갈린다 — 실제로 1차 강화에서 4쌍이 갈렸고(ko HIT / en MISS 등) 대칭 검사가 그걸 잡았다.
|
|
124
|
+
// 새 표현을 넣을 때는 반대 언어의 짝을 같이 넣고, 코퍼스에 그 쌍을 추가한다.
|
|
125
|
+
// ⚠ 한국어 **음절 합성** 함정: `듯\s*(?:하|싶)` 는 "듯하다" 는 잡지만 "듯합니다" 는 못 잡는다 —
|
|
126
|
+
// 하 + ㅂ 이 한 음절 `합` 으로 합쳐지기 때문이다. 대칭 검사가 이 한 글자 차이로 ko/en 판정이 갈린 것을 잡았다.
|
|
127
|
+
// 한국어 표현을 넣을 때는 활용형(하/해/한/했/함/합)을 함께 적어야 한다.
|
|
128
|
+
const assumption = /(아마|추정|것\s*같|듯\s*(?:하|해|한|했|함|합|싶)|probably|likely|maybe|perhaps|i\s*(?:think|assume|guess|believe|suppose)|seems?\s*to|appears?\s*to|looks?\s*(?:like|good|fine|ok|resolved|fixed|done)|should\s*(?:work|be|pass|fix)|expect(?:ed|s)?\s*to|생각(?:됩니다|된다|함|돼)|일\s*것|예상(?:됩니다|된다|됨|돼)|짐작)/i.test(t);
|
|
129
|
+
// 미검증 자백 — "아직 안 했다 / 나중에 하겠다" 를 스스로 말하면서 완료를 주장하는 형태.
|
|
130
|
+
// `(?!\s*없)` 는 "검증 필요 없는 오타 수정" 같은 **반대 의미**를 배제한다(FP 코퍼스가 잡아냈다).
|
|
131
|
+
// ⚠ **미래 범위 표현은 넣지 않는다** — "추후 확인 예정"·"will check later" 는 두 가지를 뜻할 수 있다:
|
|
132
|
+
// (a) 이 작업을 검증하지 않았다 (b) 앞으로 더 볼 것이 남았다. 정규식으로 둘을 가를 수 없다.
|
|
133
|
+
// 자체 FP 헌트에서 그 표현들이 문서 수정·설정 변경·의존성 bump·revert 를 막았다(실측 4건) —
|
|
134
|
+
// 전부 정직한 완료 주장이다. 애매하면 통과시킨다: false-BLOCK 이 false-PASS 보다 나쁘다.
|
|
135
|
+
// 여기 남기는 것은 **이미 일어난 일에 대한 부정**뿐이다("아직 안 돌렸다"·"not tested yet"·"unchecked").
|
|
136
|
+
const unverified = /(아직\s*(?:안|못)\s*(?:돌|해|확인|테스트|검증)|테스트\s*(?:는\s*)?(?:아직|안\s*(?:했|돌))|확인(?:은|을)?\s*(?:못|안)\s*(?:해|했|함)|안\s*(?:봤|돌렸|해봤)|미확인|not\s*(?:yet\s*)?(?:tested|verified|checked)|have\s*not\s*(?:tested|verified|checked)|untested|unverified|unchecked|yet\s*to\s*(?:test|verify|check)|todo\s*:?\s*(?:verify|test))/i.test(t);
|
|
137
|
+
const conclusion = /(완료|done|성공|통과|해결(?:됨|된|했|함|되었)|fixed|resolv(?:e|es|ed)|works?\b|complete[ds]?\b|implement(?:ed|s|ation)?\b|작동(?:함|한다|됨|할)|동작(?:함|한다|됨|할)|구현(?:됨|했|완료)|수정(?:됨|했|완료)|반영(?:됨|했|완료)|적용(?:됨|했|완료)|처리(?:됨|했|완료)|고쳤|고쳐(?:짐|졌)|끝냈|마무리(?:함|했|됨))/i.test(t);
|
|
138
|
+
// 1.36.110 (행위 가드가 순수 측정의 오류를 잡았다): `hasSource` 는 "파일을 언급했는가" 다.
|
|
139
|
+
// 그런데 verify-claim 의 evidence 는 **거의 항상 파일을 인용한다**(안 하면 files-missing/unverifiable 로 이미 막힌다) —
|
|
140
|
+
// 그래서 이 판정이 정작 필요한 문맥에서 도달 불가였다(실제 done 이력 139건 발화 0 의 진짜 이유).
|
|
141
|
+
// "아직 테스트 안 했다" 를 반박하는 것은 파일 이름이 아니라 **실행 증거**다. done 문맥에서만 기준을 그것으로 좁힌다.
|
|
142
|
+
// 기본 표면(`honesty-check --text`)의 판정은 종전 그대로 — 범위를 넘어 조이지 않는다.
|
|
143
|
+
// ⚠ 러너 **이름을 열거하지 않는다**. 처음엔 npm/pytest/cargo/jest… 를 나열했는데, 자체 FP 헌트에서
|
|
144
|
+
// Gradle · .NET · RSpec · PHPUnit · Elixir · Swift · 커스텀 스크립트가 **실제로 테스트를 돌렸는데도** 막혔다.
|
|
145
|
+
// 생태계마다 러너를 채우는 건 끝이 없다(완료 동의어와 같은 토끼굴). 러너 이름이 아니라
|
|
146
|
+
// **실행/결과의 흔적**을 본다: 실행 마커(Exit/Command) · 결과 수치 · 테스트 어휘 + 실행/통과 낱말.
|
|
147
|
+
const execMarker = /\bExit\s*[:=]|\bCommand\s*[:=]|exit\s*code/i.test(t);
|
|
148
|
+
const testOutcome = /\d+\s*(?:\/\s*\d+\s*)?(?:통과|passed|passing|성공)|\ball\s+(?:tests?\s+)?(?:passed|green)\b|BUILD\s+SUCCESS/i.test(t);
|
|
149
|
+
// 테스트/검증 어휘가 **실행·통과 낱말과 가까이** 있으면 실행 흔적으로 본다(러너 무관).
|
|
150
|
+
// ⚠ **부정을 건너뛰면 안 된다**: 처음엔 단순히 "테스트 낱말 … 실행/통과" 로 잡았는데,
|
|
151
|
+
// 그러면 `테스트는 아직 안 돌렸지만` 이 "테스트를 돌렸다" 로 읽혀 이 라운드의 대표 케이스가 통과했다.
|
|
152
|
+
// 사이에 부정어(안/못/않/없/not/no)가 오면 실행 흔적으로 세지 않는다(tempered 매칭).
|
|
153
|
+
// 부정어는 **뒤에 공백이 오는 것만** 본다 — "안정성"·"못지않게" 같은 낱말을 부정으로 오인하면
|
|
154
|
+
// 실행 흔적을 못 세고, 그건 곧 정직한 주장의 오차단으로 이어진다.
|
|
155
|
+
const NEG = '(?:(?!안\\s|못\\s|않|없|\\bnot\\b|\\bno\\b)[^\\n])';
|
|
156
|
+
const testWords = 'test|spec|unit|e2e|integration|verify|테스트|스펙|검증|점검';
|
|
157
|
+
const ranWords = '실행|돌렸|돌림|통과|성공|passed|passing|ran\\b|run\\b|green|success';
|
|
158
|
+
const testRun = new RegExp(`(?:${testWords})${NEG}{0,24}(?:${ranWords})`, 'i').test(t)
|
|
159
|
+
|| new RegExp(`(?:실행|ran|run)${NEG}{0,24}(?:${testWords})`, 'i').test(t);
|
|
160
|
+
const ranSomething = execMarker || testOutcome || testRun;
|
|
161
|
+
const refuted = claimedDone ? ranSomething : hasSource;
|
|
162
|
+
// 종전 차원(추정 표현 + 결론 + 근거 없음)은 **그대로** 유지한다 — 기존 사용자의 게이팅 동작을 바꾸지 않는다.
|
|
108
163
|
if (assumption && conclusion && !hasSource) findings.push({ dim: 'premature-judgment', severity: 'high', label: '검증 없는 섣부른 판단', detail: '가정·추정 표현과 완료·성공 결론이 함께 있으나 검증 근거가 없음 — 검증 없이 섣부르게 판단할 위험.' });
|
|
164
|
+
// 1.36.110 (T-0099) — **권고(medium)로 낸다. 차단하지 않는다.** 왜 그렇게 정했는지 남긴다:
|
|
165
|
+
// 이 신호를 차단으로 쓰려고 두 라운드를 넓혔는데, 자체 FP 헌트가 6건(실제로 테스트를 돌린 Gradle·.NET·
|
|
166
|
+
// RSpec·PHPUnit·Elixir·Swift 등)을, 외부 검수가 3건을 더 찾았다. 근본 이유 셋:
|
|
167
|
+
// (a) 직접 자백 표현이 두 언어에서 유계가 아니다("아직 테스트를 수행하지 않았습니다" 같은 어순 변형).
|
|
168
|
+
// (b) "검증 안 함" · "부분만 검증" · "해당 없음(문서 수정)" 을 정규식으로 가를 수 없다.
|
|
169
|
+
// (c) evidence 에 `Exit: 0` 한 줄만 적어도 반박된다 — 차단은 쉽게 우회되고, 그만큼 일관되지 못하다.
|
|
170
|
+
// 가끔 정직한 작업을 막고 자주 놓치는 게이트는 **명확한 권고보다 나쁘다**. 차단으로 올리려면
|
|
171
|
+
// 산문 파싱이 아니라 도구 자신의 실행 증거(`--run-tests` 결과)를 근거로 삼아야 한다 — 그건 별도 과제다.
|
|
172
|
+
// `unverified` 는 호출부가 done 을 보증할 때만 본다(기본 표면 동작 불변 약속을 지키기 위해).
|
|
173
|
+
if (claimedDone && unverified && !refuted) findings.push({ dim: 'unverified-admission', severity: 'medium', label: '미검증 자백', detail: '완료로 표시했으나 evidence 가 스스로 "아직 확인하지 않았다"고 말하며 실행 증거(테스트 결과·Exit·실행 흔적)가 없음 — 차단하지는 않되 재확인 권장.' });
|
|
109
174
|
// 차원3: 정보 미수집 — 외부 API/라이브러리/버전/스펙 언급인데 수집·근거 흔적 없음
|
|
110
175
|
// \bAPI\b(?!\.[a-z]) 로 파일경로(api.js/api.ts) 오탐 제외. 강한 근거(hasSource)나 수집 흔적(gathered) 있으면 통과.
|
|
111
176
|
const externalRef = /(\bAPI\b(?!\.[a-z])|\bSDK\b|라이브러리|\blibrary\b|\bpackage\b|엔드포인트|\bendpoint\b|버전\s*\d|v\d+\.\d+|\bspec\b|rate\s*limit|레이트\s*리밋|문서에\s*따르면)/i.test(t);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.111",
|
|
4
4
|
"description": "The AI-coding operations layer that makes \"done\" require evidence — persistent memory, evidence-gated completion checks, and clean handoffs for any AI agent (Claude Code, Codex, Cursor). State lives as plain files in your repo. CLI + MCP, 0 runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"leerness",
|
package/scripts/e2e.js
CHANGED
|
@@ -10457,6 +10457,154 @@ total++;
|
|
|
10457
10457
|
if (!ok) failed++;
|
|
10458
10458
|
}
|
|
10459
10459
|
|
|
10460
|
+
// ── 1.36.110 블록 N (T-0099): 미검증 자백이 done 을 통과시키던 것.
|
|
10461
|
+
// 기준선 실측: 재현율 1/16(6%) — `should work, will test later` 는 잡고 같은 뜻의 한국어는 0/8 이었다.
|
|
10462
|
+
// 같은 뜻의 주장이 표기 언어에 따라 통과/차단이 갈리면 그건 판정이 아니라 우연이다.
|
|
10463
|
+
// 구조적 병목은 "산문에 완료 낱말이 있는가" 를 다시 요구한 것이었다 — verify-claim 은 이미 done 행에만
|
|
10464
|
+
// 이 검사를 돌린다. 호출부가 아는 사실(claimedDone)을 넘겨 중복 요구를 없앴다.
|
|
10465
|
+
// FP 를 먼저 고정했다: 실제 done 이력 139건 발화 0 · 홀드아웃 정직 주장 8/8 무탐지.
|
|
10466
|
+
{
|
|
10467
|
+
total++;
|
|
10468
|
+
let ok = false; const dbg = {};
|
|
10469
|
+
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-honest110-'));
|
|
10470
|
+
try {
|
|
10471
|
+
const ENV = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb });
|
|
10472
|
+
let seq = 0;
|
|
10473
|
+
const mk = (setup) => {
|
|
10474
|
+
const d = path.join(sb, 'p' + (++seq));
|
|
10475
|
+
fs.mkdirSync(d, { recursive: true });
|
|
10476
|
+
fs.writeFileSync(path.join(d, 'package.json'), '{"name":"p","version":"0.1.0"}');
|
|
10477
|
+
cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes'], { cwd: d, encoding: 'utf8', timeout: 300000, env: ENV });
|
|
10478
|
+
if (setup) setup(d); return d;
|
|
10479
|
+
};
|
|
10480
|
+
const R = (d, a) => cp.spawnSync(process.execPath, [CLI, ...a, '--path', d], { cwd: d, encoding: 'utf8', timeout: 300000, env: ENV });
|
|
10481
|
+
const W = (d, rel, body) => { const p = path.join(d, rel); fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, body); };
|
|
10482
|
+
const verdict = (d, ev, extra = []) => {
|
|
10483
|
+
const id = (String(R(d, ['task', 'add', '주장']).stdout || '').match(/T-\d{4,}/) || [])[0];
|
|
10484
|
+
R(d, ['task', 'update', id, '--status', 'done', '--evidence', ev]);
|
|
10485
|
+
const r = R(d, ['verify-claim', id, '--json', ...extra]);
|
|
10486
|
+
let j = null; try { j = JSON.parse(String(r.stdout || '')); } catch {}
|
|
10487
|
+
const adv = (j && j.claims && Array.isArray(j.claims.advisory)) ? j.claims.advisory : [];
|
|
10488
|
+
return { ok: j ? j.ok : null, reasons: j ? (j.reasons || []) : [], advisory: adv.map(a => a.dim), out: String(r.stdout || '') };
|
|
10489
|
+
};
|
|
10490
|
+
const impl = d => W(d, 'src/impl.js', 'function f(){return 1;}\nmodule.exports={f};\n');
|
|
10491
|
+
|
|
10492
|
+
// ① 미검증 자백 → **권고로 보고하되 차단하지 않는다**. ko/en 이 같은 판정이어야 한다(언어 대칭).
|
|
10493
|
+
// 차단하지 않기로 한 이유: 직접 자백 표현이 두 언어에서 유계가 아니고, "검증 안 함"과 "부분만 검증"과
|
|
10494
|
+
// "해당 없음(문서 수정)"을 정규식으로 가를 수 없으며, evidence 에 `Exit: 0` 한 줄이면 반박된다.
|
|
10495
|
+
// 자체 FP 헌트 6건 + 외부 검수 3건이 전부 그 경계에서 나왔다. 차단으로 올리려면 산문 파싱이 아니라
|
|
10496
|
+
// 도구 자신의 실행 증거(`--run-tests`)를 근거로 삼아야 한다(별도 과제).
|
|
10497
|
+
const vKo = verdict(mk(impl), 'src/impl.js 반영했는데 엣지 케이스는 안 봤습니다.');
|
|
10498
|
+
const vEn = verdict(mk(impl), 'src/impl.js applied; edge cases unchecked.');
|
|
10499
|
+
dbg.koAdvised = vKo.advisory.includes('unverified-admission');
|
|
10500
|
+
dbg.enAdvised = vEn.advisory.includes('unverified-admission');
|
|
10501
|
+
dbg.symmetric = dbg.koAdvised === dbg.enAdvised;
|
|
10502
|
+
dbg.notBlocking = vKo.ok === true && vEn.ok === true; // 권고는 게이팅하지 않는다
|
|
10503
|
+
dbg.visibleToUser = /권고|advisory/i.test(vKo.out); // 안 보이면 탐지한 의미가 없다
|
|
10504
|
+
|
|
10505
|
+
// ② 대조군 — 근거를 댄 정직한 주장은 권고조차 붙지 않는다.
|
|
10506
|
+
const vHonest = verdict(mk(impl), 'src/impl.js 구현. 테스트 12/12 통과 (Exit: 0).');
|
|
10507
|
+
dbg.honestClean = vHonest.ok === true && vHonest.advisory.length === 0;
|
|
10508
|
+
const vHedgedButSourced = verdict(mk(impl), 'src/impl.js 구현. 20/20 passed. 성능 개선 효과는 추후 측정 예정.');
|
|
10509
|
+
dbg.sourcedHedgeClean = vHedgedButSourced.ok === true && vHedgedButSourced.advisory.length === 0;
|
|
10510
|
+
// "검증 필요 없는" 은 반대 의미 · 미래 범위 표현은 의도적으로 잡지 않는다
|
|
10511
|
+
const vNegated = verdict(mk(impl), 'src/impl.js 오타 수정만. 검증 필요 없는 주석 변경. 12/12 통과.');
|
|
10512
|
+
dbg.negationClean = vNegated.ok === true && vNegated.advisory.length === 0;
|
|
10513
|
+
const vFuture = verdict(mk(impl), 'src/impl.js 설정값만 변경. 호환성은 추후 확인.');
|
|
10514
|
+
dbg.futureScopeClean = vFuture.ok === true && vFuture.advisory.length === 0;
|
|
10515
|
+
// 러너 이름을 열거하지 않는다 — 실행했는데 권고가 붙으면 사용자에게 잘못된 신호다
|
|
10516
|
+
const vGradle = verdict(mk(impl), 'src/impl.js 구현. ./gradlew test 통과. 엣지 케이스는 안 봤습니다.');
|
|
10517
|
+
dbg.unlistedRunnerClean = vGradle.ok === true && vGradle.advisory.length === 0;
|
|
10518
|
+
|
|
10519
|
+
// ③ 기존 차단 동작은 그대로 — 이 라운드가 게이팅을 바꾸지 않았다는 것을 고정한다.
|
|
10520
|
+
const vStub = verdict(mk(d => W(d, 'src/empty.js', '')), 'src/empty.js 구현 완료');
|
|
10521
|
+
dbg.existingGateIntact = vStub.ok === false && vStub.reasons.includes('stub-impl');
|
|
10522
|
+
|
|
10523
|
+
ok = dbg.koAdvised && dbg.enAdvised && dbg.symmetric && dbg.notBlocking && dbg.visibleToUser
|
|
10524
|
+
&& dbg.honestClean && dbg.sourcedHedgeClean && dbg.negationClean && dbg.futureScopeClean
|
|
10525
|
+
&& dbg.unlistedRunnerClean && dbg.existingGateIntact;
|
|
10526
|
+
} catch (e) { dbg.err = String(e && e.message).slice(0, 200); } finally { try { fs.rmSync(sb, { recursive: true, force: true }); } catch {} }
|
|
10527
|
+
console.log(ok ? '✓ N(1.36.110/T-0099) 미검증 자백을 **권고로** 보고(차단 아님) · ko/en 동일 · 사용자에게 노출 · 정직 주장·미래범위·목록밖 러너 무탐 · 기존 게이팅 불변'
|
|
10528
|
+
: '✗ 1.36.110 정직성 판정 실패 ' + JSON.stringify(dbg));
|
|
10529
|
+
if (!ok) failed++;
|
|
10530
|
+
}
|
|
10531
|
+
|
|
10532
|
+
// ── 1.36.111 블록 O (T-0092): 영어 모드에서 한국어가 새는 양을 **래칫**으로 고정한다.
|
|
10533
|
+
// leerness 는 한국어 우선 도구가 아니다(포지셔닝). 영어 사용자가 한국어 UI 를 보는 것은 결함이다.
|
|
10534
|
+
// 기준선 실측: 43개 명령 중 31개가 새고 누출 234줄. 이번 라운드에 `commands` 카탈로그를 이중언어화해
|
|
10535
|
+
// 234 → 134 로 줄였다. 나머지는 **부채**이고, 이 가드는 그 부채가 **늘지 못하게** 한다.
|
|
10536
|
+
// ⚠ baseline 은 목표가 아니라 측정값이다. 줄이는 방향으로만 갱신한다(갚은 뒤에는 반드시 조인다).
|
|
10537
|
+
{
|
|
10538
|
+
total++;
|
|
10539
|
+
let ok = false; const dbg = {};
|
|
10540
|
+
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-i18n111-'));
|
|
10541
|
+
try {
|
|
10542
|
+
const ENV = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_LANG: 'en' });
|
|
10543
|
+
const d = path.join(sb, 'proj');
|
|
10544
|
+
fs.mkdirSync(d, { recursive: true });
|
|
10545
|
+
fs.writeFileSync(path.join(d, 'package.json'), '{"name":"proj","version":"0.1.0"}');
|
|
10546
|
+
cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--language', 'en'], { cwd: d, encoding: 'utf8', timeout: 300000, env: ENV });
|
|
10547
|
+
const R = (a) => cp.spawnSync(process.execPath, [CLI, ...a, '--path', d], { cwd: d, encoding: 'utf8', timeout: 300000, env: ENV });
|
|
10548
|
+
// 상태를 조금 만든다 — 빈 프로젝트는 출력이 짧아 누출을 과소평가한다. 입력은 전부 영어.
|
|
10549
|
+
R(['task', 'add', 'Implement the parser']);
|
|
10550
|
+
R(['decision', 'add', 'Use JSON storage', '--why', 'simplest']);
|
|
10551
|
+
R(['plan', 'add', 'Ship v1']);
|
|
10552
|
+
const HANGUL = /[가-힣ㄱ-ㆎ]/;
|
|
10553
|
+
// 측정 대상은 **도구가 말하는 표면**이다. 사용자 데이터(프로젝트명·과거 기록)는 한국어여도 정상이므로
|
|
10554
|
+
// 여기서는 영어 입력만 넣은 새 프로젝트를 쓴다 — 나오는 한글은 전부 도구의 것이다.
|
|
10555
|
+
// codex 검수 P2(재현됨): 목록이 좁으면 누출이 **목록 밖으로 옮겨가** 래칫을 빠져나간다.
|
|
10556
|
+
// 커버리지를 넓히고, 아래에서 목록 크기 자체도 단언한다(누가 케이스를 지워 통과시키지 못하게).
|
|
10557
|
+
const CMDS = [['commands'], ['status'], ['health'], ['pulse'], ['check'], ['mode'], ['toggle', 'list'],
|
|
10558
|
+
['task', 'list'], ['decision', 'list'], ['plan', 'list'], ['lesson', 'list'], ['rule', 'list'],
|
|
10559
|
+
['lens'], ['route', 'feature'], ['milestones'], ['round-history'], ['memory', 'status'],
|
|
10560
|
+
['reuse-map'], ['preview', 'list'], ['feature', 'list'], ['requests', 'list'], ['roles', 'list'],
|
|
10561
|
+
['permissions', 'list'], ['insights'], ['agents', 'list'], ['constraints', 'list'],
|
|
10562
|
+
['provider', 'list'], ['skill', 'list'], ['next-action', 'list'], ['session-resume'],
|
|
10563
|
+
['idempotency', 'audit'], ['context', 'budget'], ['drift', 'check'], ['verify'],
|
|
10564
|
+
['encoding', 'check'], ['scan', 'secrets'], ['which'], ['glossary'], ['release', 'cadence']];
|
|
10565
|
+
let leaky = 0, produced = 0; const worst = [], silent = [];
|
|
10566
|
+
for (const a of CMDS) {
|
|
10567
|
+
const r = R(a);
|
|
10568
|
+
const out = String(r.stdout || '') + String(r.stderr || '');
|
|
10569
|
+
// codex 검수 P2(재현됨): 명령이 죽으면 출력이 없어 "누출 0" 이 되고, 그게 곧 통과였다 —
|
|
10570
|
+
// **깨진 명령이 래칫을 낮춰 주는** 형태다. 실패도 무출력도 그 자체로 실패로 본다.
|
|
10571
|
+
if (r.status === 0 && out.trim()) produced++; else silent.push(a.join(' ') + '(exit=' + r.status + ')');
|
|
10572
|
+
const n = out.split('\n').filter(l => HANGUL.test(l)).length;
|
|
10573
|
+
leaky += n;
|
|
10574
|
+
if (n) worst.push(a.join(' ') + ':' + n);
|
|
10575
|
+
}
|
|
10576
|
+
dbg.leaky = leaky; dbg.produced = produced; dbg.worst = worst.slice(0, 8); dbg.silent = silent;
|
|
10577
|
+
// 계측 판별력 — **모든** 프로브가 성공하고 출력을 내야 한다. 한 건이라도 조용하면 판정 불가다.
|
|
10578
|
+
dbg.probeAlive = silent.length === 0 && produced === CMDS.length;
|
|
10579
|
+
dbg.coverage = CMDS.length >= 39; // 케이스를 지워 통과시키는 우회 차단(줄이려면 근거를 적고 이 수도 함께 낮춰라)
|
|
10580
|
+
// 래칫: 지금 측정된 부채보다 **늘면 실패**. 줄어드는 것은 언제나 통과.
|
|
10581
|
+
// 1.36.111 실측. 처음엔 21개 명령만 재서 46 이었는데, 검수 지적으로 커버리지를 39개로 넓히자 **111** 이 드러났다 —
|
|
10582
|
+
// 좁은 목록의 기준선은 실제 부채보다 낙관적이었다. 줄인 만큼만 낮춘다(느슨하게 두면 그 안에서 조용히 썩는다).
|
|
10583
|
+
const BASELINE = 111;
|
|
10584
|
+
dbg.baseline = BASELINE;
|
|
10585
|
+
dbg.withinRatchet = leaky <= BASELINE;
|
|
10586
|
+
// 이번 라운드가 고친 표면은 **0 이어야** 한다 — 래칫과 별개로 회귀를 직접 막는다.
|
|
10587
|
+
const cmdOut = String(R(['commands']).stdout || '');
|
|
10588
|
+
dbg.commandsClean = cmdOut.split('\n').filter(l => HANGUL.test(l)).length === 0;
|
|
10589
|
+
// 한국어 사용자가 보던 것은 **바뀌면 안 된다**. 영어화하면서 `개` 단위를 양쪽에서 없애는 회귀를 냈고
|
|
10590
|
+
// 직전 커밋과의 바이트 비교가 잡았다. 여기서는 한국어 출력이 여전히 한국어 관례를 지키는지 단언한다.
|
|
10591
|
+
const koEnv = Object.assign({}, ENV, { LEERNESS_LANG: 'ko' });
|
|
10592
|
+
const koOut = String(cp.spawnSync(process.execPath, [CLI, 'commands', '--path', d], { cwd: d, encoding: 'utf8', timeout: 300000, env: koEnv }).stdout || '');
|
|
10593
|
+
dbg.koIntact = /## status \(\d+개\)/.test(koOut) && /총 \d+ 명령/.test(koOut) && HANGUL.test(koOut);
|
|
10594
|
+
// --json 은 기계 계약이다 — 기존 키(cmd/desc)가 살아 있고 카테고리 수가 그대로여야 한다(추가 키는 하위호환).
|
|
10595
|
+
let cj = null;
|
|
10596
|
+
try { cj = JSON.parse(String(R(['commands', '--json']).stdout || '')); } catch {}
|
|
10597
|
+
dbg.jsonContract = !!cj && typeof cj.totalCommands === 'number' && cj.totalCommands > 50
|
|
10598
|
+
&& Object.values(cj.categories).every(list => list.every(e => typeof e.cmd === 'string' && typeof e.desc === 'string'
|
|
10599
|
+
&& e.descEn === undefined && e.cmdEn === undefined)) // 내부 번역 키가 payload 로 새면 70% 부풀었다(검수 P1 실측)
|
|
10600
|
+
&& cj.lang === 'en'; // 로케일 의존을 **명시**한다 — 암묵적으로 흔들리지 않는다
|
|
10601
|
+
ok = dbg.probeAlive && dbg.coverage && dbg.withinRatchet && dbg.commandsClean && dbg.koIntact && dbg.jsonContract;
|
|
10602
|
+
} catch (e) { dbg.err = String(e && e.message).slice(0, 200); } finally { try { fs.rmSync(sb, { recursive: true, force: true }); } catch {} }
|
|
10603
|
+
console.log(ok ? `✓ O(1.36.111/T-0092) 영어 누출 ${dbg.leaky}줄 ≤ 래칫 ${dbg.baseline} · commands 표면 0 · 한국어 출력 불변 · --json 계약 유지 · 계측 살아있음`
|
|
10604
|
+
: '✗ 1.36.111 i18n 래칫 위반 ' + JSON.stringify(dbg));
|
|
10605
|
+
if (!ok) failed++;
|
|
10606
|
+
}
|
|
10607
|
+
|
|
10460
10608
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
10461
10609
|
if (failed > 0) process.exit(1);
|
|
10462
10610
|
|