leerness 1.33.0 → 1.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,77 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.34.0 — 2026-06-19 — 🛡️ [안정화/Stable] verify-claim + CI gate 슬라이스 강화 (CLI→CI→MCP 완결) 안정 minor
4
+
5
+ **🛡️ 안정화(Stable) minor — 직전 minor(1.33.0) 이후 누적된 패치 3건(1.33.1~1.33.3)을 검증·통합해 npm 공개.** R-0011 정책의 25번째 stable minor. **이번 minor 의 핵심은 차별화 슬라이스(verify-claim + CI gate)를 한 테마로 완결한 것** — 웹 Opus 4.8 외부리뷰가 "leerness 의 진짜 가치 = verify-claim + CI gate"라 짚었고, 사용자가 그 방향을 선택해 3라운드에 걸쳐 플래그십을 CLI → CI 게이트 → MCP 까지 연결했습니다. 한국어 우선 기본은 그대로.
6
+
7
+ ### 이번 minor 통합 (1.33.1~1.33.3)
8
+ - **🔒 CI 게이트 워크플로 production-grade (1.33.1)**: `leerness ci init` 생성 PR 게이트가 **leerness 버전 핀**(재현성·공급망 안전 — 새 릴리스가 게이트 판정을 조용히 못 바꿈) + **최소권한**(`permissions: contents: read`) + **중복 취소**(`concurrency` + `cancel-in-progress`). 맹신X: gate 본체는 이미 실제 시크릿을 차단(미수정, 헛수정 회피).
9
+ - **🔎 verify-claim --all (1.33.2)**: 플래그십을 per-task 전용 → **일괄**(`leerness verify-claim --all`)로. 모든 done/완료 주장을 한 명령으로 검증, 하나라도 불일치면 exit 1(CI·스케일). per-task 경로에 비침투 `opts.collect` 추가로 정밀 검사(스텁·부풀린 카운트·optimism) 그대로 재사용. 맹신X: 일괄 verdict ↔ per-task 일치 재현.
10
+ - **🔗 gate --claims + MCP (1.33.3)**: 일괄 검증을 강제·에이전트 표면에 연결 — `leerness gate --claims` opt-in 6번째 체크(기본 5체크 무변경, 회귀 0) + MCP `leerness_verify_claim_all`(86 도구). 비-exit 코어 `_verifyClaimsAll` 를 CLI·게이트가 공유. 맹신X: 게이트 5체크가 done 주장에 verify-claim 을 안 돌렸음을 소스 확인 → `--claims` 로 정밀화.
11
+
12
+ ### 안정화 표시 (R-0006)
13
+ - annotated tag `v1.34.0` (Stable) · GitHub release `--latest` · npm dist-tag `stable` 시도.
14
+ - **게시본 클린룸 재실증**: npm 공개본을 빈 임시폴더에 fresh 설치 → 버전 + 플래그십(verify-claim 거짓완료 차단 + gate --claims) 행위 재확인.
15
+
16
+ ### 검증 (회귀 0)
17
+ - selftest **259** · E2E **380** (양 minor 누적 가드 포함). bin+package.json 동시 bump 일치.
18
+ - **npm 공개**(minor — R-0011): 1.33.0 → 1.34.0. 누적 패치는 GitHub/CHANGELOG 에만 있던 것을 안정 버전으로 묶어 게시.
19
+
20
+ ## 1.33.3 — 2026-06-19 — 🔗 verify-claim --all → CI gate(opt-in) + MCP (양 표면 도달)
21
+
22
+ **🔗 차별화 슬라이스(verify-claim + CI gate) 견고화 3번째 — 1.33.2 의 일괄 검증을 실제 강제·에이전트 표면에 연결.** 1.33.2 가 CLI 에 `verify-claim --all` 을 추가했지만, ① CI **게이트**는 여전히 done 주장에 정밀 per-claim 검사를 안 돌렸고(거짓완료를 lazy/audit 휴리스틱으로만 차단), ② **MCP**(leerness 의 *주요* 에이전트 인터페이스)에는 일괄 검증 도구가 없었음(에이전트가 "내 완료 주장 전부 맞나?"를 한 번에 못 물음). 이 두 갭을 닫음.
23
+
24
+ ### 맹신X (양방향 재현)
25
+ - 게이트 5체크(verify/audit/scan/encoding/lazy) 중 **어느 것도 done 주장에 verify-claim 을 실행 안 함**을 소스로 확인 — README 의 "claims fail → cannot merge" 가 휴리스틱에 의존. `gate --claims` 로 정밀화.
26
+ - 회귀 0 재현: 클린 프로젝트는 기본 5체크/`--claims` 6체크 둘 다 통과 · 거짓완료는 기본 게이트 exit 1(기존 동작 유지) + `--claims` 의 6번째 `verify-claims` 체크가 명시적으로 ok:false. **human `--claims` 가 gate summary 까지 도달**(코어가 process.exit 안 해 step 집계 보호됨)을 검증.
27
+
28
+ ### 변경 (둘 다 additive·zero-regression)
29
+ - **`leerness gate --claims`** opt-in — 6번째 체크 `verify-claims`(= verify-claim --all 정밀 검사) 추가. **기본 게이트는 5체크 무변경**(기존 CI 어댑터 회귀 0). CI 가 README 약속을 문자 그대로 강제하려면 `--claims`.
30
+ - **MCP `leerness_verify_claim_all`** (85→86) — read-only. 응답 `{ ok, total, failed, results:[{id,ok,reasons}] }`. 에이전트가 세션 마감 전 모든 done 주장을 한 호출로 자가 점검.
31
+ - 구현: 비-exit 코어 `_verifyClaimsAll(root)` 추출(렌더/exit 없이 결과만) → CLI(`verify-claim --all`)·게이트(`--claims`)가 **동일 코어 공유**. 코어는 절대 `process.exit(` 안 함(게이트 프로세스 조기종료 방지 — selftest 가드).
32
+ - README: enforcement 섹션을 `gate --claims` 로 정밀화 + MCP 도구 수 86 갱신(en/ko).
33
+
34
+ ### 검증 (회귀 0)
35
+ - **selftest 259**(신규 1.33.3: 코어 exit 부재 + gate --claims 6번째 + MCP def↔case) · **E2E 380**(B(1.33.3): 클린 5/6 + 거짓완료 `--claims` 차단 + human summary 도달 + MCP `verify_claim_all` 라운드트립).
36
+ - patch(1.33.3) — npm 미배포(R-0011). bin+package.json 동시 bump 일치.
37
+
38
+ ## 1.33.2 — 2026-06-19 — 🔎 verify-claim --all (모든 done 주장 일괄 검증 — CI·스케일)
39
+
40
+ **🔎 차별화 슬라이스(verify-claim) 견고화 — 플래그십을 스케일로.** 직전 라운드(1.33.1)에 이어 verify-claim/gate 슬라이스를 강화. 종전 verify-claim 은 **per-task 전용**(`verify-claim <T-ID>`)이라 "내 완료 주장 전부가 증거와 맞는가?"를 한 명령으로 확인할 수 없었음. `--all` 추가로 모든 done/완료 주장을 일괄 검증 → CI·대규모 트래커에서 바로 사용. (사용자 방향 선택: verify-claim + CI gate 강화.)
41
+
42
+ ### 맹신X (양방향 재현 — 헛수정 회피)
43
+ - 직접 재현: 거짓완료(존재하지 않는 파일 주장 done) 프로젝트에서 `gate` 는 **이미** exit 1 로 차단(lazy/audit 휴리스틱) — 값 제안이 공허하지 않음을 확인 → "gate 가 주장을 안 본다"는 가설 **기각**. 단, `verify-claim --all`/no-id 는 둘 다 "T-ID 필요" 에러 → **일괄 검증 부재**가 진짜 갭.
44
+ - 일괄 verdict ↔ per-task 일치 검증: 거짓 task 는 `--all` 에서 ok:false(`files-missing`) + 개별 `verify-claim` 도 exit 1, 진실 task 는 양쪽 다 통과. 정밀 검사(스텁·부풀린 카운트·optimism)를 그대로 재사용(분기 없음).
45
+
46
+ ### 변경
47
+ - **`leerness verify-claim --all [--json]`** 신규 — progress-tracker 의 done/완료 주장을 전부 검증, 하나라도 불일치면 exit 1(CI 게이트). `--json`: `{ ok, total, failed, results:[{id,ok,reasons}] }`. 완료 주장 0건이면 통과(exit 0).
48
+ - 통과 플래그(`--run-tests`/`--strict-claims`/`--require-evidence`/`--lenient`/`--test-cmd`)는 전역이라 각 task 에 그대로 적용 — 기본은 정적 검사(빠름), `--run-tests` 시 각 task 테스트 실행.
49
+ - 구현: per-task 경로(`verifyClaimCmd`)에 `opts.collect` 비침투 추가 — 렌더/exit 없이 verdict 만 반환(기존 `--json`/human 게이팅 부울을 **동일 출처**로 공유). 일괄 모드는 thin orchestrator. **per-task 동작 무변경**(opts 없을 때 종전과 100% 동일).
50
+
51
+ ### 검증 (회귀 0)
52
+ - **selftest 258**(신규 1.33.2 가드: collect early-return·done 필터·라우팅·게이팅 부울 공유 + 7th헌트 not_found 윈도 {0,700}→{0,1200} 정합).
53
+ - **E2E 378→379**: 새 가드 B(1.33.2) — 거짓완료 일괄 차단(exit 1, `files-missing`)·진실완료/빈프로젝트 통과·per-task verdict 일치·human exit 1.
54
+ - patch(1.33.2) — npm 미배포(R-0011, 다음 minor 에 게시). bin+package.json 동시 bump + 일치 가드 통과.
55
+
56
+ ## 1.33.1 — 2026-06-17 — 🔒 verify-claim + CI gate 슬라이스 강화 (생성 워크플로 production-grade)
57
+
58
+ **🔒 차별화 슬라이스(verify-claim + CI gate) 견고화.** 웹 Opus 4.8 외부리뷰가 "가치의 대부분 + 버전을 핀하라"고 짚은 부분을 채택 — `leerness ci init` 이 생성하는 PR 게이트 워크플로를 production-grade 로. (사용자 방향 선택: self-dev 중 고가치 항목.)
59
+
60
+ ### 맹신X (gate 동작은 견고 — 헛수정 회피)
61
+ - 직접 재현: `gate` 는 5체크(verify/audit/scan-secrets/encoding/lazy) · `--json` 단일객체 집계(version/root/ok/total/failed/checks) · **실제 커밋 시크릿 차단**(exit 1, scan-secrets ok:false). AWS 문서 placeholder(`...EXAMPLE`)를 무시하는 건 정상(FP 회피)임을 확인 → gate 본체는 미수정.
62
+
63
+ ### 변경 (생성 워크플로 강화)
64
+ - **leerness 버전 핀**: `npx -y leerness gate .`(unpinned latest) → `npx -y leerness@<설치버전> gate .`. `ci init` 가 설치 버전을 주입 → **재현성·공급망 안전**(새 릴리스가 게이트 판정을 조용히 바꾸지 못함). 업데이트 안내 주석 + `ci init --force` 재생성.
65
+ - **최소 권한**: `permissions: contents: read` (least-privilege — gate 는 읽기·검증만).
66
+ - **중복 취소**: `concurrency` + `cancel-in-progress: true` (superseded PR run 자동 취소).
67
+ - **crisp CI 스토리**: `ci init` 성공 메시지에 핀/권한 + "branch protection 에서 required 지정 → guardrail 완성" 다음단계 명시. README ci 섹션도 1줄 보강.
68
+
69
+ ### 검증 (회귀 0)
70
+ - **selftest 257** (ci init 가드(3307)를 핀/permissions/concurrency 검사로 강화 + VERSION↔package.json 일치).
71
+ - **E2E 377→378**: 새 가드 B(1.33.1) — 버전핀(설치버전 일치)·미핀 latest 부재·`contents: read`·concurrency cancel.
72
+ - 생성 워크플로 **YAML 파싱 검증**(name/permissions/concurrency/run 구조 유효).
73
+ - patch(1.33.1) — npm 미배포(R-0011, 다음 minor 에 게시). bin+package.json 동시 bump + 일치 가드 통과.
74
+
3
75
  ## 1.33.0 — 2026-06-17 — 🛡️ [안정화/Stable] 15th리뷰 수정 + 정직성 calibration + A3 fencing 안정 minor
