leerness 1.36.78 → 1.36.80

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,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.80 — 2026-07-28 — referee 캘리브레이션 · 라이브 미리보기 · 기본 스킬 정리 · 최소성 사다리 — MCP 89 도구 · R-0001 검수 22회전
4
+
5
+ **이 릴리스에 함께 들어간 것** (아래 상세):
6
+ - **라이브 미리보기(사용자 요청)**: `preview serve <P-ID>` 가 임시 워크스페이스 + **0-deps 로컬 서버**로 시안을 띄우고 승인/수정 시 정리(project 모드 배치본 포함). **시그니처 포트 5337**(키패드 "LEER", ollama:11434 방식) + 점유 시 순차 회피 → OS 빈 포트. `preview mode self|project` — A 기본·B 옵트인, 미설정 시 사용자에게 질문하되 흐름은 막지 않음. leerness 는 사용자의 dev 서버를 임의 실행하지 않는다.
7
+ - **기본 제공 스킬 정리(사용자 지시)**: 도메인 7종(commerce-api/office/crawling/firebase/ads-analytics/appstore-review/ai-verified-skill-publisher) 내장 제거 — **설명 4줄짜리 메타데이터**인데 `verification:'passed'` 를 근거 없이 주장했고 특정 사용자 도메인이라 전역 기본값으로 부적절했다. 남은 2종의 verification 은 **`'unverified'`** 로 정직화(selftest 가 'passed' 주장 금지를 영구 단언). **기존 설치분은 파일·락 모두 보존**(비파괴) · 제거된 id 명시 요청 시 조용히 무시하지 않고 고지.
8
+ - **`review-request --ladder` (P-0003, codex 공동판단 ADOPT)**: 순서 있는 최소성 결정 영수증(필요한가→기존코드→stdlib→네이티브→설치된 의존성→조합→최소구현). 옵트인·자문 전용이라 `proceed` 무영향, 근거 없으면 `unknown`, "줄이면 안 되는 것"(검증·보안·접근성·에러처리·데이터손실) 명시. **기각**: 룰 거버넌스/campaign 스냅샷(P-0002 — campaign 개념 부재, 제안/승인 분리는 이미 기본 모델, 모순탐지는 false-BLOCK 위험).
9
+ - **검수 22회전이 High 5 포함 12건 차단**: ① **신규 모듈 2개가 git 미추적** → 커밋 시 CLI 기동 불가(출하 직전 차단) ② 캘리브레이션이 **무관한 실패**(없는 바이너리·SyntaxError·exit 0+신호)로 통과 → 런처 실패 판별 + `--expect-bad` 필수 + check 연결 ③ 캘리브레이션(최대 600s)이 락 대기 10s/stale 30s 를 넘겨 **살아있는 락이 삭제** → 실행은 락 밖, 저장은 락 안 CAS(지문 불일치 시 폐기) ④ 서버 경로 격리 우회(형제 디렉토리 `%2e%2e`·정션·잘못된 인코딩으로 서버 crash) → path.relative + realpath + 400 ⑤ `--static-dir` 정션으로 프로젝트 밖 쓰기 → realpath 검증 ⑥ 신호 매칭이 8000자 절단본 기준 → 전체 출력 기준 ⑦ 스토어 형상 검증 강화 ⑧ project 배치본·멱등 재승인 정리 누락 ⑨ 프로젝트 CSS 의 `</style>` 탈출로 스크립트 주입 → 별도 스타일시트+link ⑩ 제거 스킬 명시 요청 조용히 무시 ⑪ "아무것도 저장 안 함" 과장 → 주장 범위 정정 ⑫ 실패 시 빈 임시 디렉토리 잔존.
10
+ - 검증: 검수 지적 9종 프로브 재현→수정→재확인 실측, selftest 334, e2e 404/404.
11
+
12
+ ---
13
+
14
+ ### (원안) 검증기 캘리브레이션 `referee` — P-0001, 외부 사례 검토 채택
15
+
16
+ 사용자가 지정한 두 외부 저장소(ponytail · anthropics/code-migration-kit)를 codex 독립 검토 + 4렌즈 병렬 분석으로 평가 → **만장일치 1순위 한 건만** 채택. leerness 정체성("done 은 증거를 요구한다")의 미해결 형제 명제 — **검증기 자신의 증거** — 를 채운다.
17
+
18
+ - **문제**: leerness 의 모든 검사(verify-claim/gate/contract)는 정적·텍스트 대리 지표였다. `lens` 가 "이 테스트가 실제로 실패할 수 있나?"를 *묻지만*, 실행으로 탐지력을 증명하는 장치는 없었다 — 아무것도 검사하지 않는 초록 스위트를 걸러낼 수 없었다.
19
+ - **`leerness referee add|verify|list|show|drop`**: 검증기를 신뢰하기 전에 ① known-good 통과 ② 일부러 망가뜨린 known-bad 거부 ③ **거부 사유가 심어둔 결함과 일치**(`--expect-bad`) ④ 명령/기대치 변경 시 지문 불일치로 **자동 stale**(신뢰 철회). 무관한 실패(환경 오류·실행 불가·타임아웃)는 탐지력으로 인정하지 않는다.
20
+ - **게이팅**: `verify-claim <T-ID> --referee <id>` — 미등록/미검증/stale 검증기는 실행 전에 거부(`referee_not_found`/`referee_stale`), 캘리브레이션된 검증기가 거부하면 `referee_failed`. MCP `leerness_referee`(safe-write) + `leerness_verify_claim` 의 `refereeId`.
21
+ - 적대적 FP 프로브 9종 실측 통과: 기준선 빨간 상태·bad 통과·사유 불일치·실행 불가·명령 변경 stale·미검증 사용·검증기 실패·없는 id·손상 스토어 fail-closed.
22
+ - **정직성**: ponytail 의 "코드 54%↓·비용 20%↓" 등 벤치 수치는 **인용하지 않는다**(모델 1개·저장소 1계열 기반이고 ponytail 자신도 해당 수치가 낡았다고 명시). leerness 가 효율을 주장하려면 품질을 고정한 on/off 대조 실험이 필요하다 — 이번 릴리스는 그런 주장을 하지 않는다.
23
+ - 기각(양 검토 합의): ponytail 명령군·intensity 모드(lens 토글 중복) · LOC 를 게이트로 · 마이그레이션 러너/빌드 데몬(정체성 변질) · **파일 존재=완료**(핵심 불변식 위반) · 실패 후 자동 룰 완화. 룰 스냅샷(P-0002)·최소성 사다리(P-0003)는 승인 대기.
24
+ - 검증: selftest 334(문서 drift 가드가 88→89 불일치를 잡아 README 정합), e2e +1.
25
+
26
+ ## 1.36.79 — 2026-07-28 — 실 프로젝트 60개 도그푸딩: P1 5건 (합성 헌트 10회가 못 본 결함)
27
+
28
+ 사용자의 **실제 프로젝트 60개**(v0.1.40 ~ 1.36.77)를 5개 렌즈로 병렬 감사(읽기 전용 + temp 복사본 실험). 합성 픽스처 기반 codex 헌트 10라운드가 한 번도 못 본 P1 5건이 나왔다 — 실데이터에서만 발화하는 조건들이었다.
29
+
30
+ - **P1 progress-tracker 행 무단 삭제 (실 데이터 손실)**: ID 접두를 `T|M|D` 로만 인정해 `R-`/`UR-` 등 다른 접두 행이 파싱에서 탈락 → 재작성 시 **사용자 이력이 조용히 삭제**됐다(실측: auto-Trading 9행 → 1행, 942B, exit 0 무경고). migrate 만이 아니라 `task add` 등 모든 쓰기 경로 공통. 접두 일반화 + **클래스 가드**(디스크 행 수 > 쓰려는 행 수면 쓰기 중단) — 규칙 하나가 아니라 불변식으로 차단.
31
+ - **P1 `handoff --json` 이 사람용 drift 배너로 오염**: stale 프로젝트에서만 발화해(실 프로젝트 27/37 해당) JSON.parse 가 실패, MCP/AI 소비자가 전부 깨졌다 → `--json` 이면 배너 억제(텍스트 모드는 유지).
32
+ - **P1 `parent detect <path>` 가 positional 경로 무시**: cwd 를 분석해 자신만만한 오답을 exit 0 으로 반환(positional path 잔존 클래스의 미소진 지점).
33
+ - **P1 tech 언어 오판**: devDependency 1개짜리 package.json 이 **205개 .py 파일을 이겨** Python 모노레포를 "javascript" 로 라벨링 → 실제 소스 파일 수를 1순위 근거로 재정렬 + 스캔 깊이 2→3(3단 레이아웃 누락 해소). 진짜 JS 프로젝트는 무회귀.
34
+ - **P1 audit 이 파괴된 설치를 healthy 로 보고**: 56개 중 44개가 사라진 설치에 `healthy:true·failures:0`(같은 디렉토리에서 integrity 는 `ok:false` — 두 판정 모순) → integrity 의 관리문서 부재/손상 신호를 audit 이 공유(단일 진실). 3건 이상 부재는 차단 등급.
35
+ - (문서) `doctor` 는 설계상 CLI 설치 진단이라 경로 인자가 없다 — usage 2곳의 잘못된 `doctor [path]` 광고 정정.
36
+ - **(R-0001 검수 21회전이 제 수정에서 P1급 5건을 더 차단)**: ① 클래스 가드의 기준선이 틀렸다 — "변이 후 행 수" 비교는 upsert 가 행을 추가하면 손실을 상쇄해 무력화(실측) → **"디스크에 파서가 못 읽는 행-모양 라인이 있는가"**로 재설계. ② `allowShrink` boolean 우회가 가드를 통째로 꺼 dedup 중 손상 행까지 삭제 → **expectedShrink 수량제**로. ③ 접두 전면 개방(`[A-Z]{1,3}`)이 무관한 참조표(`| API-123 | owner |`)를 task 로 흡수 → **등록 네임스페이스(T/M/D/R/UR/F/P)** 한정. ④ 표 **뒤 사용자 콘텐츠**(참조표·메모·푸터)가 재작성 때 삭제되던 것 → 후행 보존. ⑤ audit 이 integrity 임계값을 재구현해 판정이 갈림 → **단일 판정기 공유**. ⑥ tech 디렉토리당 400개 컷이 집계를 편향(1501 JS < 500 PY) → 상한 5000 + 전체 예산 20000. ⑦ parent 가 맨 상대경로를 무시하고 없는 경로도 성공 → 위치인자 파싱 + 존재 검증.
37
+ - 검증: P1 5건 + 검수 7건 재현→수정→재확인 실측(무회귀 포함), selftest 334, e2e +1(10 단언).
38
+
3
39
  ## 1.36.78 — 2026-07-28 — codex 10차 홀리스틱 헌트: 오케스트레이션/분석 명령군 7건 수정 (병렬 적대검증 8건 중 7 CONFIRMED·1 REFUTED)
4
40
 
5
41
  표면 회전으로 team/agents·constraints/intent·contract·milestones 를 헌트. 8건 보고를 8개 독립 에이전트로 병렬 재현(워크플로) → 7 CONFIRMED 수정, 1 REFUTED(pulse).
package/README.ko.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  > **어떤 언어, 어떤 AI 에이전트로 작업하든 — "증거 없이는 끝났다고 말할 수 없게" 만드는 AI 코딩 운영 레이어.** 코드를 대신 쓰는 도구가 아니라, AI 에이전트의 **기억·인수인계·검증·감사·보안 가드**를 프로젝트에 영속화하는 CLI + MCP 서버입니다.
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-88-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-89-blue) · **런타임 의존성 0** · **install-script 0** · offline-first · Node ≥ 18 · MIT
17
17
 
18
18
  ---
19
19
 
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-88-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-89-blue) · **0 runtime deps** · **0 install scripts** · offline-first · Node ≥ 18 · MIT
16
16
 
17
17
  **🇰🇷 한국어 전문: [README.ko.md](./README.ko.md)**
18
18
 
@@ -122,7 +122,7 @@ MIT
122
122
  <!-- leerness:project-readme:start -->
123
123
  ## Leerness Project Harness
124
124
 
125
- 이 프로젝트는 Leerness v1.36.78 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
+ 이 프로젝트는 Leerness v1.36.80 하네스를 사용합니다. 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.78는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **88개 도구**를 노출:
179
+ Leerness v1.36.80는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **89개 도구**를 노출:
180
180
 
181
181
  ```jsonc
182
182
  // 카테고리별
@@ -189,7 +189,7 @@ Leerness v1.36.78는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
189
189
  // • Workflow: session_close / agents_list / task_export / env_check / usage_stats / reuse_map / whats_new
190
190
 
191
191
  // MCP server 실행: leerness mcp serve
192
- // tools/list 응답: 88 도구
192
+ // tools/list 응답: 89 도구
193
193
  ```
194
194
 
195
195
  ### Autonomous mode (자율 모드)
@@ -197,7 +197,7 @@ Leerness v1.36.78는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
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.78 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
+ 현재 누적: **v1.9.x → 1.36.80 릴리스 태그 이력** (수백 라운드) · _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.78: 2026-07-28
238
+ Last synced by Leerness v1.36.80: 2026-07-28
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.78';
37
+ const VERSION = '1.36.80';
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') 시 호스트 프로세스 오염.
@@ -532,14 +532,19 @@ function skillLock(skills, root) {
532
532
  const set = new Set(skills);
533
533
  try {
534
534
  if (root) {
535
+ // 1.36.80: 내장 카탈로그에서 빠진 스킬(도메인 7종 제거)이라도 **이미 설치돼 있으면** 락에 남긴다 —
536
+ // 디스크 실재를 기록한다는 F12 원칙 유지 + 기존 사용자의 스킬이 락에서 유령처럼 사라지지 않게(비파괴).
537
+ // 판단 근거는 skill.json/SKILL.md 존재(= leerness 가 설치한 흔적) — 임의 디렉토리는 여전히 배제.
535
538
  const dir = path.join(root, '.harness', 'skills');
536
539
  if (exists(dir)) for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
537
- if (e.isDirectory() && skillCatalog[e.name]) set.add(e.name);
540
+ if (!e.isDirectory()) continue;
541
+ if (skillCatalog[e.name]) { set.add(e.name); continue; }
542
+ if (exists(path.join(dir, e.name, 'skill.json')) || exists(path.join(dir, e.name, 'SKILL.md'))) set.add(e.name);
538
543
  }
539
544
  }
540
545
  } catch {}
541
546
  const data = { leernessVersion: VERSION, updatedAt: now(), installedSkills: {} };
542
- for (const s of [...set].sort()) data.installedSkills[s] = skillCatalog[s] || { version: 'unknown' }; // (검수 #4) 정렬 직렬화 — 동일 멤버십이면 바이트 동일(no-op 보존)
547
+ for (const s of [...set].sort()) data.installedSkills[s] = skillCatalog[s] || { version: 'unknown', source: 'installed', note: 'not in built-in catalog (user-installed or removed from defaults)' }; // (검수 #4) 정렬 직렬화 — 동일 멤버십이면 바이트 동일(no-op 보존)
543
548
  return JSON.stringify(data, null, 2) + '\n';
544
549
  }
545
550
 