4
76
 
5
77
  **🛡️ 안정화(Stable) minor — 직전 minor(1.32.0) 이후 누적된 패치 3건(1.32.1~1.32.3)을 검증·통합해 npm 공개.** R-0011 정책의 24번째 stable minor. **이번 minor 의 핵심은 정직성 — 1.32.2 에서 calibrate 한 정직한 README/docs(self-administered 클린룸·성숙도 라벨)가 그동안 GitHub 에만 있었고 npm 에는 1.32.0 의 과장본이 남아 있었음. 1.33.0 이 정직한 게시본을 npm 에 올린다** (false-"done" 을 막는 도구가 자기 자신에 대해서도 과장하지 않도록). 한국어 우선 기본은 그대로.
package/README.ko.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  > **어떤 언어, 어떤 AI 에이전트로 작업하든 — "증거 없이는 끝났다고 말할 수 없게" 만드는 AI 코딩 운영 레이어.** 코드를 대신 쓰는 도구가 아니라, AI 에이전트의 **기억·인수인계·검증·감사·보안 가드**를 프로젝트에 영속화하는 CLI + MCP 서버입니다. (이 포지셔닝은 **자체 수행 AI 클린룸 평가** — AI 에이전트가 빈 임시폴더에 fresh 설치 후 행위만으로 검증, 검증기 적대공격 포함 — 로 점검했습니다. *제3자 인간 감사·동료심사가 아닙니다.* 방법론·결과·정직한 한계: [docs/clean-room-evaluations.md](./docs/clean-room-evaluations.md))
15
15
 
16
- [![npm](https://img.shields.io/npm/v/leerness)](https://www.npmjs.com/package/leerness) · ![MCP tools](https://img.shields.io/badge/MCP--tools-85-blue) · **런타임 의존성 0** · **install-script 0** · offline-first · Node ≥ 18 · MIT
16
+ [![npm](https://img.shields.io/npm/v/leerness)](https://www.npmjs.com/package/leerness) · ![MCP tools](https://img.shields.io/badge/MCP--tools-86-blue) · **런타임 의존성 0** · **install-script 0** · offline-first · Node ≥ 18 · MIT
17
17
 
18
18
  > 이 문서는 **AI 다중 모델(Codex / Claude Sonnet / Claude Opus)이 README 없이 leerness 를 직접 설치·실행·분석한 클린룸 리뷰**(행위 기반 — 제3자 인간 객관 감사는 아님)를 바탕으로 재구성됐습니다. 매 릴리스마다 자동 갱신됩니다.
19
19
 
@@ -112,7 +112,7 @@ leerness verify-claim T-0001 --require-evidence
112
112
  - **외부 에이전트**: `agents list/check/dispatch` · `provider` · `roles` · `adapter`
113
113
  - **운영/확장**: `release` · `migrate` · `team` · `install-safety` · `route` · `review`(페르소나)
114
114
  - **브리지(opt-in)**: `web`(playwright) · `pc`(robotjs) · `lsp`
115
- - **MCP**: `mcp serve` — stdio JSON-RPC 서버로 85개 도구 노출
115
+ - **MCP**: `mcp serve` — stdio JSON-RPC 서버로 86개 도구 노출 (verify-claim --all 일괄 검증 포함, 1.33.3)
116
116
 
117
117
  전체 명령은 `leerness commands` 또는 `leerness --help` 로 확인하세요.
118
118
 
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  > **The AI-coding operations layer that makes "done" require evidence — for any language, any AI agent.**
13
13
  > leerness does not write code. It gives your AI agent persistent memory, verified completion, and clean handoffs — stored inside your repo as plain files, exposed via CLI + MCP.
14
14
 
15
- [![npm](https://img.shields.io/npm/v/leerness)](https://www.npmjs.com/package/leerness) · ![MCP tools](https://img.shields.io/badge/MCP--tools-85-blue) · **0 runtime deps** · **0 install scripts** · offline-first · Node ≥ 18 · MIT
15
+ [![npm](https://img.shields.io/npm/v/leerness)](https://www.npmjs.com/package/leerness) · ![MCP tools](https://img.shields.io/badge/MCP--tools-86-blue) · **0 runtime deps** · **0 install scripts** · offline-first · Node ≥ 18 · MIT
16
16
 
17
17
  **🇰🇷 한국어 전문: [README.ko.md](./README.ko.md)**
18
18
 
@@ -49,7 +49,7 @@ You never have to type a command yourself. Paste this into Claude Code, Cursor,
49
49
 
50
50
  The agent installs and operates it for you — `leerness init` also writes the instructions into CLAUDE.md / AGENTS.md so future sessions pick them up automatically.
51
51
 
52
- Prefer pure natural language? leerness ships an **MCP server with 85 tools** (`leerness mcp serve`). Connect it once to Claude Desktop / Claude Code and just ask: *"what was I working on?"*, *"did the AI actually finish T-0001?"*
52
+ Prefer pure natural language? leerness ships an **MCP server with 86 tools** (`leerness mcp serve`). Connect it once to Claude Desktop / Claude Code and just ask: *"what was I working on?"*, *"did the AI actually finish T-0001?"*
53
53
 
54
54
  ---
55
55
 
@@ -77,7 +77,9 @@ By default leerness is **cooperative**: your AI agent runs the commands because
77
77
  leerness ci init # writes .github/workflows/leerness-gate.yml — runs `leerness gate` on every PR
78
78
  ```
79
79
 
80
- Then make that check **required** in GitHub branch protection. Now a PR that skips verification (or whose claims fail) **cannot merge** — the gate runs independently of the agent, returns a non-zero exit code, and blocks. That is the difference between a guideline and a guardrail.
80
+ The generated workflow is production-grade: it **pins the leerness version** (reproducible — the gate's verdict can't change from a silent upgrade), runs with **least-privilege permissions** (`contents: read`), and cancels superseded runs.
81
+
82
+ Then make that check **required** in GitHub branch protection. Now a PR that skips verification (or whose claims fail) **cannot merge** — the gate runs independently of the agent, returns a non-zero exit code, and blocks. That is the difference between a guideline and a guardrail. For exact per-claim enforcement, run `leerness gate --claims` — it adds a 6th check that runs `verify-claim` on **every** completed task and fails the gate if any "done" task's evidence doesn't match reality (the default 5-check gate already blocks false-done via heuristics; `--claims` makes it precise).
81
83
 
82
84
  ---
83
85
 
@@ -93,7 +95,7 @@ The asymmetry is what makes a trial reasonable anyway: MIT, **0 runtime dependen
93
95
 
94
96
  - **Memory** — `task` / `plan` / `decision` / `lesson` / `rule`: canonical JSON + markdown projections, archive/restore.
95
97
  - **Handoff** — `handoff` (session start context) · `session close` (closing report). Survives agent swaps.
96
- - **Verification** — `verify-claim` (evidence vs reality, stub/fake-test/inflated-count detection, `--run-tests --test-cmd` for any language) · `contract verify` (spec ↔ impl) · `gate` (one-call CI gate).
98
+ - **Verification** — `verify-claim` (evidence vs reality, stub/fake-test/inflated-count detection, `--run-tests --test-cmd` for any language; `--all` checks **every** completed claim at once for CI) · `contract verify` (spec ↔ impl) · `gate` (one-call CI gate).
97
99
  - **Audit** — `audit` · `lazy detect` · `drift check` keep the workspace honest over time.
98
100
  - **Security** — `scan secrets` (committed-secret detection) · `encoding check` (BOM/CP949) — also runs at `session close`.
99
101
 
@@ -112,7 +114,7 @@ MIT
112
114
  <!-- leerness:project-readme:start -->
113
115
  ## Leerness Project Harness
114
116
 
115
- 이 프로젝트는 Leerness v1.33.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
117
+ 이 프로젝트는 Leerness v1.34.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
116
118
 
117
119
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
118
120
 
@@ -166,7 +168,7 @@ leerness memory restore decision <date|title>
166
168
 
167
169
  ### MCP server (외부 AI 통합)
168
170
 
169
- Leerness v1.33.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
171
+ Leerness v1.34.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
170
172
 
171
173
  ```jsonc
172
174
  // 카테고리별
@@ -179,7 +181,7 @@ Leerness v1.33.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
179
181
  // • Workflow: session_close / agents_list / task_export / env_check / usage_stats / reuse_map / whats_new
180
182
 
181
183
  // MCP server 실행: leerness mcp serve
182
- // tools/list 응답: 85 도구
184
+ // tools/list 응답: 86 도구
183
185
  ```
184
186
 
185
187
  ### Autonomous mode (자율 모드)
@@ -187,7 +189,7 @@ Leerness v1.33.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
187
189
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
188
190
  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) 다음 라운드 예약.
189
191
 
190
- 현재 누적: **70 라운드 (1.9.40 → 1.33.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
192
+ 현재 누적: **70 라운드 (1.9.40 → 1.34.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
191
193
 
192
194
  ### 성능 가이드 (1.9.140 측정)
193
195
 
@@ -225,6 +227,6 @@ leerness release pack --close --auto-main-push
225
227
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
226
228
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
227
229
 
228
- Last synced by Leerness v1.33.0: 2026-06-17
230
+ Last synced by Leerness v1.34.0: 2026-06-19
229
231
  <!-- leerness:project-readme:end -->
230
232
 
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.33.0';
35
+ const VERSION = '1.34.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') 시 호스트 프로세스 오염.
@@ -2966,7 +2966,7 @@ function _selfTestCases() {
2966
2966
  { name: '6번째 외부평가/codex P1-A (UR-0098): install-safety 레시피 셸-무관 + hardeningNote (1.9.397)', run: () => { if (typeof installSafetyCmd !== 'function') return false; const save = process.argv; const _w = process.stdout.write; let out = ''; try { process.argv = ['node', 'h', 'install-safety', '--json']; process.stdout.write = s => { out += s; return true; }; installSafetyCmd({ json: true }); } catch {} finally { process.stdout.write = _w; process.argv = save; } let j; try { j = JSON.parse(out); } catch {} const noPosixPrefix = !!j && Array.isArray(j.safeInstall) && !j.safeInstall.some(x => /^npm_config_\w+=/.test(String(x).trim())); const crossShell = !!j && j.safeInstall.filter(x => String(x).includes('npx --yes')).length >= 2; const noteOk = !!j && typeof j.hardeningNote === 'string' && j.hardeningNote.includes('PowerShell'); return noPosixPrefix && crossShell && noteOk; } },
2967
2967
  { name: '6번째 외부평가/codex P1-C (UR-0099): --json 에러 경로 구조화 failJson + 와이어 (1.9.398)', run: () => { const io = require('../lib/io'); if (io.failJson !== failJson) return false; const _w = process.stdout.write; const saved = process.exitCode; let jOut = '', hOut = ''; let jExit = 0; try { process.stdout.write = s => { jOut += s; return true; }; process.exitCode = 0; failJson(true, 'tc', 'm'); jExit = process.exitCode; process.stdout.write = s => { hOut += s; return true; }; process.exitCode = 0; failJson(false, 'c', 'humanmsg'); } catch {} finally { process.stdout.write = _w; process.exitCode = saved; } let pj; try { pj = JSON.parse(jOut); } catch {} const jsonOk = !!pj && pj.ok === false && pj.code === 'tc' && pj.error === 'm' && jExit === 1; const humanOk = hOut.includes('✗') && hOut.includes('humanmsg') && !hOut.includes('{'); const src = read(__filename); const wired = src.includes("failJson(_j, 'missing_args'") && src.includes("failJson(_j, 'spec_not_found'"); return jsonOk && humanOk && wired; } },
2968
2968
  { name: '7번째 버그헌트 P1-A (UR-0104): 테이블셀 안전화 _cellSafe/_cellUnescape (파이프/개행 injection 차단) (1.9.399)', run: () => { const m = require('../lib/pure-utils'); if (m._cellSafe !== _cellSafe || m._cellUnescape !== _cellUnescape) return false; const safe = _cellSafe('fix | bug\nrow2'); const noRaw = !/(?<!\\)\|/.test(safe) && !/[\r\n]/.test(safe); const pipeRt = _cellUnescape(_cellSafe('a | b | c')) === 'a | b | c'; const nlGone = _cellSafe('a\nb') === 'a b'; const src = read(__filename); const wired = src.includes('_cellSafe(r.request)') && src.includes('_cellSafe(r.rule)'); return noRaw && pipeRt && nlGone && wired; } },
2969
- { name: '7번째 버그헌트 P1-B (UR-0105): verify-claim/optimism-check/honesty-check --json 에러 구조화 (1.9.400)', run: () => { const src = read(__filename); const vc = /function verifyClaimCmd[\s\S]{0,700}?failJson\(_j, 'not_found'/.test(src); const oc = /function optimismCheckCmd[\s\S]{0,700}?failJson\(_j, 'not_found'/.test(src); const hc = /function honestyCheckCmd[\s\S]{0,900}?failJson\(has\('--json'\), 'not_found'/.test(src); return vc && oc && hc; } }, // 1.30.5: {0,400}→{0,700} (F4 가 missing_args 라인을 en/ko 로 늘려 not_found 가 창 밖)
2969
+ { name: '7번째 버그헌트 P1-B (UR-0105): verify-claim/optimism-check/honesty-check --json 에러 구조화 (1.9.400)', run: () => { const src = read(__filename); const vc = /function verifyClaimCmd[\s\S]{0,1200}?failJson\(_j, 'not_found'/.test(src); const oc = /function optimismCheckCmd[\s\S]{0,700}?failJson\(_j, 'not_found'/.test(src); const hc = /function honestyCheckCmd[\s\S]{0,900}?failJson\(has\('--json'\), 'not_found'/.test(src); return vc && oc && hc; } }, // 1.30.5: {0,400}→{0,700} (F4 가 missing_args 라인을 en/ko 로 늘려 not_found 가 창 밖) · 1.33.2: vc {0,700}→{0,1200} (opts.collect 가드 라인이 not_found 를 더 밀어냄)
2970
2970
  { name: '7번째 버그헌트 P1-C (UR-0106): 시크릿 FN — gitignore 부정(!) + placeholder substring 정밀화 (1.9.401)', run: () => { const m = require('../lib/pure-utils'); const gm = m._gitignoreMatch; const negOk = gm('*.example\n!.env.example', '.env.example') === false && gm('*.log', 'a.log') === true && gm('a.log\n!a.log', 'a.log') === false && gm('.env', '.env') === true; const ph = m._isPlaceholderSecret; const phOk = ph('sk-EXAMPLEab12cd34ef56gh78ij90kl') === false && ph('sk-proj-realKEYexample9988776655') === false && ph('your-key-here') === true && ph('changeme') === true && ph('example') === true && ph('xxxxxxxxxxxxxxxxxxxxxxxxxxxx') === true; return negOk && phOk; } },
2971
2971
  { name: '7번째 버그헌트 P1-A 잔여 (UR-0108): decisions/lessons MD projection 개행 주입 차단 _lineSafe (1.9.402)', run: () => { const m = require('../lib/pure-utils'); if (m._lineSafe !== _lineSafe) return false; const lsOk = _lineSafe('a\nb\r\nc') === 'a b c'; const md = m._renderDecisionsMd([{ date: '2026-06-07', title: 'real\n### 2099-01-01 — FAKE\n- Decision: forged', decision: 'd', reason: 'r' }]); const re = m._decisionsFromMd(md); const noInject = re.length === 1 && !/^### 2099-01-01 — FAKE/m.test(md); const lmd = m._renderLessonsMd([{ date: '2026-06-07', text: 'l1\n### FAKE\n- Lesson: x', tag: 't' }]); const lre = m._parseLessonEntries(lmd); const lNoInject = lre.length === 1; return lsOk && noInject && lNoInject; } },
2972
2972
  { name: '7번째 버그헌트 P2 (UR-0107): api-skill show/drop 에러 exit code 1 (1.9.403)', run: () => { const src = read(__filename); const showId = src.includes("api-skill show <id>')); process.exitCode = 1"); const dropId = src.includes("api-skill drop <id>')); process.exitCode = 1"); const addUrl = src.includes("api-skill add <url> [--direction") && src.includes('process.exitCode = 1'); return showId && dropId && addUrl; } },
@@ -3304,13 +3304,17 @@ function _selfTestCases() {
3304
3304
  && src.includes('completion_claim_allowed: _ccaH'); // handoff json + latest.json
3305
3305
  return pure && wired;
3306
3306
  } },
3307
- { name: 'GPT-5.5 전략리뷰 §6.7 (UR-0152): ci init — PR gate 워크플로 생성 + exit-code 정책 (1.9.444)', run: () => {
3307
+ { name: 'GPT-5.5 전략리뷰 §6.7 (UR-0152): ci init — PR gate 워크플로 생성 + exit-code 정책 (1.9.444) + 강화(1.33.1 버전핀/권한/concurrency)', run: () => {
3308
3308
  if (typeof ciInitCmd !== 'function') return false;
3309
3309
  const wf = LEERNESS_GATE_WORKFLOW;
3310
- const contentOk = /name:\s*leerness-gate/.test(wf) && /on:\s*\n\s*pull_request:/.test(wf) && /leerness gate \./.test(wf) && /exit code 정책/.test(wf) && /actions\/checkout@v4/.test(wf);
3310
+ const contentOk = /name:\s*leerness-gate/.test(wf) && /on:\s*\n\s*pull_request:/.test(wf) && /exit code 정책/.test(wf) && /actions\/checkout@v4/.test(wf);
3311
+ // 1.33.1 강화: 버전 핀(leerness@x.y.z gate, latest 와 동일) + 최소권한 permissions + concurrency cancel
3312
+ const hardened = new RegExp('leerness@' + VERSION.replace(/\./g, '\\.') + ' gate \\.').test(wf)
3313
+ && /permissions:\s*\n\s*contents: read/.test(wf)
3314
+ && /concurrency:\s*\n\s*group: leerness-gate/.test(wf) && /cancel-in-progress: true/.test(wf);
3311
3315
  const src = read(__filename);
3312
3316
  const wired = src.includes("cmd === 'ci' && (args[1] === 'init'") && src.includes('ciInitCmd(absRoot(_resolveRoot(args[2]))');
3313
- return contentOk && wired;
3317
+ return contentOk && hardened && wired;
3314
3318
  } },
3315
3319
  { name: 'UR-0151: decision/lesson/rule add positional path 지원(_taskPositionalPath 재사용, cwd 오염 차단) (1.9.445)', run: () => {
3316
3320
  const src = read(__filename);
@@ -3451,6 +3455,28 @@ function _selfTestCases() {
3451
3455
  const gitAdvisory = src.includes("const gitClaimOk = !(has('--strict-claims') && gitStrongMismatch);");
3452
3456
  return defaultGate && jsonExposed && jsonGate && overall && wholeScan && gitAdvisory;
3453
3457
  } },
3458
+ { name: '1.33.2 (verify-claim --all): 집계 모드 — collect early-return(per-task verdict 재사용) + done 필터 + 라우팅 + 게이팅 부울 공유', run: () => {
3459
+ const src = read(__filename);
3460
+ const sigOpts = /function verifyClaimCmd\(root, taskId, opts = \{\}\)/.test(src);
3461
+ const fn = /function verifyClaimAllCmd\(root\)/.test(src);
3462
+ const doneFilter = src.includes("rows.filter(r => /done|완료|completed/i.test(String(r.status || '')))");
3463
+ const reuse = src.includes("doneRows.map(r => verifyClaimCmd(root, r.id, { collect: true }))");
3464
+ // collect 게이팅 부울이 --json/human 경로와 동일 출처(분기 없음) — 정밀 검사를 일괄 모드에서도 그대로 재사용
3465
+ const collectGate = src.includes('if (opts.collect) {') && src.includes("if (!filesAllExist) reasons.push('files-missing')") && src.includes("if (claimsChecked && !strictOk) reasons.push('optimism/honesty')") && src.includes("if (!gitClaimOk) reasons.push('git-mismatch')") && src.includes("if (claimsChecked && stubFiles.length > 0) reasons.push('stub-impl')");
3466
+ const routed = src.includes("if (args[1] === '--all' || has('--all')) return verifyClaimAllCmd(_p)");
3467
+ return sigOpts && fn && doneFilter && reuse && collectGate && routed;
3468
+ } },
3469
+ { name: '1.33.3 (verify-claim --all → gate+MCP): _verifyClaimsAll 코어(exit 없음) + gate --claims opt-in 6번째(기본 5 유지) + MCP leerness_verify_claim_all def↔case', run: () => {
3470
+ const src = read(__filename);
3471
+ const coreDef = src.match(/function _verifyClaimsAll\(root\) \{[\s\S]*?\n\}/); // 함수 본문만 캡처(첫 줄머리 } 까지)
3472
+ const core = !!coreDef && coreDef[0].includes('return { ok: failed.length === 0, total: doneRows.length, failed: failed.length, results };') && !/process\.exit\(/.test(coreDef[0]); // 코어는 절대 process.exit( 안 함(게이트 step 집계 보호)
3473
+ const gateOptIn = src.includes("const withClaims = has('--claims');") && src.includes('# leerness gate (${withClaims ? 6 : 5} checks)') && src.includes("if (withClaims) step('verify-claims', () => { const r = _verifyClaimsAll(root);");
3474
+ const reuse = src.includes('const res = _verifyClaimsAll(root);'); // CLI 도 코어 공유(분기 없음)
3475
+ const tools = require('../lib/mcp-tools');
3476
+ const def = tools.find(t => t.name === 'leerness_verify_claim_all');
3477
+ const mcpOk = !!def && def.requiredTier === 'read-only' && src.includes("case 'leerness_verify_claim_all':") && /case 'leerness_verify_claim_all':[\s\S]{0,160}'verify-claim', '--all'[\s\S]{0,80}'--json'/.test(src);
3478
+ return core && gateOptIn && reuse && mcpOk && tools.length >= 84;
3479
+ } },
3454
3480
  { name: '14th 버그헌트 P2 (UR-0178/0179/0180): completed→done 정규화 + rule archive _cellSafe + nextRuleId 아카이브 스캔 (1.11.3)', run: () => {
3455
3481
  if (_normTaskStatus('completed') !== 'done' || _normTaskStatus('verified') !== 'done' || _normTaskStatus('in-progress') !== 'in-progress') return false;
3456
3482
  const src = read(__filename);
@@ -4647,7 +4673,7 @@ function commandsCmd(root) {
4647
4673
  { cmd: 'scan secrets [path]', desc: '시크릿 탐지' },
4648
4674
  { cmd: 'encoding check [path]', desc: '인코딩 검증' },
4649
4675
  { cmd: 'lazy detect [path] [--json]', desc: '게으른 작업 감지 (1.9.101)' },
4650
- { cmd: 'verify-claim <T-ID> [--run-tests] [--test-cmd "<명령>"] [--strict-claims] [--require-evidence]', desc: '주장 검증 (1.9.18~26) — --require-evidence: done 주장에 파일+테스트 근거 강제 (1.9.287) · --test-cmd: 비-JS 테스트 명령 (1.17.2)' },
4676
+ { 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)' },
4651
4677
  { cmd: 'lens [code|design|docs|test|security] [--json]', desc: '분야별 자기질문 품질 렌즈 + 분야간 인과관계 (1.18.3)' },
4652
4678
  { cmd: 'optimism-check <T-ID>', desc: '낙관적 API 감지 (1.9.26)' },
4653
4679
  { cmd: 'requests audit|list|complete|drop|auto-complete', desc: '사용자 요청 추적 (1.9.207/223)' },
@@ -10385,13 +10411,14 @@ function _vcImplIsEmpty(body) {
10385
10411
  return residue === ''; // 의미 토큰이 하나도 안 남으면 스텁
10386
10412
  }
10387
10413
 
10388
- function verifyClaimCmd(root, taskId) {
10414
+ function verifyClaimCmd(root, taskId, opts = {}) {
10389
10415
  root = absRoot(root);
10390
10416
  const _j = has('--json'); // 1.9.400 (7번째 버그헌트 P1-B, UR-0105): --json 에러도 구조화
10391
- if (!taskId) return failJson(_j, 'missing_args', _uiLang(root) === 'en' ? 'verify-claim <T-ID> required. ex: leerness verify-claim T-0008' : 'verify-claim <T-ID> 필요. 예: leerness verify-claim T-0008'); // 1.30.5 (#156 F4)
10417
+ // 1.33.2: opts.collect verify-claim --all 집계 모드. 렌더/exit 없이 verdict 객체만 반환 (per-task 경로 무변경, collect 때만 분기).
10418
+ if (!taskId) { if (opts.collect) return { id: taskId, ok: false, reasons: ['no-id'] }; return failJson(_j, 'missing_args', _uiLang(root) === 'en' ? 'verify-claim <T-ID> required. ex: leerness verify-claim T-0008' : 'verify-claim <T-ID> 필요. 예: leerness verify-claim T-0008'); } // 1.30.5 (#156 F4)
10392
10419
  const rows = readProgressRows(root);
10393
10420
  const row = rows.find(r => r.id === taskId);
10394
- if (!row) return failJson(_j, 'not_found', `progress-tracker.md에 ${taskId} 없음.`);
10421
+ if (!row) { if (opts.collect) return { id: taskId, ok: false, reasons: ['not-found'] }; return failJson(_j, 'not_found', `progress-tracker.md에 ${taskId} 없음.`); }
10395
10422
 
10396
10423
  const evidence = row.evidence || '';
10397
10424
  // 1.9.20: 파일 경로 추출 — 도메인 폴더 자동 인식 + 루트 메타파일
@@ -10589,6 +10616,22 @@ function verifyClaimCmd(root, taskId) {
10589
10616
  // 1.11.2 (UR-0175): git strongMismatch 는 기본 게이트에서 제외(advisory) — 커밋 후 검증(정상 흐름)에서 커밋된 파일이 working-tree 변경에 없어 false-fail 하므로. --strict-claims 시에만 FAIL 기여. 기본 게이트는 신뢰도 높은 optimism(claimsConsistent)이 담당.
10590
10617
  const gitClaimOk = !(has('--strict-claims') && gitStrongMismatch);
10591
10618
 
10619
+ // 1.33.2 (verify-claim --all): 집계 모드는 렌더/exit 없이 verdict 만 반환. 게이팅 부울은 아래 --json/human 경로와 동일 계산을 공유(분기 없음).
10620
+ if (opts.collect) {
10621
+ const _tcMatch = declaredTestCount == null ? null : (!testMeasured ? null : actualTestCount >= declaredTestCount);
10622
+ const _runFail = !!(runResult && !runResult.skipped && !runResult.allPassed);
10623
+ const reasons = [];
10624
+ if (!filesAllExist) reasons.push('files-missing');
10625
+ if (_tcMatch === false) reasons.push('test-count-short');
10626
+ if (!evidenceQualityOk) reasons.push('evidence-incomplete');
10627
+ if (claimsChecked && !strictOk) reasons.push('optimism/honesty');
10628
+ if (!gitClaimOk) reasons.push('git-mismatch');
10629
+ if (claimsChecked && stubFiles.length > 0) reasons.push('stub-impl');
10630
+ if (has('--strict-claims') && testLinkOk === false) reasons.push('test-impl-unlinked');
10631
+ if (_runFail) reasons.push('tests-failed');
10632
+ return { id: taskId, request: row.request, status: row.status, ok: reasons.length === 0, reasons };
10633
+ }
10634
+
10592
10635
  if (has('--json')) {
10593
10636
  const out = {
10594
10637
  project: path.basename(root),
@@ -10742,6 +10785,43 @@ function verifyClaimCmd(root, taskId) {
10742
10785
  log(t(` ✓ evidence 주장이 실제 파일·테스트${runResult && !runResult.skipped ? '·실행 결과' : ''}와 일치`, ` ✓ evidence claim matches actual files·tests${runResult && !runResult.skipped ? '·run results' : ''}`));
10743
10786
  }
10744
10787
 
10788
+ // 1.33.2 (verify-claim+CI gate 슬라이스 강화): verify-claim --all — 모든 done/완료 주장을 한 번에 검증(CI·스케일용).
10789
+ // per-task 경로(verifyClaimCmd)를 opts.collect 로 재사용 → verdict 만 집계(렌더/exit 분기는 collect 가 흡수). 통과 플래그(--run-tests/--strict-claims/--lenient 등)는 전역이라 각 task 에 그대로 적용됨.
10790
+ // 동기: 플래그십(verify-claim)이 종전 per-task 전용이라, "내 완료 주장 전부 증거와 맞는가?"를 한 명령으로 못 했음. 게이트는 lazy/audit 휴리스틱으로 거짓완료를 잡지만, 부풀린 카운트·스텁은 verify-claim 의 정밀 검사가 더 잘 잡음.
10791
+ // 1.33.3: 일괄 검증 코어 — 렌더/exit 없이 결과만 반환. verifyClaimAllCmd(CLI 렌더+exit) 와 gate --claims(opt-in 체크) 가 공유.
10792
+ // gate 의 step() 은 process.exit 가 아니라 process.exitCode 로 실패를 감지하므로, 코어는 절대 process.exit 하지 않음(게이트 프로세스 조기종료 방지).
10793
+ function _verifyClaimsAll(root) {
10794
+ root = absRoot(root);
10795
+ const rows = readProgressRows(root);
10796
+ const doneRows = rows.filter(r => /done|완료|completed/i.test(String(r.status || '')));
10797
+ const results = doneRows.map(r => verifyClaimCmd(root, r.id, { collect: true }));
10798
+ const failed = results.filter(x => x && !x.ok);
10799
+ return { ok: failed.length === 0, total: doneRows.length, failed: failed.length, results };
10800
+ }
10801
+ function verifyClaimAllCmd(root) {
10802
+ root = absRoot(root);
10803
+ const _j = has('--json');
10804
+ const _L = _uiLang(root); const t = (ko, en) => (_L === 'en' ? en : ko);
10805
+ const res = _verifyClaimsAll(root);
10806
+ const { results, total } = res;
10807
+ if (_j) {
10808
+ log(JSON.stringify({ ok: res.ok, root: path.basename(root), total, failed: res.failed, results }, null, 2));
10809
+ if (res.failed) process.exitCode = 1;
10810
+ return;
10811
+ }
10812
+ log(`# verify-claim --all (${path.basename(root)})`);
10813
+ if (!total) { log(t(' 완료(done) 주장이 없어 검증할 항목이 없습니다.', ' No completed (done) claims to verify.')); return; }
10814
+ log(t(` 완료 주장 ${total}건 검증 — 통과 ${total - res.failed} · 실패 ${res.failed}`, ` Verified ${total} completed claim(s) — pass ${total - res.failed} · fail ${res.failed}`));
10815
+ log('');
10816
+ for (const r of results) {
10817
+ if (r.ok) log(` ✓ ${r.id} ${String(r.request || '').slice(0, 60)}`);
10818
+ else log(` ✗ ${r.id} ${String(r.request || '').slice(0, 50)} ${t('← 불일치', '← mismatch')}: ${r.reasons.join(', ')}`);
10819
+ }
10820
+ log('');
10821
+ if (res.failed) { log(t(` ⚠ ${res.failed}건의 완료 주장이 증거와 불일치 — 재검토 권장 (개별 상세: leerness verify-claim <T-ID>)`, ` ⚠ ${res.failed} completed claim(s) do not match evidence — review (per-task detail: leerness verify-claim <T-ID>)`)); return process.exit(1); }
10822
+ log(t(` ✓ 모든 완료 주장이 증거와 일치`, ` ✓ all completed claims match evidence`));
10823
+ }
10824
+
10745
10825
  // 1.9.22: orchestrate — Ollama 로컬 LLM으로 best-of-N 멀티 에이전트 시뮬
10746
10826
  // 정책 (사용자 명시 1.9.22):
10747
10827
  // 1) 자동 적용 금지. LEERNESS_OLLAMA_BASE_URL 환경변수 감지 opt-in 전용
@@ -12634,7 +12714,10 @@ function gate(root) {
12634
12714
  const jsonMode = has('--json'); // 외부리뷰 C2: --json 일관성 — 이전엔 텍스트 헤더+단계별 JSON 혼재로 파싱 불가. 단일 객체로 집계.
12635
12715
  const checks = [];
12636
12716
  let bad = 0;
12637
- if (!jsonMode) log('# leerness gate (5 checks)');
12717
+ // 1.33.3 (verify-claim+CI gate 슬라이스 강화): --claims opt-in — 모든 done 주장을 정밀 per-claim 검증(verify-claim --all)으로 추가(6번째). 기본 5체크는 무변경(기존 어댑터 회귀 0).
12718
+ // 기본 게이트는 lazy/audit 휴리스틱으로 거짓완료를 잡지만, 부풀린 카운트·스텁은 verify-claim 의 정밀 검사가 더 잘 잡음 → CI 가 README 약속("claims fail → cannot merge")을 문자 그대로 강제하려면 --claims.
12719
+ const withClaims = has('--claims');
12720
+ if (!jsonMode) log(`# leerness gate (${withClaims ? 6 : 5} checks)`);
12638
12721
  function step(label, fn) {
12639
12722
  const code0 = process.exitCode || 0;
12640
12723
  if (!jsonMode) log(`\n## ${label}`);
@@ -12653,6 +12736,8 @@ function gate(root) {
12653
12736
  step('scan secrets', () => scanSecrets(root));
12654
12737
  step('encoding check', () => encodingCheck(root));
12655
12738
  step('lazy detect', () => lazyDetect(root));
12739
+ // 1.33.3: opt-in 정밀 per-claim 검증. 코어(_verifyClaimsAll)는 exit 안 하고 결과만 반환 → step 의 exitCode 감지로 실패 집계. 비-json 모드는 불일치 task 를 fail() 로 표면화.
12740
+ if (withClaims) step('verify-claims', () => { const r = _verifyClaimsAll(root); if (!r.ok) { process.exitCode = 1; if (!jsonMode) r.results.filter(x => x && !x.ok).forEach(x => fail(`${x.id} 불일치: ${x.reasons.join(', ')}`)); } });
12656
12741
  if (jsonMode) { log(JSON.stringify({ version: VERSION, root, ok: bad === 0, total: checks.length, failed: bad, checks }, null, 2)); if (bad) process.exitCode = 1; return; }
12657
12742
  log(`\n# gate summary: ${bad} 단계 실패`);
12658
12743
  if (bad) process.exitCode = 1;
@@ -16041,6 +16126,7 @@ function _mcpToCliArgs(name, args, targetPath) {
16041
16126
  case 'leerness_drift_check': cliArgs = ['drift', 'check', targetPath, '--json']; break;
16042
16127
  case 'leerness_audit': cliArgs = ['audit', targetPath, '--json', ...(args.fix ? ['--fix'] : []), ...(args.strict ? ['--strict'] : [])]; break;
16043
16128
  case 'leerness_verify_claim': cliArgs = ['verify-claim', args.taskId, '--path', targetPath, ...(args.runTests ? ['--run-tests'] : []), ...(args.strictClaims ? ['--strict-claims'] : []), ...(args.lenient ? ['--lenient'] : [])]; break;
16129
+ case 'leerness_verify_claim_all': cliArgs = ['verify-claim', '--all', '--path', targetPath, '--json', ...(args.runTests ? ['--run-tests'] : []), ...(args.strictClaims ? ['--strict-claims'] : []), ...(args.lenient ? ['--lenient'] : [])]; break; // 1.33.3: 모든 done 주장 일괄 검증(--json 강제 → process.exitCode 만, 하드 exit 없음)
16044
16130
  case 'leerness_contract_verify': cliArgs = ['contract', 'verify', args.spec, args.impl]; break;
16045
16131
  case 'leerness_agents_list': cliArgs = ['agents', 'list', '--json']; break;
16046
16132
  case 'leerness_reuse_map': cliArgs = ['reuse-map', targetPath, ...(args.allApps ? ['--all-apps'] : []), ...(args.strictElements ? ['--strict-elements'] : []), '--json']; break;
@@ -17227,13 +17313,22 @@ function runsShowCmd(root, id) {
17227
17313
 
17228
17314
  // 1.9.444 (GPT-5.5 전략리뷰 §6.7, UR-0152): CI/PR 턴키 — PR 마다 leerness gate 를 실행하는 GitHub Actions 워크플로 생성.
17229
17315
  // gate = verify + audit + scan secrets + encoding + lazy. exit 1 시 PR 체크 실패 → 증거 없는 완료/시크릿/규칙 위반을 CI 에서 차단.
17316
+ // 1.33.1 (verify-claim+gate 슬라이스 강화, 웹 Opus 4.8 리뷰 "pin a version"): 생성 워크플로를 production-grade 로 —
17317
+ // (1) leerness 버전 핀(재현성·공급망: ci init 가 설치 버전 주입) (2) permissions 최소권한 (3) concurrency 중복 취소.
17230
17318
  const LEERNESS_GATE_WORKFLOW = [
17231
17319
  '# leerness gate — AI 코딩 작업 증거/규칙/검증 게이트 (PR CI). leerness ci init 로 생성.',
17232
17320
  '# exit code 정책: 통과=0, 실패(테스트 실패 / 커밋 시크릿 / 인코딩 깨짐 / 게으름·증거 누락 / 필수 규칙 파일 없음)=1 → PR 체크 실패.',
17321
+ '# 버전 핀(재현성·공급망 안전): 아래 leerness@' + VERSION + ' 을 의도적으로만 갱신하세요 (leerness ci init --force 재생성).',
17322
+ '# unpinned latest 는 새 릴리스가 나오면 게이트 판정이 조용히 바뀔 수 있어 지양 — CI 게이트는 재현 가능해야 합니다.',
17233
17323
  'name: leerness-gate',
17234
17324
  'on:',
17235
17325
  ' pull_request:',
17236
17326
  ' workflow_dispatch:',
17327
+ 'permissions:',
17328
+ ' contents: read # 최소 권한 — gate 는 읽기·검증만 (least-privilege)',
17329
+ 'concurrency:',
17330
+ ' group: leerness-gate-${{ github.ref }}',
17331
+ ' cancel-in-progress: true',
17237
17332
  'jobs:',
17238
17333
  ' gate:',
17239
17334
  ' runs-on: ubuntu-latest',
@@ -17243,7 +17338,7 @@ const LEERNESS_GATE_WORKFLOW = [
17243
17338
  ' with:',
17244
17339
  " node-version: '20'",
17245
17340
  ' - name: leerness gate',
17246
- ' run: npx -y leerness gate .',
17341
+ ' run: npx -y leerness@' + VERSION + ' gate .',
17247
17342
  '',
17248
17343
  ].join('\n');
17249
17344
  function ciInitCmd(root, opts = {}) {
@@ -17261,6 +17356,8 @@ function ciInitCmd(root, opts = {}) {
17261
17356
  if (json) { log(JSON.stringify({ ok: true, created: true, path: relPath })); return; }
17262
17357
  ok(`생성: ${relPath} — PR 마다 leerness gate 실행 (exit 1 시 PR 체크 실패)`);
17263
17358
  log(' gate = verify + audit + scan secrets + encoding + lazy. 증거 없는 완료·시크릿·규칙 위반을 CI 에서 차단.');
17359
+ log(` 버전 핀 leerness@${VERSION}(재현성·공급망) · permissions 최소권한(contents: read) · 중복 실행 자동 취소.`);
17360
+ log(' ⮕ 다음 단계(가드레일 완성): GitHub branch protection 에서 leerness-gate 를 "required" 체크로 지정 — 그래야 우회 불가.');
17264
17361
  }
17265
17362
  // 1.9.294 (UR-0025 3단계): 역할/모델 카탈로그(_PROVIDER_MODEL_CATALOG + _AGENT_ROLE_PROMPTS + ROLE_CATALOG + _ROLE_ALIASES) 데이터 모듈 분리 (비파괴, require-based).
17266
17363
  const { _PROVIDER_MODEL_CATALOG, _AGENT_ROLE_PROMPTS, ROLE_CATALOG, _ROLE_ALIASES } = require('../lib/role-catalog');
@@ -20227,7 +20324,7 @@ async function main() {
20227
20324
  if (cmd === 'memory' && args[1] === 'search') return memorySearch(arg('--path', process.cwd()), args.slice(2).join(' '));
20228
20325
  if (cmd === 'handoff') return handoffCmd(arg('--path', args[1] || process.cwd()));
20229
20326
  if (cmd === 'reuse-map') return reuseMapCmd(arg('--path', args[1] || process.cwd()));
20230
- if (cmd === 'verify-claim') return verifyClaimCmd(arg('--path', process.cwd()), args[1]);
20327
+ if (cmd === 'verify-claim') { const _p = arg('--path', process.cwd()); if (args[1] === '--all' || has('--all')) return verifyClaimAllCmd(_p); return verifyClaimCmd(_p, args[1]); } // 1.33.2: --all → 모든 done 주장 일괄 검증
20231
20328
  if (cmd === 'orchestrate') return await orchestrateCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')));
20232
20329
  if (cmd === 'llm-bench' && args[1] === 'record') return llmBenchRecordCmd(arg('--path', process.cwd()));
20233
20330
  if (cmd === 'deps') return depsImpactCmd(arg('--path', process.cwd()), args[1]);
package/lib/mcp-tools.js CHANGED
@@ -8,6 +8,7 @@ module.exports = [
8
8
  { name: 'leerness_drift_check', requiredTier: 'read-only', description: '1.9.136 — AI 에이전트 leerness 미사용 drift 자동 감지 JSON ({ root, score, level, signals[], healthy }). 5+ 신호 + 4단계 레벨 (🟢 healthy / 🟡 warning / 🟠 caution / 🔴 critical). 보안 신호 통합 (1.9.78)', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
9
9
  { name: 'leerness_audit', requiredTier: 'read-only', description: '1.9.102 — 워크스페이스 일관성 감사 JSON (warnings/failures/fixed/healthy + findings[]. kind 11종: design_dup/design_system_default/reuse_map_empty/milestone_unlinked/handoff_not_generated/current_state_stale/readme_version_mismatch/npm_cve/gitignore_missing_secrets/env_keys_missing/strict_promoted)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, fix: { type: 'boolean' }, strict: { type: 'boolean' } } } },
10
10
  { name: 'leerness_verify_claim', requiredTier: 'read-only', description: 'AI 거짓 완료 자동 검증 (evidence 파일 + 실 테스트 실행). 1.9.309(UR-0048): done/완료 주장은 evidence(수정파일+테스트) 기본 강제 — 증거 없는 done 은 FAIL(exit 1). lenient=true 로 완화, runTests/strictClaims 추가 가능. 응답 verdict.evidenceComplete 포함.', inputSchema: { type: 'object', properties: { taskId: { type: 'string' }, path: { type: 'string' }, runTests: { type: 'boolean' }, strictClaims: { type: 'boolean' }, lenient: { type: 'boolean' } }, required: ['taskId'] } },
11
+ { name: 'leerness_verify_claim_all', requiredTier: 'read-only', description: '1.33.3 — 모든 done/완료 주장을 한 번에 검증(CI·스케일). progress-tracker 의 done 항목 전부를 verify-claim 정밀 검사(파일 존재·스텁·부풀린 카운트·증거 완전성)로 일괄 점검. 응답 { ok, total, failed, results:[{id,ok,reasons}] }. 세션 마감 전 "내 완료 주장 전부 증거와 맞는가?" 자가 점검용. runTests/strictClaims/lenient 추가 가능.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, runTests: { type: 'boolean' }, strictClaims: { type: 'boolean' }, lenient: { type: 'boolean' } } } },
11
12
  { name: 'leerness_contract_verify', requiredTier: 'read-only', description: '명세 ↔ 구현 함수/필드 일치 자동 검사', inputSchema: { type: 'object', properties: { spec: { type: 'string' }, impl: { type: 'string' } }, required: ['spec', 'impl'] } },
12
13
  { name: 'leerness_agents_list', requiredTier: 'read-only', description: '외부 AI CLI 가용성 표 (claude/codex/agy/copilot 상태 + 환경변수 활성화 여부)', inputSchema: { type: 'object', properties: {} } },
13
14
  { name: 'leerness_reuse_map', requiredTier: 'read-only', description: '워크스페이스 중복 함수/capability 자동 감지 (--all-apps + fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, allApps: { type: 'boolean' }, strictElements: { type: 'boolean' } } } },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -6332,6 +6332,109 @@ total++;
6332
6332
  if (!ok) failed++;
6333
6333
  }
6334
6334
 
6335
+ // 1.33.1 회귀 (verify-claim+gate 슬라이스 강화): ci init 생성 워크플로가 production-grade — leerness 버전 핀(재현성) + 최소권한 permissions + concurrency 취소.
6336
+ total++;
6337
+ {
6338
+ let ok = false;
6339
+ try {
6340
+ const ver = require(path.resolve(__dirname, '..', 'package.json')).version;
6341
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ci-'));
6342
+ cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes'], { encoding: 'utf8', timeout: 30000 });
6343
+ const ci = cp.spawnSync(process.execPath, [CLI, 'ci', 'init', d], { encoding: 'utf8', timeout: 15000 });
6344
+ const wf = fs.readFileSync(path.join(d, '.github', 'workflows', 'leerness-gate.yml'), 'utf8');
6345
+ // 버전 핀(설치 버전 == package.json) · 미핀 latest 부재 · 최소권한 · concurrency 취소 · gate 호출
6346
+ const pinned = new RegExp('run: npx -y leerness@' + ver.replace(/\./g, '\\.') + ' gate \\.').test(wf);
6347
+ const noUnpinned = !/npx -y leerness gate \./.test(wf);
6348
+ const perms = /permissions:\n\s*contents: read/.test(wf);
6349
+ const conc = /concurrency:\n\s*group: leerness-gate-\$\{\{ github\.ref \}\}\n\s*cancel-in-progress: true/.test(wf);
6350
+ const stillGate = /leerness-gate/.test(wf) && /pull_request:/.test(wf) && /actions\/checkout@v4/.test(wf);
6351
+ fs.rmSync(d, { recursive: true, force: true });
6352
+ ok = ci.status === 0 && pinned && noUnpinned && perms && conc && stillGate;
6353
+ } catch {}
6354
+ console.log(ok ? '✓ B(1.33.1) gate 슬라이스 강화: ci init 워크플로 버전핀(leerness@설치버전) + 최소권한 permissions(contents:read) + concurrency cancel + 미핀 latest 부재' : '✗ ci init 워크플로 강화 가드 실패');
6355
+ if (!ok) failed++;
6356
+ }
6357
+
6358
+ // 1.33.2 회귀 (verify-claim+gate 슬라이스 강화): verify-claim --all — 모든 done 주장 일괄 검증. 거짓완료=exit 1(files-missing), 진실완료/빈프로젝트=exit 0, per-task 경로와 verdict 일치(맹신 X).
6359
+ total++;
6360
+ {
6361
+ let ok = false;
6362
+ try {
6363
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-vca-'));
6364
+ const R = (a) => cp.spawnSync(process.execPath, [CLI, ...a, '--path', d], { encoding: 'utf8', timeout: 30000 });
6365
+ cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes'], { encoding: 'utf8', timeout: 30000 });
6366
+ // 빈(완료 0) → ok true exit 0
6367
+ const empty = R(['verify-claim', '--all', '--json']);
6368
+ const ej = JSON.parse(empty.stdout);
6369
+ const emptyOk = empty.status === 0 && ej.ok === true && ej.total === 0;
6370
+ // 진실 완료(실제 파일 + 테스트)
6371
+ fs.writeFileSync(path.join(d, 'calc.js'), 'function add(a,b){ return a+b; }\nmodule.exports={add};\n');
6372
+ fs.mkdirSync(path.join(d, 'tests'), { recursive: true });
6373
+ fs.writeFileSync(path.join(d, 'tests', 'calc.test.js'), 'const {add}=require("../calc.js");\ntest("add",()=>{ if(add(1,2)!==3) throw new Error("x"); });\n');
6374
+ const aT = R(['task', 'add', 'Implement calc.js add()']);
6375
+ const idT = (aT.stdout.match(/T-\d{4,}/) || [])[0];
6376
+ R(['task', 'update', idT, '--status', 'done', '--evidence', 'calc.js + tests/calc.test.js — 1 test passing']);
6377
+ // 거짓 완료(존재하지 않는 파일 주장)
6378
+ const aF = R(['task', 'add', 'Implement payment API']);
6379
+ const idF = (aF.stdout.match(/T-\d{4,}/) || [])[0];
6380
+ R(['task', 'update', idF, '--status', 'done', '--evidence', 'payment.js implemented and tested']);
6381
+ const mixed = R(['verify-claim', '--all', '--json']);
6382
+ const mj = JSON.parse(mixed.stdout);
6383
+ const fr = mj.results.find((x) => x.id === idF);
6384
+ const tr = mj.results.find((x) => x.id === idT);
6385
+ const mixedOk = mixed.status === 1 && mj.ok === false && mj.total === 2 && mj.failed === 1 && fr && fr.ok === false && fr.reasons.includes('files-missing') && tr && tr.ok === true;
6386
+ // per-task 경로와 일치(맹신 X): 거짓 개별 exit 1, 진실 개별 exit 0 — 일괄 verdict 가 정밀 검사를 그대로 재사용
6387
+ const consistent = R(['verify-claim', idF]).status === 1 && R(['verify-claim', idT]).status === 0;
6388
+ // human 출력도 exit 1 + 불일치 표기
6389
+ const human = R(['verify-claim', '--all']);
6390
+ const humanOk = human.status === 1 && /불일치|mismatch/.test(human.stdout);
6391
+ fs.rmSync(d, { recursive: true, force: true });
6392
+ ok = emptyOk && mixedOk && consistent && humanOk;
6393
+ } catch (e) {}
6394
+ console.log(ok ? '✓ B(1.33.2) verify-claim --all: 거짓완료 일괄 차단(exit 1, files-missing) + 진실완료/빈프로젝트 통과 + per-task verdict 일치' : '✗ verify-claim --all 일괄 검증 가드 실패');
6395
+ if (!ok) failed++;
6396
+ }
6397
+
6398
+ // 1.33.3 회귀 (verify-claim+CI gate 슬라이스 강화): gate --claims opt-in 6번째 체크(기본 5 무변경) + MCP leerness_verify_claim_all 라운드트립.
6399
+ total++;
6400
+ {
6401
+ let ok = false;
6402
+ try {
6403
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-g3-'));
6404
+ const R = (a) => cp.spawnSync(process.execPath, [CLI, ...a, '--path', d], { encoding: 'utf8', timeout: 30000 });
6405
+ cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes'], { encoding: 'utf8', timeout: 30000 });
6406
+ // 클린: 기본 gate 5체크, --claims 6체크(verify-claims 추가) 둘 다 통과
6407
+ const cleanDefault = JSON.parse(R(['gate', '.', '--json']).stdout);
6408
+ const cleanClaims = JSON.parse(R(['gate', '.', '--claims', '--json']).stdout);
6409
+ const cleanOk = cleanDefault.total === 5 && cleanDefault.ok === true && cleanClaims.total === 6 && cleanClaims.ok === true && cleanClaims.checks.some((c) => c.name === 'verify-claims');
6410
+ // 거짓 완료 추가
6411
+ const aF = R(['task', 'add', 'Implement payment API']);
6412
+ const idF = (aF.stdout.match(/T-\d{4,}/) || [])[0];
6413
+ R(['task', 'update', idF, '--status', 'done', '--evidence', 'payment.js implemented and tested']);
6414
+ // 기본 gate(5): 기존 동작 유지(lazy/audit 가 거짓완료 차단 → exit 1)
6415
+ const falseDefault = R(['gate', '.', '--json']);
6416
+ const fd = JSON.parse(falseDefault.stdout);
6417
+ const defaultStill = falseDefault.status === 1 && fd.total === 5;
6418
+ // --claims gate(6): verify-claims 체크가 명시적으로 false + exit 1 + human 모드 summary 도달(하드 exit 없음)
6419
+ const falseClaims = R(['gate', '.', '--claims', '--json']);
6420
+ const fc = JSON.parse(falseClaims.stdout);
6421
+ const vcCheck = fc.checks.find((c) => c.name === 'verify-claims');
6422
+ const claimsBlocks = falseClaims.status === 1 && vcCheck && vcCheck.ok === false && fc.total === 6;
6423
+ const humanClaims = R(['gate', '.', '--claims']);
6424
+ const humanOk = humanClaims.status === 1 && /verify-claims/.test(humanClaims.stdout) && /gate summary/.test(humanClaims.stdout);
6425
+ // MCP leerness_verify_claim_all 라운드트립
6426
+ const mcpCall = (req) => { const r = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], { encoding: 'utf8', timeout: 12000, input: JSON.stringify(req) + '\n' }); try { const line = r.stdout.split('\n').filter(Boolean)[0]; const j = JSON.parse(line); return JSON.parse(j.result.content[0].text); } catch { return null; } };
6427
+ const listed = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], { encoding: 'utf8', timeout: 12000, input: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }) + '\n' });
6428
+ let toolListed = false; try { toolListed = JSON.parse(listed.stdout.split('\n').filter(Boolean)[0]).result.tools.some((t) => t.name === 'leerness_verify_claim_all'); } catch {}
6429
+ const mcpRes = mcpCall({ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'leerness_verify_claim_all', arguments: { path: d } } });
6430
+ const mcpOk = toolListed && mcpRes && mcpRes.ok === false && mcpRes.total === 1 && mcpRes.failed === 1 && Array.isArray(mcpRes.results) && mcpRes.results[0].reasons.includes('files-missing');
6431
+ fs.rmSync(d, { recursive: true, force: true });
6432
+ ok = cleanOk && defaultStill && claimsBlocks && humanOk && mcpOk;
6433
+ } catch (e) {}
6434
+ console.log(ok ? '✓ B(1.33.3) gate --claims opt-in(기본 5 무변경 + 6번째 verify-claims 거짓완료 차단) + MCP verify_claim_all 라운드트립' : '✗ gate --claims + MCP verify_claim_all 가드 실패');
6435
+ if (!ok) failed++;
6436
+ }
6437
+
6335
6438
  // 1.9.430 (10th 외부평가 UR-0130): health 보안 CRITICAL(커밋 시크릿)은 --strict 없이도 exit 1(CI 게이트). 클린은 exit 0.
6336
6439
  total++;
6337
6440
  {
@@ -6456,7 +6559,7 @@ total++;
6456
6559
  const wfPath = path.join(d, '.github', 'workflows', 'leerness-gate.yml');
6457
6560
  const created = fs.existsSync(wfPath);
6458
6561
  const content = created ? fs.readFileSync(wfPath, 'utf8') : '';
6459
- const contentOk = /name:\s*leerness-gate/.test(content) && /pull_request:/.test(content) && /leerness gate \./.test(content);
6562
+ const contentOk = /name:\s*leerness-gate/.test(content) && /pull_request:/.test(content) && /leerness@[\d.]+ gate \./.test(content); // 1.33.1: 버전 핀(leerness@x.y.z gate)
6460
6563
  // 멱등: 재실행 시 경고(덮어쓰기 X, exit 0)
6461
6564
  const r2 = cp.spawnSync(process.execPath, [CLI, 'ci', 'init', d], { encoding: 'utf8', timeout: 15000 });
6462
6565
  const idempotent = r2.status === 0 && /이미 존재|exists/.test((r2.stdout || '') + (r2.stderr || ''));