@@ -1020,6 +1025,12 @@ async function resolveInstallOptions(root, opts = {}) {
1020
1025
  const explicitSkills = arg('--skills', null);
1021
1026
  let lang = explicitLang ? detectLanguageValue(root, explicitLang) : detectLanguageValue(root, 'auto');
1022
1027
  let skills = explicitSkills ? parseSkillsValue(explicitSkills) : [];
1028
+ // 1.36.80 (검수 #10): 사용자가 **명시한** 스킬 id 가 카탈로그에 없으면 조용히 무시하지 않고 분명히 알린다
1029
+ // (1.36.80 에서 도메인 7종을 내장에서 제거했으므로 구 명령이 조용히 0개 설치로 끝나는 것을 막는다).
1030
+ if (explicitSkills && skills.unknown && skills.unknown.length) {
1031
+ warn(`요청한 스킬 미존재: ${skills.unknown.join(', ')} — 1.36.80 에서 내장 기본 스킬이 정리되었습니다(사용 가능: ${Object.keys(skillCatalog).join(', ') || '(없음)'})`);
1032
+ log(` ⓘ 이미 설치된 스킬은 그대로 유지됩니다. 외부 스킬은 leerness skill install <경로|URL> 로 추가하세요.`);
1033
+ }
1023
1034
  const shouldAsk = !has('--yes') && !opts.nonInteractive && process.stdin.isTTY && process.stdout.isTTY && !opts.migration;
1024
1035
  // 1.9.269 (UR-0022): 비대화형 auto 설치(npx leerness init --yes 등)에서 OS 시스템 언어가 적용됐으면 투명성 안내.
1025
1036
  // --language 명시 시 미발화 (explicitLang). 대화형은 이후 언어 prompt 가 처리.
@@ -3867,7 +3878,7 @@ function _selfTestCases() {
3867
3878
  { name: 'UR-0025 심층: i18n STRINGS catalog→lib/catalogs + _translate→pure-utils 분리 (1.9.338)', run: () => { const c = require('../lib/catalogs'); const m = require('../lib/pure-utils'); const catOk = c.STRINGS && typeof c.STRINGS['common.ready'] === 'object' && c.STRINGS['common.ready'].en === 'Ready'; const work = m._translate(c.STRINGS, 'common.ready', 'en') === 'Ready' && m._translate(c.STRINGS, 'common.ready', 'ko') === '준비 완료' && m._translate(c.STRINGS, 'no.such.key', 'en') === 'no.such.key' && m._translate(null, 'x', 'ko') === 'x' && m._translate({ k: { ko: '케이' } }, 'k', 'en') === '케이'; const src = read(__filename); const moved = STRINGS === c.STRINGS && _translate === m._translate && !/const STRINGS = \{/.test(src); return catOk && work && moved; } },
3868
3879
  { name: 'UR-0053: decisions canonical JSON 레이어(_loadDecisions/_saveDecisions/decisionsJsonPath) + pure 파서/렌더 round-trip (1.9.339)', run: () => { const m = require('../lib/pure-utils'); const md = '# Decisions\n\n### 2026-06-05 — A\n- Decision: a\n- Reason: r\n- Alternatives: alt\n- Impact: imp\n\n### 2026-06-04 — B\n- Decision: b\n- Alternatives:\n'; const objs = m._decisionsFromMd(md); const parseOk = objs.length === 2 && objs[0].alternatives === 'alt' && objs[0].impact === 'imp' && objs[1].alternatives === null && objs[1].title === 'B'; const rt = m._decisionsFromMd(m._renderDecisionsMd(objs)); const rtOk = JSON.stringify(rt) === JSON.stringify(objs); const tplOk = m._decisionsFromMd(m._renderDecisionsMd([])).length === 0; const layerOk = typeof _loadDecisions === 'function' && typeof _saveDecisions === 'function' && typeof decisionsJsonPath === 'function' && _decisionsFromMd === m._decisionsFromMd; return parseOk && rtOk && tplOk && layerOk; } },
3869
3880
  { name: 'UR-0058: lessons canonical JSON 레이어(_loadLessons/_saveLessons/lessonsJsonPath) + pure 파서/렌더 round-trip', run: () => { const m = require('../lib/pure-utils'); const objs = [{ date: '2026-06-05', text: 'A', tag: 't' }, { date: '2026-06-04', text: 'B', tag: null }]; const rt = m._parseLessonEntries(m._renderLessonsMd(objs)); const rtOk = JSON.stringify(rt) === JSON.stringify(objs); const tplOk = m._parseLessonEntries(m._renderLessonsMd([])).length === 0; const layerOk = typeof _loadLessons === 'function' && typeof _saveLessons === 'function' && typeof lessonsJsonPath === 'function' && _renderLessonsMd === m._renderLessonsMd; return rtOk && tplOk && layerOk; } },
3870
- { name: 'UR-0025 심층: BUILTIN_CATALOG→lib/catalogs + _withBuiltinSource→pure-utils 분리 (1.9.341)', run: () => { const c = require('../lib/catalogs'); const m = require('../lib/pure-utils'); const catOk = c.BUILTIN_CATALOG && Object.keys(c.BUILTIN_CATALOG).length === 9 && c.BUILTIN_CATALOG.office && c.BUILTIN_CATALOG.office.version === '1.0.0'; const out = m._withBuiltinSource(c.BUILTIN_CATALOG); const work = Object.keys(out).length === 9 && Object.values(out).every(v => v._source === 'builtin') && out.office.version === '1.0.0' && Array.isArray(out.office.capabilities) && Object.keys(m._withBuiltinSource(null)).length === 0; const src = read(__filename); const moved = BUILTIN_CATALOG === c.BUILTIN_CATALOG && _withBuiltinSource === m._withBuiltinSource && !/const BUILTIN_CATALOG = \{/.test(src); return catOk && work && moved; } },
3881
+ { name: 'UR-0025 심층: BUILTIN_CATALOG→lib/catalogs + _withBuiltinSource→pure-utils 분리 (1.9.341)', run: () => { const c = require('../lib/catalogs'); const m = require('../lib/pure-utils'); const catOk = c.BUILTIN_CATALOG && Object.keys(c.BUILTIN_CATALOG).length === 2 && c.BUILTIN_CATALOG['feature-implementation'] && c.BUILTIN_CATALOG['project-roadmap-generator'] && Object.values(c.BUILTIN_CATALOG).every(v => v.verification !== 'passed'); const out = m._withBuiltinSource(c.BUILTIN_CATALOG); const work = Object.keys(out).length === 2 && Object.values(out).every(v => v._source === 'builtin') && out['feature-implementation'].version === '1.0.0' && Array.isArray(out['feature-implementation'].capabilities) && Object.keys(m._withBuiltinSource(null)).length === 0; const src = read(__filename); const moved = BUILTIN_CATALOG === c.BUILTIN_CATALOG && _withBuiltinSource === m._withBuiltinSource && !/const BUILTIN_CATALOG = \{/.test(src); return catOk && work && moved; } },
3871
3882
  { name: 'UR-0025 심층: ROADMAP_STATUS_LABEL/COLOR→lib/catalogs 분리 (1.9.342)', run: () => { const c = require('../lib/catalogs'); const lblOk = c.ROADMAP_STATUS_LABEL && Object.keys(c.ROADMAP_STATUS_LABEL).length === 11 && c.ROADMAP_STATUS_LABEL.done === '완료' && c.ROADMAP_STATUS_LABEL.blocked === '오류'; const colOk = c.ROADMAP_STATUS_COLOR && Object.keys(c.ROADMAP_STATUS_COLOR).length === 11 && c.ROADMAP_STATUS_COLOR.done === '#16a34a' && c.ROADMAP_STATUS_COLOR.skill === '#8b5cf6'; const src = read(__filename); const moved = ROADMAP_STATUS_LABEL === c.ROADMAP_STATUS_LABEL && ROADMAP_STATUS_COLOR === c.ROADMAP_STATUS_COLOR && !/const ROADMAP_STATUS_LABEL = \{/.test(src); return lblOk && colOk && moved; } },
3872
3883
  { name: 'UR-0025 심층: SECRET_PATTERNS→lib/catalogs 보안 응집 분리 (1.9.343)', run: () => { const c = require('../lib/catalogs'); const catOk = Array.isArray(c.SECRET_PATTERNS) && c.SECRET_PATTERNS.length === 20 && c.SECRET_PATTERNS.every(p => p.name && p.re instanceof RegExp); const A = 'A'.repeat(40); const hit = (s) => c.SECRET_PATTERNS.some(p => { p.re.lastIndex = 0; return p.re.test(s); }); const det = hit('AKIA' + 'ABCD1234EFGH5678') && hit('sk-' + 'ant-api03-' + A + '_' + A) && !hit('const u = "john' + '_doe_2024";'); const moved = SECRET_PATTERNS === c.SECRET_PATTERNS; return catOk && det && moved; } },
3873
3884
  { name: 'UR-0025 심층: SKILL_CATALOG_PRESETS→lib/catalogs 분리 (1.9.344)', run: () => { const c = require('../lib/catalogs'); const catOk = c.SKILL_CATALOG_PRESETS && Object.keys(c.SKILL_CATALOG_PRESETS).length === 2 && c.SKILL_CATALOG_PRESETS.vercel && c.SKILL_CATALOG_PRESETS.vercel.owner === 'vercel-labs' && c.SKILL_CATALOG_PRESETS.anthropic && c.SKILL_CATALOG_PRESETS.anthropic.repo === 'skills'; const moved = SKILL_CATALOG_PRESETS === c.SKILL_CATALOG_PRESETS; return catOk && moved; } },
@@ -5880,6 +5891,41 @@ const _INTEGRITY_DOCS = [
5880
5891
  '.harness/consistency-policy.md', '.harness/testing-strategy.md', '.harness/review-checklist.md',
5881
5892
  '.harness/release-checklist.md', '.harness/session-close-policy.md', '.harness/plan-progress-boundary.md',
5882
5893
  ];
5894
+ // 1.36.79 (도그푸딩 P1-E): integrity 의 "관리 문서 부재/손상" 판정을 audit 도 공유하도록 경량 추출.
5895
+ // audit 이 존재-여부만 보고 healthy 를 선언해 integrity 와 모순되던 것 해소(두 명령 단일 진실).
5896
+ // (검수 #5) 임계값·H1 판정을 재구현하면 두 명령이 갈린다(실측: 10KB→520B 문서를 integrity 는 truncated, audit 은 무탐).
5897
+ // integrityCmd 와 **동일한 단일 판정기**를 쓴다 — 여기가 유일한 구현이고 integrityCmd 도 이 함수를 호출한다.
5898
+ function _integrityFindings(root) {
5899
+ root = absRoot(root);
5900
+ if (!exists(path.join(root, '.harness'))) return [];
5901
+ let minimal = false;
5902
+ try { minimal = !!JSON.parse(read(path.join(root, '.harness', 'manifest.json'))).minimal; } catch {}
5903
+ const lang = detectLanguageValue(root, 'auto');
5904
+ const templates = coreFiles(root, lang, [], { minimal });
5905
+ const out = [];
5906
+ for (const key of _INTEGRITY_DOCS.filter(k => typeof templates[k] === 'string')) {
5907
+ const tmpl = templates[key];
5908
+ const fp = path.join(root, key);
5909
+ let kind = null;
5910
+ if (!exists(fp)) kind = 'missing';
5911
+ else {
5912
+ let cur = ''; try { cur = read(fp); } catch { kind = 'unreadable'; }
5913
+ if (!kind) {
5914
+ // 코드펜스 안의 `# 주석`을 H1 로 오인하지 않도록 펜스 밖 라인만 검사 (integrityCmd 와 동일 규칙)
5915
+ let _fence = false;
5916
+ const _hasH1 = cur.split('\n').some(l => {
5917
+ const t = l.trim();
5918
+ if (t.startsWith('```')) { _fence = !_fence; return false; }
5919
+ return !_fence && /^# \S/.test(t);
5920
+ });
5921
+ if (!_hasH1) kind = 'h1_lost';
5922
+ else if (cur.length < tmpl.length * 0.4) kind = 'truncated';
5923
+ }
5924
+ }
5925
+ if (kind) out.push({ file: key, kind, templateBytes: Buffer.byteLength(tmpl, 'utf8') });
5926
+ }
5927
+ return out;
5928
+ }
5883
5929
  function integrityCmd(root) {
5884
5930
  root = absRoot(root);
5885
5931
  const json = has('--json');
@@ -6764,6 +6810,7 @@ function commandsCmd(root) {
6764
6810
  { cmd: 'clarify "<사용자 요청>" [--json]', desc: '요청 모호성 신호 감지 → 사용자에게 물을 질문 생성 (추측 구현 방지) — 1.36.51 UR-0061' },
6765
6811
  { cmd: 'tech [--json]', desc: '기술 프로필 — 개발 언어·연결 서비스 자동 감지 + 마이그레이션/언어전환 이력, 그래프 🛠 탭 표시 — 1.36.53 UR-0062' },
6766
6812
  { cmd: 'integrity check [--repair] [--json]', desc: 'managed 정책-문서 12종 무결성(부재/H1 상실/절단) 점검 — --repair: archive 대피 후 템플릿 재생성 — 1.36.57 감사 F-04' },
6813
+ { cmd: 'referee add|verify|list|show|drop', desc: '검증기 캘리브레이션 — 신뢰 전에 탐지력 증명(known-good 통과 + 망가뜨린 known-bad 를 기대 사유로 거부). verify-claim --referee / gate 게이팅 — 1.36.80 P-0001' },
6767
6814
  { cmd: 'preview add|list|show|approve|revise|mockup', desc: '신규 기능 미리보기 승인 워크플로 — approve 전 코드 작성 금지 계약. mockup <P-ID> [--force]: 자립형 HTML 디자인 시안 스캐폴드 · add --mockup <파일>: 기존 시안 첨부 — 1.36.51 UR-0061 · 1.36.75 UR-0066' },
6768
6815
  { cmd: 'review <file> --persona <ids>', desc: '페르소나 리뷰 (1.9.29)' },
6769
6816
  { cmd: 'brainstorm "<topic>" [--include-code]', desc: '워크스페이스 회수 + 코드 grep' }
@@ -8541,7 +8588,7 @@ function _autoFixIdempotency(root) {
8541
8588
  kept.push(r);
8542
8589
  }
8543
8590
  if (removedExact > 0 || droppedSameText > 0) {
8544
- writeProgressRows(root, header, kept);
8591
+ writeProgressRows(root, header, kept, { expectedShrink: removedExact }); // 1.36.79 (검수 #1): 제거한 만큼만 축소 허용 — 손상 행은 여전히 차단
8545
8592
  fixed.push({ kind: 'task-duplicate-request', action: 'deduped', removedExact, droppedSameText });
8546
8593
  }
8547
8594
  }
@@ -9180,6 +9227,9 @@ function _saveTeams(root, teams) {
9180
9227
  const _team = require('../lib/team');
9181
9228
  const _tgl = require('../lib/toggles'); // 1.36.30: 기능 토글 (그래프 ⚙ 탭 연동 — gate/lens/auto-graph/delegation-brief)
9182
9229
  const _clar = require('../lib/clarify'); // 1.36.51 (UR-0061): 모호성 질문 + 미리보기 승인 워크플로
9230
+ const _ref = require('../lib/referee'); // 1.36.80 (P-0001): 검증기 캘리브레이션 — 검증기 자신의 탐지력을 실행으로 증명
9231
+ const _pvsrv = require('../lib/preview-serve'); // 1.36.80 (UR-0067): 라이브 미리보기 — 임시 워크스페이스 + 로컬 서버 + 확인 후 정리
9232
+ function refereeCmd(root, sub, rest) { return _ref.refereeCmd(root, sub, rest, { has, arg, VERSION, _withLock }); }
9183
9233
  const _tech = require('../lib/tech-profile'); // 1.36.53 (UR-0062): 기술 프로필 (언어·서비스 감지 + 변경 이력)
9184
9234
  function teamCmd(root, sub, id, opts = {}) { return _team.teamCmd(root, sub, id, opts, { VERSION, _loadTeams, _loadTeamsChecked, _saveTeams, _detectShellCtx, arg, has, _withLock, failJson }); } // 1.36.31: add 경합 락 · 1.36.78: 형상무효 가드 + failJson
9185
9235
 
@@ -9210,11 +9260,19 @@ function nextId(root, prefix) {
9210
9260
  return `${prefix}-${String(max + 1).padStart(4, '0')}`;
9211
9261
  }
9212
9262
 
9263
+ // 1.36.79 (도그푸딩 P1-A, 실 프로젝트 데이터 손실): ID 접두를 T/M/D 로만 인정해 R-/UR- 등 다른 접두 행이
9264
+ // 파싱에서 탈락 → writeProgressRows 가 파싱된 행만으로 파일을 재작성하며 **사용자 이력을 조용히 삭제**했다
9265
+ // (실측: auto-Trading 9행 → 1행, 942B 소실, exit 0 무경고). migrate 만이 아니라 task add 등 모든 쓰기 경로 공통.
9266
+ // 접두를 일반화(대문자 1~3자)해 왕복 보존.
9267
+ // (검수 #2) 다만 `[A-Z]{1,3}` 전면 개방은 과했다 — 사용자가 트래커에 붙여 둔 무관한 표(`| API-123 | owner | ... |`)까지
9268
+ // task 로 흡수해 task list 에 노출·정규화됐다(실측). leerness 가 실제 발급하는 **등록 네임스페이스만** 인정한다.
9269
+ const _PROGRESS_ID_NS = ['T', 'M', 'D', 'R', 'UR', 'F', 'P'];
9270
+ const _PROGRESS_ROW_RE = new RegExp('^\\|\\s*(?:' + _PROGRESS_ID_NS.join('|') + ')-\\d{3,}\\s*\\|');
9213
9271
  function readProgressRows(root) {
9214
9272
  const text = exists(progressPath(root)) ? read(progressPath(root)) : '';
9215
9273
  const rows = [];
9216
9274
  for (const line of text.split('\n')) {
9217
- if (!/^\| (?:T|M|D)-\d{4,} \|/.test(line)) continue;
9275
+ if (!_PROGRESS_ROW_RE.test(line)) continue;
9218
9276
  // 1.9.399 (7번째 버그헌트 P1-A, UR-0104): 비이스케이프 파이프에서만 분리 + 셀 복원 — 사용자 텍스트의 '|'(이스케이프됨)이 컬럼을 깨지 않음.
9219
9277
  const cells = line.split(/(?<!\\)\|/).slice(1, -1).map(s => _cellUnescape(s).trim());
9220
9278
  if (cells.length < 6) continue;
@@ -9245,11 +9303,54 @@ function progressHeader(root) {
9245
9303
  }
9246
9304
  return text.slice(0, text.indexOf('\n', idx)).trimEnd();
9247
9305
  }
9248
- function writeProgressRows(root, header, rows) {
9306
+ function writeProgressRows(root, header, rows, opts = {}) {
9307
+ // 1.36.79 (도그푸딩 P1-A 클래스 가드): 파서가 놓친 행이 재작성으로 사라지는 사고를 규칙 하나가 아니라 **불변식**으로 차단 —
9308
+ // 디스크의 행-모양 라인 수가 쓰려는 행 수보다 많으면 쓰기를 중단한다(향후 파서 강화/CRLF/셀부족 등 같은 클래스 전부 포착).
9309
+ // 단 **의도적 행 감소**(audit --fix 의 완전중복 dedup)는 opts.allowShrink 로 명시 옵트인 — 오탐으로 정당한 정리를 막지 않는다.
9310
+ // 판정 기준은 "변이 후 행 수"가 아니라 **디스크에 파서가 못 읽는 행-모양 라인이 있는가** — upsert 가 행을 추가하면
9311
+ // 개수 비교는 손실을 상쇄해 무력화된다(실측: 디스크 2 vs 쓰기 2 로 통과, 셀부족 행 소실).
9312
+ // (검수 #1) 의도적 축소는 boolean 우회가 아니라 **예상 감소량**으로 — allowShrink:true 는 가드를 통째로 꺼서
9313
+ // dedup 중 손상 행까지 조용히 삭제했다. expectedShrink 만큼만 허용하고 손상 행 존재는 여전히 차단한다.
9314
+ try {
9315
+ const _cur = exists(progressPath(root)) ? read(progressPath(root)) : '';
9316
+ if (_cur) {
9317
+ let _shaped = 0, _parsable = 0;
9318
+ for (const line of _cur.split('\n')) {
9319
+ if (!_PROGRESS_ROW_RE.test(line)) continue;
9320
+ _shaped++;
9321
+ if (line.split(/(?<!\\)\|/).slice(1, -1).length >= 6) _parsable++;
9322
+ }
9323
+ if (_parsable < _shaped) {
9324
+ const _msg = `progress-tracker.md 행 손실 위험 — 행-모양 ${_shaped}건 중 ${_parsable}건만 파싱 가능(셀 부족/형식 이상). ` +
9325
+ `쓰면 나머지 ${_shaped - _parsable}행이 사라지므로 중단합니다(데이터 보호): ${progressPath(root)} 의 해당 행을 6칸으로 고치거나 .harness/archive 백업 복원 후 재시도`;
9326
+ throw Object.assign(new Error(_msg), { code: 'E_PROGRESS_ROW_LOSS' });
9327
+ }
9328
+ const _expect = Number.isInteger(opts.expectedShrink) ? Math.max(0, opts.expectedShrink) : 0;
9329
+ if (_parsable - _expect > rows.length) {
9330
+ const _msg = `progress-tracker.md 행 손실 위험 — 파싱 ${_parsable}행 − 예상감소 ${_expect} > 쓰려는 ${rows.length}행. 쓰기 중단(데이터 보호): ${progressPath(root)}`;
9331
+ throw Object.assign(new Error(_msg), { code: 'E_PROGRESS_ROW_LOSS' });
9332
+ }
9333
+ }
9334
+ } catch (e) { if (e && e.code === 'E_PROGRESS_ROW_LOSS') throw e; }
9249
9335
  // 1.9.399 (7번째 버그헌트 P1-A, UR-0104): 셀 콘텐츠 안전화 — 개행→공백, '|'→'\|'. 사용자 텍스트의 파이프/개행이 표 행을 손상/주입 못 함.
9336
+ // 1.36.79 (검수 #2 후반): 표 **뒤에 사용자가 쓴 내용**(참조표·메모·`Status values:` 푸터)이 재작성 때 통째로 사라지던 것 —
9337
+ // 마지막 task 행 이후의 비-행 콘텐츠를 그대로 이어붙여 보존한다.
9338
+ let _suffix = '';
9339
+ try {
9340
+ const _cur0 = exists(progressPath(root)) ? read(progressPath(root)) : '';
9341
+ if (_cur0) {
9342
+ const _lines = _cur0.split('\n');
9343
+ let _lastRow = -1;
9344
+ for (let i = 0; i < _lines.length; i++) if (_PROGRESS_ROW_RE.test(_lines[i])) _lastRow = i;
9345
+ if (_lastRow >= 0) {
9346
+ const _tail = _lines.slice(_lastRow + 1).join('\n').replace(/^\n+/, '');
9347
+ if (_tail.trim()) _suffix = '\n' + _tail.replace(/\s+$/, '') + '\n';
9348
+ }
9349
+ }
9350
+ } catch {}
9250
9351
  const composed = header + '\n' +
9251
9352
  rows.map(r => `| ${_cellSafe(r.id)} | ${_cellSafe(r.status)} | ${_cellSafe(r.request)} | ${_cellSafe(r.evidence)} | ${_cellSafe(r.nextAction)} | ${_cellSafe(r.updated)} |`).join('\n') +
9252
- (rows.length ? '\n' : '');
9353
+ (rows.length ? '\n' : '') + _suffix;
9253
9354
  writeUtf8(progressPath(root), composed);
9254
9355
  }
9255
9356
  function upsertProgress(root, row) {
@@ -10295,7 +10396,7 @@ function debug(root) {
10295
10396
 
10296
10397
  const _audit = require('../lib/audit');
10297
10398
  // 1.9.421 (UR-0025/UR-0125 큰 핸들러 모듈화 6번째): audit → lib/audit.js (DI 위임, thin wrapper)
10298
- function audit(root, opts = {}) { return _audit.audit(root, opts, { VERSION, arg, has, planPath, readProgressRows, currentStatePath, handoffPath, envDiff, _readFeatureGraph, _matchAPISkills, _listAPISkills, _collectSecretFindings }); }
10399
+ function audit(root, opts = {}) { return _audit.audit(root, opts, { VERSION, arg, has, planPath, readProgressRows, currentStatePath, handoffPath, envDiff, _readFeatureGraph, _matchAPISkills, _listAPISkills, _collectSecretFindings, _integrityFindings }); } // 1.36.79: integrity 신호 공유
10299
10400
 
10300
10401
  // 1.9.312 (UR-0050, 설치리뷰 3중수렴): secret 스캐너 현대 키 패턴 보강.
10301
10402
  // 배경: 기존 OpenAI 패턴 `sk-[A-Za-z0-9]{32,}` 은 하이픈에서 끊겨 sk-proj-/sk-svcacct- (modern 프로젝트/서비스 키)를 놓침.
@@ -12440,7 +12541,10 @@ function handoffCmd(root) {
12440
12541
  // 1.9.38 (A): drift 임계 시 .harness/agent-reminders.md 자동 생성 — 메인 에이전트 프롬프트에 표시되도록.
12441
12542
  // 1.9.38 (D): skip 횟수 학습 — --no-drift-check 빈도 ≥5 시 임계 완화 (1d → 2d).
12442
12543
  const absR0 = absRoot(root || process.cwd());
12443
- if (exists(path.join(absR0, '.harness')) && process.env.LEERNESS_NO_DRIFT_CHECK !== '1') {
12544
+ // 1.36.79 (도그푸딩 P1-B, 실 프로젝트 27/37 해당): 사람용 drift 배너가 --json 모드에서도 stdout 에 찍혀
12545
+ // handoff --json 등의 응답을 통째로 파싱 불가로 만들었다(실측: JSON.parse 실패). stale 프로젝트에서만 발화해
12546
+ // 합성 픽스처 헌트가 못 봤다. --json 이면 배너 억제(구조화 필드/텍스트 모드로만 노출).
12547
+ if (exists(path.join(absR0, '.harness')) && process.env.LEERNESS_NO_DRIFT_CHECK !== '1' && !has('--json')) {
12444
12548
  // skip 카운트
12445
12549
  if (has('--no-drift-check')) {
12446
12550
  try {
@@ -12837,6 +12941,21 @@ function verifyClaimCmd(root, taskId, opts = {}) {
12837
12941
  const rows = readProgressRows(root);
12838
12942
  const row = rows.find(r => r.id === taskId);
12839
12943
  if (!row) { if (opts.collect) return { id: taskId, ok: false, reasons: ['not-found'] }; return failJson(_j, 'not_found', `progress-tracker.md에 ${taskId} 없음.`); }
12944
+ // 1.36.80 (P-0001): --referee <id> — 캘리브레이션된 검증기만 신뢰한다. 미검증/무효(stale) 검증기의 통과는 증거가 아니므로
12945
+ // 실행 전에 거부한다(검증기 자신의 탐지력이 증명되지 않은 상태에서 나온 초록은 정보가 없다).
12946
+ const _refId = opts.refereeId || arg('--referee', null);
12947
+ let _refereeResult = null;
12948
+ if (_refId && _refId !== true) {
12949
+ const _g = _ref.requireReferee(root, String(_refId));
12950
+ if (!_g.ok) { if (opts.collect) return { id: taskId, ok: false, reasons: [_g.code] }; return failJson(_j, _g.code, _g.message); }
12951
+ const _rr = cp.spawnSync(_g.referee.check, { cwd: root, shell: true, encoding: 'utf8', timeout: 600000, maxBuffer: 8 * 1024 * 1024 });
12952
+ _refereeResult = { id: _g.referee.id, check: _g.referee.check, exit: (_rr.status == null ? 1 : _rr.status), calibratedAt: _g.referee.calibration.at };
12953
+ if (_refereeResult.exit !== 0) {
12954
+ const _msg = `referee ${_g.referee.id} 검증 실패(exit ${_refereeResult.exit}) — 캘리브레이션된 검증기가 이 상태를 거부했습니다: ${_g.referee.check}`;
12955
+ if (opts.collect) return { id: taskId, ok: false, reasons: ['referee_failed'], referee: _refereeResult };
12956
+ return failJson(_j, 'referee_failed', _msg);
12957
+ }
12958
+ }
12840
12959
 
12841
12960
  const evidence = row.evidence || '';
12842
12961
  // 1.9.20: 파일 경로 추출 — 도메인 폴더 자동 인식 + 루트 메타파일
@@ -18809,7 +18928,7 @@ function _mcpToCliArgs(name, args, targetPath) {
18809
18928
  case 'leerness_handoff': cliArgs = ['handoff', targetPath, '--compact', '--no-drift-check']; break;
18810
18929
  case 'leerness_drift_check': cliArgs = ['drift', 'check', targetPath, '--json']; break;
18811
18930
  case 'leerness_audit': cliArgs = ['audit', targetPath, '--json', ...(args.fix ? ['--fix'] : []), ...(args.strict ? ['--strict'] : [])]; break;
18812
- case 'leerness_verify_claim': cliArgs = ['verify-claim', args.taskId, '--path', targetPath, ...(args.runTests ? ['--run-tests'] : []), ...(args.strictClaims ? ['--strict-claims'] : []), ...(args.lenient ? ['--lenient'] : [])]; break;
18931
+ case 'leerness_verify_claim': cliArgs = ['verify-claim', args.taskId, '--path', targetPath, ...(args.refereeId ? ['--referee', String(args.refereeId)] : []), ...(args.runTests ? ['--run-tests'] : []), ...(args.strictClaims ? ['--strict-claims'] : []), ...(args.lenient ? ['--lenient'] : [])]; break;
18813
18932
  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 없음)
18814
18933
  case 'leerness_contract_verify': cliArgs = ['contract', 'verify', args.spec, args.impl]; break;
18815
18934
  case 'leerness_agents_list': cliArgs = ['agents', 'list', '--json']; break;
@@ -19016,6 +19135,20 @@ function _mcpToCliArgs(name, args, targetPath) {
19016
19135
  case 'leerness_clarify':
19017
19136
  cliArgs = ['clarify', String(args.text || ''), '--path', targetPath, '--json'];
19018
19137
  break;
19138
+ case 'leerness_referee': { // 1.36.80 (P-0001)
19139
+ const _ra = String(args.action || 'list');
19140
+ cliArgs = ['referee', _ra, '--path', targetPath, '--json'];
19141
+ if (_ra === 'add') {
19142
+ cliArgs.splice(2, 0, String(args.id || ''));
19143
+ if (args.check) cliArgs.push('--check', String(args.check));
19144
+ if (args.good) cliArgs.push('--good', String(args.good));
19145
+ if (args.bad) cliArgs.push('--bad', String(args.bad));
19146
+ if (args.expectBad) cliArgs.push('--expect-bad', String(args.expectBad));
19147
+ } else if (['show', 'verify', 'drop'].includes(_ra)) {
19148
+ cliArgs.splice(2, 0, String(args.id || ''));
19149
+ }
19150
+ break;
19151
+ }
19019
19152
  case 'leerness_preview': {
19020
19153
  const _pa = String(args.action || 'list');
19021
19154
  cliArgs = ['preview', _pa, '--path', targetPath, '--json'];
@@ -23006,7 +23139,7 @@ SETUP & UPDATE
23006
23139
 
23007
23140
  STATUS & DIAGNOSTICS
23008
23141
  status [path] Install status (files present)
23009
- health [path] | doctor [path] Install/state diagnostics
23142
+ health [path] | doctor [--json] health: project state · doctor: CLI install/environment (no path arg)
23010
23143
  verify [path] Required-file verification
23011
23144
  which [--json] Resolve current binary/version (npm cache conflicts)
23012
23145
  selftest [--json] Core-function integrity self-check (CI-friendly)
@@ -23093,7 +23226,7 @@ function help() {
23093
23226
  leerness rule add "<설명>" --trigger every-session|every-update|every-commit|session-start|session-close|pre-publish # 사용자 룰 등록 (1.9.8)
23094
23227
  leerness rule list|verify|pause <id>|resume <id>|remove <id>|stop|resume-all
23095
23228
  leerness context [path] [--json] # 에이전트 온보딩 컨텍스트 (1.9.292, UR-0065)
23096
- leerness health [path] [--json] · leerness doctor [path] # 설치/상태 진단
23229
+ leerness health [path] [--json] · leerness doctor [--json] # health: 프로젝트 상태 · doctor: CLI 설치/환경(경로 인자 없음)
23097
23230
  leerness pulse [path] · leerness milestones [path] · leerness round-history [path] # 종합 지표
23098
23231
  leerness decision add "<title>" [--reason ...] | decision list|drop <id> # 결정 메모리 (.harness, canonical JSON)
23099
23232
  leerness lesson save "<text>" [--tag t] | lesson list|drop <date|text> # 교훈 메모리
@@ -23348,7 +23481,20 @@ async function main() {
23348
23481
  if (_pvRaw[i].startsWith('--')) { if (['--design', '--features', '--note', '--path', '--mockup'].includes(_pvRaw[i]) && _pvRaw[i + 1]) i++; continue; } // 1.36.75 (검수 #2): --mockup 값이 제목에 흡수되던 것
23349
23482
  _pvToks.push(_pvRaw[i]);
23350
23483
  }
23351
- return _clar.previewCmd(arg('--path', process.cwd()), _pvToks[0], _pvToks.slice(1), { has, arg, _withLock }); // 1.36.54 (#2): 변경은 직렬화
23484
+ // 1.36.80 (UR-0067): 라이브 미리보기 serve(로컬 서버) / mode(self|project) 별도 모듈로 위임
23485
+ const _pvRoot = arg('--path', process.cwd());
23486
+ if (_pvToks[0] === 'serve') return _pvsrv.previewServeCmd(_pvRoot, _pvToks[1], { has, arg, previews: _clar._loadPreviews(_pvRoot) });
23487
+ if (_pvToks[0] === 'mode') return _pvsrv.previewModeCmd(_pvRoot, _pvToks[1], { has, arg });
23488
+ return _clar.previewCmd(_pvRoot, _pvToks[0], _pvToks.slice(1), { has, arg, _withLock, _onResolved: (id) => _pvsrv.cleanupServeDir(_pvRoot, id) }); // 1.36.54 (#2): 변경은 락 직렬화 · 1.36.80: 승인/수정 시 임시 워크스페이스 정리
23489
+ }
23490
+ // 1.36.80 (P-0001): referee — 값 플래그가 positional 로 새지 않게 원시 argv 파싱(preview 와 동일 패턴)
23491
+ if (cmd === 'referee') {
23492
+ const _rvRaw = process.argv.slice(2); const _rvI = _rvRaw.indexOf('referee'); const _rvToks = [];
23493
+ for (let i = _rvI + 1; i < _rvRaw.length && _rvI >= 0; i++) {
23494
+ if (_rvRaw[i].startsWith('--')) { if (['--check', '--good', '--bad', '--expect-bad', '--path', '--timeout'].includes(_rvRaw[i]) && _rvRaw[i + 1]) i++; continue; }
23495
+ _rvToks.push(_rvRaw[i]);
23496
+ }
23497
+ return refereeCmd(arg('--path', process.cwd()), _rvToks[0], _rvToks.slice(1));
23352
23498
  }
23353
23499
  if (cmd === 'lens') return lensCmd(args[1]); // 1.18.3 (UR-0003): 분야별 자기질문 품질 렌즈
23354
23500
  // 1.9.233: leerness commands — 카테고리화된 전체 CLI 명령 목록
@@ -23421,7 +23567,19 @@ async function main() {
23421
23567
  if (cmd === 'wakeup-interval') return wakeupIntervalCmd(arg('--path', process.cwd()), args[1], args[2]);
23422
23568
  // 1.9.211: leerness workspace-dir <get|guide> — 현재 워크스페이스 디렉토리 / AI 참조 가이드 (사용자 명시)
23423
23569
  if (cmd === 'workspace-dir') return workspaceDirCmd(arg('--path', process.cwd()), args[1]);
23424
- if (cmd === 'parent') return parentCmd(arg('--path', process.cwd()), args[1]);
23570
+ // 1.36.79 (도그푸딩 P1-C): positional 경로(`parent detect <path>`)를 무시하고 cwd 를 분석해 자신만만한 오답(exit 0) 내던 것
23571
+ // — positional path 잔존 클래스(1.36.33)의 미소진 지점. --path 우선, 없으면 sub 다음 positional.
23572
+ // (검수 #3) _taskPositionalPath 는 './' 접두 등 "경로처럼 보이는" 값만 인정해 `parent detect parent\child` 같은 맨 상대경로를
23573
+ // 무시하고 cwd 를 분석했다. parent 는 하위명령 뒤 첫 non-flag 를 경로로 받고, 존재하는 디렉토리인지 먼저 검증한다.
23574
+ if (cmd === 'parent') {
23575
+ const _pPos = args.slice(2).find(a => a && !a.startsWith('-'));
23576
+ const _pRoot = arg('--path', null) || _pPos || process.cwd();
23577
+ const _pAbs = path.resolve(_pRoot);
23578
+ if (_pPos || arg('--path', null)) {
23579
+ if (!exists(_pAbs) || !fs.statSync(_pAbs).isDirectory()) { failJson(has('--json'), 'path_not_found', `경로 없음 또는 디렉토리 아님: ${_pAbs}`); return; }
23580
+ }
23581
+ return parentCmd(_pAbs, args[1]);
23582
+ }
23425
23583
  // 1.9.211: leerness migrate-workspace-dir — .harness → .leerness 마이그레이션 (사용자 명시)
23426
23584
  if (cmd === 'migrate-workspace-dir') return migrateWorkspaceDirCmd(arg('--path', null) || _taskPositionalPath(args, 1) || process.cwd()); // 1.36.33: positional path 무시 → cwd 오염 (1.36.21/24 계열 잔존 — 실제로 이 버그로 소스 저장소가 오염될 뻔)
23427
23585
  // 1.9.212: leerness idempotency audit — 멱등성 위반 탐지 (사용자 명시)
package/lib/audit.js CHANGED
@@ -34,6 +34,25 @@ function audit(root, opts = {}, deps = {}) {
34
34
  process.exitCode = 1;
35
35
  return;
36
36
  }
37
+ // 1.36.79 (도그푸딩 P1-E, 실 프로젝트): .harness/AGENTS.md 존재만 보고 통과시켜 **56개 중 44개가 사라진 설치**를
38
+ // healthy:true·failures:0 으로 보고했다(같은 디렉토리에서 integrity check 는 ok:false — 두 판정이 모순).
39
+ // integrity 가 이미 계산하는 관리 문서 부재/손상 신호를 audit 에도 반영해 "가장 먼저 부르는 명령"이 진실을 말하게 한다.
40
+ try {
41
+ if (typeof deps._integrityFindings === 'function') {
42
+ const _integ = deps._integrityFindings(root) || [];
43
+ // (검수 #5) integrity 와 동일 kind 집합 — truncated 도 포함해 두 명령의 판정이 갈리지 않게(단, 차단 등급은 missing 기준)
44
+ const _missing = _integ.filter(f => f && ['missing', 'h1_lost', 'truncated', 'unreadable'].includes(f.kind));
45
+ if (_missing.length) {
46
+ // 코어 문서 다수 부재 = 설치 손상(차단). 소수는 경고.
47
+ const _sev = _missing.filter(f => f.kind === 'missing').length >= 3 ? 'fail' : 'warn';
48
+ if (_sev === 'fail') { failures++; fail(`관리 문서 ${_missing.length}건 부재/손상 — 설치 불완전 (leerness init 또는 integrity check --repair)`); }
49
+ else { warnings++; warn(`관리 문서 ${_missing.length}건 부재/손상 (leerness integrity check --repair)`); }
50
+ _finding('managed_docs_incomplete', _sev, 'managed policy docs missing/damaged (install incomplete)', { count: _missing.length, files: _missing.slice(0, 12).map(f => f.file || f.name), kinds: [...new Set(_missing.map(f => f.kind))] });
51
+ } else {
52
+ ok('관리 문서 무결성 OK (integrity)');
53
+ }
54
+ }
55
+ } catch {}
37
56
  // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태파일 JSON 무결성 — 깨진 JSON 을 그레이스풀 폴백(빈 상태)이 "healthy" 로 감추던 false-negative 차단.
38
57
  // 손상 파일을 warning + corrupted_state_json finding 으로 표면화(--strict 시 failure 로 승격). 비-크래시(헬퍼가 파서 예외를 흡수).
39
58
  try {
package/lib/catalogs.js CHANGED
@@ -349,16 +349,9 @@ const STRINGS = {
349
349
 
350
350
  // 1.9.341 (UR-0025 심층): 내장 스킬 catalog (9종) — harness 에서 분리. _loadSkillCatalog 의 builtin fallback.
351
351
  const BUILTIN_CATALOG = {
352
- 'office': { displayNameKo: '마이크로소프트 오피스 자동화 스킬 라이브러리', description: '문서 산출물이 필요할 때 사용 — Word/Excel/PowerPoint 파일을 만들거나 편집하거나 템플릿으로 문서를 뽑아야 때. Use when the task produces or edits Office documents.', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Word/Excel/PowerPoint 문서 자동화', '템플릿 기반 문서 생성', '표/차트/요약 문서화', '민감정보 제외 규칙 적용'] },
353
- 'commerce-api': { displayNameKo: '커머스 API 연동 스킬 라이브러리', description: '커머스 플랫폼(쿠팡·롯데온·스마트스토어) API 를 붙일 때 사용 — 주문/상품/매출 연동 설계나 인증·레이트리밋 처리가 필요할 때. Use when integrating commerce platform APIs.', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['쿠팡·롯데온·스마트스토어 API 연동 설계', '주문/상품/매출 동기화', '환경변수 기반 인증 분리', '레이트리밋/재시도/오류 처리'] },
354
- 'crawling': { displayNameKo: '크롤링·브라우저 자동화 스킬 라이브러리', description: '웹 페이지를 자동으로 조작·수집해야 할 때 사용 — 로그인 세션, 다운로드, 스크린샷 진단, 차단 위험 점검이 걸린 작업일 때. Use when the task drives or scrapes web pages.', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Playwright 기반 자동화', '다운로드/로그인 세션 처리', '스크린샷 기반 실패 진단', '약관/권한/차단 위험 점검'] },
355
- 'firebase': { displayNameKo: 'Firebase·Cloud Functions 스킬 라이브러리', description: 'Firebase/Cloud Functions 작업을 시작할 때 사용 — Functions 배포 구조, 시크릿 분리, IAM 점검, 에뮬레이터 검증이 필요할 때. Use when deploying or configuring Firebase.', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Firebase Functions 배포 구조', '환경변수/시크릿 분리', '권한/IAM 점검', '로컬 에뮬레이터 검증'] },
356
- 'ads-analytics': { displayNameKo: '광고·GA4 분석 스킬 라이브러리', description: '광고·GA4 데이터를 다룰 때 사용 — 이벤트/전환 점검, 광고 데이터 수집, 소스·매체 분석 리포트가 필요할 때. Use when analyzing ads or GA4 data.', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['GA4 이벤트/전환 점검', '광고 데이터 수집 구조화', '소스/매체 분석', '리포트 자동화'] },
357
- 'appstore-review': { displayNameKo: '앱스토어 심사 대응 스킬 라이브러리', description: '앱스토어 심사를 준비하거나 리젝에 대응할 때 사용 — 심사 문구 분석, 개인정보 라벨 점검이 필요할 때. Use when preparing for or responding to app store review.', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['심사 문구 분석', '개인정보 라벨 점검', '리젝 대응 초안', '웹뷰/앱 데이터 수집 구분'] },
358
- 'ai-verified-skill-publisher': { displayNameKo: 'AI 검증 스킬 업로드·라이브러리화 스킬', description: '검증된 스킬을 라이브러리로 정규화해 업로드할 때 사용 — 민감정보 스캔과 업로드 게이트가 필요할 때. Use when publishing a verified skill to a library.', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['검증된 스킬 정규화', '민감정보 스캔', 'AI 검증 메타데이터 작성', 'npm/git 업로드 dry-run 및 실행 게이트'] },
359
- 'feature-implementation': { displayNameKo: '기능 구현 표준 스킬', description: '새 기능 구현을 시작할 때 사용 — feature-contract 작성, 재사용 우선 검사, 테스트 증거 수집이 필요한 구현 작업일 때. Use when starting to implement a new feature.', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['feature-contracts 작성', '재사용 우선 검사', '테스트 증거 수집', '핸드오프 트리거'] },
352
+ 'feature-implementation': { displayNameKo: '기능 구현 표준 스킬', description: ' 기능 구현을 시작할 때 사용 — feature-contract 작성, 재사용 우선 검사, 테스트 증거 수집이 필요한 구현 작업일 때. Use when starting to implement a new feature.', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'unverified', capabilities: ['feature-contracts 작성', '재사용 우선 검사', '테스트 증거 수집', '핸드오프 트리거'] },
360
353
  // 1.9.11: 기본 내장 — 로드맵 자동 생성 스킬
361
- 'project-roadmap-generator': { displayNameKo: '프로젝트 로드맵 자동 생성 스킬', description: '프로젝트 진행 상황을 로드맵 한 장으로 시각화해 달라고 할 때 사용 — .harness 상태를 SVG 트리 HTML 로 뽑을 때. Use when the user asks for a visual roadmap.', version: '0.2.0', lastUpdated: '2026-05-12', verification: 'passed', capabilities: ['leerness .harness/* 통합 파싱 (plan/progress/skills/rules/decisions/handoff/current-state)', '좌→우 수평 트리 + 상하 중앙정렬 SVG', '7개 상태 색상 (완료/진행/보류/검토/예정/미완료/오류)', 'design-system + CSS variables 자동 주입', '화이트보드 panning/zoom + 더블클릭 reset', '단일 HTML 출력 (외부 의존성 0)'] }
354
+ 'project-roadmap-generator': { displayNameKo: '프로젝트 로드맵 자동 생성 스킬', description: '프로젝트 진행 상황을 로드맵 한 장으로 시각화해 달라고 할 때 사용 — .harness 상태를 SVG 트리 HTML 로 뽑을 때. Use when the user asks for a visual roadmap.', version: '0.2.0', lastUpdated: '2026-05-12', verification: 'unverified', capabilities: ['leerness .harness/* 통합 파싱 (plan/progress/skills/rules/decisions/handoff/current-state)', '좌→우 수평 트리 + 상하 중앙정렬 SVG', '7개 상태 색상 (완료/진행/보류/검토/예정/미완료/오류)', 'design-system + CSS variables 자동 주입', '화이트보드 panning/zoom + 더블클릭 reset', '단일 HTML 출력 (외부 의존성 0)'] }
362
355
  };
363
356
 
364
357
  // 1.9.342 (UR-0025 심층): roadmap.html 상태 라벨/색상 맵 (status → ko 라벨 / hex 색상) — harness 에서 분리.
package/lib/clarify.js CHANGED
@@ -253,11 +253,14 @@ function previewCmd(root, sub, rest, deps = {}) {
253
253
  if (sub === 'approve') {
254
254
  // 1.36.54 (#10): 재승인은 멱등 no-op — 중복 이력 방지
255
255
  if (p.status === 'approved') {
256
+ // 1.36.80 (검수 #8): 멱등 재승인 경로가 정리 훅 앞에서 return 해 임시 워크스페이스가 남았다 — 정리는 항상 수행.
257
+ try { if (typeof deps._onResolved === 'function') deps._onResolved(p.id); } catch {}
256
258
  if (json) { log(JSON.stringify({ ok: true, id: p.id, status: p.status, changed: false }, null, 2)); return; }
257
259
  ok(`${p.id} 이미 승인됨 — 변경 없음`); return;
258
260
  }
259
261
  p.status = 'approved'; (p.history = p.history || []).push({ at: now(), event: 'approved' });
260
262
  if (!_savePreviews(root, list, json)) return;
263
+ try { if (typeof deps._onResolved === 'function') deps._onResolved(p.id); } catch {} // 1.36.80 (UR-0067): 확인 완료 → 임시 워크스페이스 정리
261
264
  if (json) { log(JSON.stringify({ ok: true, id: p.id, status: p.status }, null, 2)); return; }
262
265
  ok(`${p.id} 승인 — 이제 구현을 시작해도 됩니다.`);
263
266
  return;
@@ -268,6 +271,7 @@ function previewCmd(root, sub, rest, deps = {}) {
268
271
  if (!note) { failJson(json, 'note_required', `preview revise ${p.id} --note "<수정 요구 내용>" 필요`); return; }
269
272
  p.status = 'revision-requested'; (p.history = p.history || []).push({ at: now(), event: 'revision-requested', note });
270
273
  if (!_savePreviews(root, list, json)) return;
274
+ try { if (typeof deps._onResolved === 'function') deps._onResolved(p.id); } catch {} // 1.36.80 (UR-0067): 확인 절차 종료 → 임시 워크스페이스 정리
271
275
  if (json) { log(JSON.stringify({ ok: true, id: p.id, status: p.status, note }, null, 2)); return; }
272
276
  ok(`${p.id} 수정요구 기록${note ? ` — ${note}` : ''} — 미리보기를 수정해 다시 제시하세요 (코드 작성은 계속 보류).`);
273
277
  return;
package/lib/mcp-tools.js CHANGED
@@ -7,7 +7,7 @@ module.exports = [
7
7
  { name: 'leerness_handoff', requiredTier: 'safe-write', description: '워크스페이스 컨텍스트(plan/progress/decisions) 적재', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
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: 'safe-write', 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
- { 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'] } },
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' }, refereeId: { type: 'string' } }, required: ['taskId'] } },
11
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' } } } },
12
12
  { name: 'leerness_contract_verify', requiredTier: 'read-only', description: '명세 ↔ 구현 함수/필드 일치 자동 검사', inputSchema: { type: 'object', properties: { spec: { type: 'string' }, impl: { type: 'string' } }, required: ['spec', 'impl'] } },
13
13
  { name: 'leerness_agents_list', requiredTier: 'read-only', description: '외부 AI CLI 가용성 표 (claude/codex/agy/copilot 상태 + 환경변수 활성화 여부)', inputSchema: { type: 'object', properties: {} } },
@@ -92,7 +92,9 @@ module.exports = [
92
92
  { name: 'leerness_honesty_check', requiredTier: 'read-only', description: '1.9.305 (사용자 명시) — AI 인식론적 정직성 점검. 주장/evidence 텍스트가 (1) 모르는 걸 아는 척(근거 없는 단정), (2) 검증 없는 섣부른 판단(추정+완료 결론), (3) 외부 정보 미수집(API/버전/스펙 언급+수집흔적 없음) 중 무엇에 해당하는지 휴리스틱 탐지. read-only. 에이전트가 자기 주장을 단언 전 self-check. 인자: { path?, taskId?, text? }. 응답: { ok, findings[], dimensions[], highCount }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, taskId: { type: 'string' }, text: { type: 'string' } } } },
93
93
  { name: 'leerness_brief', requiredTier: 'read-only', description: '1.9.308 (UR-0055) — 프로젝트 청사진 회수. 프로젝트 개요/소개/목적/기능/스택/아키텍처/성공기준/방향이력을 구조화 반환(show) 또는 복사용 blueprint 텍스트(export=true). read-only. 외부 에이전트가 "이 프로젝트가 무엇이고 어디로 가는가"를 1콜로 파악하거나, 신규 프로젝트 계획으로 복사. 인자: { path?, export? }. 응답(show): { project, intro, purpose, features[], stack[], directionHistory[], ... }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, export: { type: 'boolean' } } } },
94
94
  { name: 'leerness_clarify', requiredTier: 'read-only', description: '1.36.77 (UR-0061) — 사용자 요청 텍스트의 판단-모호 신호 감지 → 사용자에게 그대로 물을 질문 목록 JSON ({ ambiguous, signals[], questions[] }). 추측 구현 방지 — 모호하면 질문 먼저.', inputSchema: { type: 'object', properties: { text: { type: 'string' }, path: { type: 'string' } }, required: ['text'] } },
95
- { name: 'leerness_preview', requiredTier: 'safe-write', description: '1.36.77 (UR-0061/0066) — 신규 기능/디자인 미리보기 승인 워크플로. action: add(title/design/features/mockup)·list·show·approve·revise(note 필수)·mockup(HTML 시안 스캐폴드 생성, force 로 재생성). 계약: approve 전 해당 기능 코드 작성 금지. list/show 는 읽기지만 단일 도구라 safe-write 로 보수 선언.', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['add', 'list', 'show', 'approve', 'revise', 'mockup'] }, path: { type: 'string' }, title: { type: 'string' }, design: { type: 'string' }, features: { type: 'string' }, mockup: { type: 'string' }, id: { type: 'string' }, note: { type: 'string' }, force: { type: 'boolean' } }, required: ['action'] } },];
95
+ { name: 'leerness_preview', requiredTier: 'safe-write', description: '1.36.77 (UR-0061/0066) — 신규 기능/디자인 미리보기 승인 워크플로. action: add(title/design/features/mockup)·list·show·approve·revise(note 필수)·mockup(HTML 시안 스캐폴드 생성, force 로 재생성). 계약: approve 전 해당 기능 코드 작성 금지. list/show 는 읽기지만 단일 도구라 safe-write 로 보수 선언.', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['add', 'list', 'show', 'approve', 'revise', 'mockup'] }, path: { type: 'string' }, title: { type: 'string' }, design: { type: 'string' }, features: { type: 'string' }, mockup: { type: 'string' }, id: { type: 'string' }, note: { type: 'string' }, force: { type: 'boolean' } }, required: ['action'] } },
96
+ { name: 'leerness_referee', requiredTier: 'safe-write', description: '1.36.80 (P-0001) — 검증기 캘리브레이션: 검증기를 신뢰하기 전에 탐지력을 실행으로 증명(known-good 통과 + 일부러 망가뜨린 known-bad 를 기대 사유로 거부). action: add(id/check/good/bad/expectBad)·list·show·verify·drop. 명령/기대치가 바뀌면 캘리브레이션이 stale 이 되어 신뢰가 철회된다. verify-claim 의 refereeId 로 게이팅.', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['add', 'list', 'show', 'verify', 'drop'] }, path: { type: 'string' }, id: { type: 'string' }, check: { type: 'string' }, good: { type: 'string' }, bad: { type: 'string' }, expectBad: { type: 'string' } }, required: ['action'] } },
97
+ ];
96
98
 
97
99
  // 1.36.58 (외부 GPT 감사 F-09): core 프로필 — 세션 시작 토큰/도구 선택 비용을 줄이는 핵심 20종.
98
100
  // `leerness mcp serve --profile core` (또는 LEERNESS_MCP_PROFILE=core) 에서 tools/list·tools/call 이 이 집합만 노출.