leerness 1.36.79 → 1.36.81
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 +38 -0
- package/README.ko.md +1 -1
- package/README.md +6 -6
- package/bin/leerness.js +77 -7
- package/lib/catalogs.js +2 -9
- package/lib/clarify.js +4 -0
- package/lib/mcp-tools.js +4 -2
- package/lib/preview-serve.js +254 -0
- package/lib/pure-utils.js +11 -2
- package/lib/referee.js +264 -0
- package/lib/review-request.js +43 -1
- package/package.json +1 -1
- package/scripts/e2e.js +151 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.81 — 2026-07-28 — 게시본 클린룸 실측이 잡은 2건 (referee 상태 신호 정확화 · 출하 소스 생 NUL 제거)
|
|
4
|
+
|
|
5
|
+
1.36.80 을 게시한 뒤 **게시된 tarball 로** 신규 기능을 실행해 본 결과 2건이 나왔다. 둘 다 게이트(selftest 334 · e2e 404)는 초록이었다 — 단언이 없던 자리다.
|
|
6
|
+
|
|
7
|
+
- **referee 상태 신호가 부정확했다** — `isStale()` 이 "캘리브레이션이 아예 없음"까지 stale 로 묶어, **한 번도 검증한 적 없는** 검증기가 `verify-claim` 게이트에서 `referee_stale` 로 보고됐다. 두 상태는 사용자가 취할 조치가 다르다(캘리브레이션 실행 vs 재검증). 정직한 신호가 존재 이유인 기능에서 신호 자체가 틀린 셈이라 고쳤다: stale 은 **"한때 증명됐으나 정의가 바뀌어 무효"** 만을 뜻하고, 미증명은 `referee_uncalibrated`, 캘리브레이션 실패도 `referee_uncalibrated`(사유 포함). fail-closed 동작은 이전과 동일 — 어느 상태든 통과시키지 않는다. e2e 가 미존재/미증명/무효/검증기실패 **4구분**을 고정한다.
|
|
8
|
+
- **출하 소스에 생 NUL 바이트** — 지문·ID 구분자를 소스에 raw NUL 로 써서 git/grep 이 `bin/leerness.js` 와 `lib/referee.js` 를 **바이너리로 취급**했다(diff 가 `Bin 16408 -> 17121 bytes` 로만 표시, 코드 검색 차단). 더 위험한 쪽은 정규화 도구가 이 바이트를 조용히 지우면 해시 입력이 바뀌어 **기존 프로젝트의 캘리브레이션 지문과 api-skill ID 가 한꺼번에 어긋난다**는 점이다. 런타임 값이 동일한 유니코드 이스케이프 표기로 치환(기존 지문/ID 불변 — 실측 확인) + selftest 메타가드로 bin/lib 전수 0 을 상시 강제.
|
|
9
|
+
**검수 23회전이 잡은 4건** (전건 직접 재현 확인):
|
|
10
|
+
|
|
11
|
+
- **High — fail-closed 기능의 fail-open**: `calibration.ok` 가 `"false"`(truthy 문자열)나 `1` 로 손상/조작되면 `!r.calibration.ok` 가 false 가 되어 **미증명 검증기가 게이트를 그대로 통과**했고 `referee show` 는 `calibrated: true` 로 표시했다. 이 기능의 전제 자체를 뚫는 구멍이다. `_entryValid` 가 `ok`(boolean)·`fingerprint`(비어있지 않은 문자열)를 형상 검증해 거부하고(스토어 전체 fail-closed — previews/teams 와 동일 규율), 판정 지점도 `=== true` 엄격 비교로 이중 방어. 형상 무효는 "없음" 대신 `referee_store_invalid` 로 정확히 보고한다.
|
|
12
|
+
- **캘리브레이션 실패 + 정의 변경을 `referee_stale` 로 오분류** — 증명된 적이 없는데 "있던 증명이 무효화됐다"는 거짓 서사였다. 실패 상태는 정의가 바뀌어도 `referee_uncalibrated`.
|
|
13
|
+
- **메타가드 스캔 범위가 이름보다 좁음** — `scripts/` 도 `package.json` `files` 에 포함돼 npm 에 함께 실리는데 스캔하지 않았다. bin/lib/scripts 전수로 확장.
|
|
14
|
+
- **공허한 e2e 단언 2건** — "FP 프로브 3종 거부" 중 둘은 `--expect-bad` 없이 `add` 해서 **add 자체가 `missing_args` 로 실패**했고, 이어진 `verify` 가 돌려준 `not_found`(ok:false)를 e2e 가 "거부됨"으로 통과 처리했다. 탐지력 없는 검증기를 거부한다는 걸 전혀 증명하지 못했다. add 성공을 먼저 단언하고 거부 **사유**까지 확인하도록 교체.
|
|
15
|
+
|
|
16
|
+
- 검증: selftest 335 · e2e 전건 · 게시본 실측 프로브(게이트 4구분 · 지문 동치 · 임시 워크스페이스 정리 · 포트 5337) · 검수 지적 4건 수정 전/후 실측 대조.
|
|
17
|
+
|
|
18
|
+
## 1.36.80 — 2026-07-28 — referee 캘리브레이션 · 라이브 미리보기 · 기본 스킬 정리 · 최소성 사다리 — MCP 89 도구 · R-0001 검수 22회전
|
|
19
|
+
|
|
20
|
+
**이 릴리스에 함께 들어간 것** (아래 상세):
|
|
21
|
+
- **라이브 미리보기(사용자 요청)**: `preview serve <P-ID>` 가 임시 워크스페이스 + **0-deps 로컬 서버**로 시안을 띄우고 승인/수정 시 정리(project 모드 배치본 포함). **시그니처 포트 5337**(키패드 "LEER", ollama:11434 방식) + 점유 시 순차 회피 → OS 빈 포트. `preview mode self|project` — A 기본·B 옵트인, 미설정 시 사용자에게 질문하되 흐름은 막지 않음. leerness 는 사용자의 dev 서버를 임의 실행하지 않는다.
|
|
22
|
+
- **기본 제공 스킬 정리(사용자 지시)**: 도메인 7종(commerce-api/office/crawling/firebase/ads-analytics/appstore-review/ai-verified-skill-publisher) 내장 제거 — **설명 4줄짜리 메타데이터**인데 `verification:'passed'` 를 근거 없이 주장했고 특정 사용자 도메인이라 전역 기본값으로 부적절했다. 남은 2종의 verification 은 **`'unverified'`** 로 정직화(selftest 가 'passed' 주장 금지를 영구 단언). **기존 설치분은 파일·락 모두 보존**(비파괴) · 제거된 id 명시 요청 시 조용히 무시하지 않고 고지.
|
|
23
|
+
- **`review-request --ladder` (P-0003, codex 공동판단 ADOPT)**: 순서 있는 최소성 결정 영수증(필요한가→기존코드→stdlib→네이티브→설치된 의존성→조합→최소구현). 옵트인·자문 전용이라 `proceed` 무영향, 근거 없으면 `unknown`, "줄이면 안 되는 것"(검증·보안·접근성·에러처리·데이터손실) 명시. **기각**: 룰 거버넌스/campaign 스냅샷(P-0002 — campaign 개념 부재, 제안/승인 분리는 이미 기본 모델, 모순탐지는 false-BLOCK 위험).
|
|
24
|
+
- **검수 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 ⑩ 제거 스킬 명시 요청 조용히 무시 ⑪ "아무것도 저장 안 함" 과장 → 주장 범위 정정 ⑫ 실패 시 빈 임시 디렉토리 잔존.
|
|
25
|
+
- 검증: 검수 지적 9종 프로브 재현→수정→재확인 실측, selftest 334, e2e 404/404.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### (원안) 검증기 캘리브레이션 `referee` — P-0001, 외부 사례 검토 채택
|
|
30
|
+
|
|
31
|
+
사용자가 지정한 두 외부 저장소(ponytail · anthropics/code-migration-kit)를 codex 독립 검토 + 4렌즈 병렬 분석으로 평가 → **만장일치 1순위 한 건만** 채택. leerness 정체성("done 은 증거를 요구한다")의 미해결 형제 명제 — **검증기 자신의 증거** — 를 채운다.
|
|
32
|
+
|
|
33
|
+
- **문제**: leerness 의 모든 검사(verify-claim/gate/contract)는 정적·텍스트 대리 지표였다. `lens` 가 "이 테스트가 실제로 실패할 수 있나?"를 *묻지만*, 실행으로 탐지력을 증명하는 장치는 없었다 — 아무것도 검사하지 않는 초록 스위트를 걸러낼 수 없었다.
|
|
34
|
+
- **`leerness referee add|verify|list|show|drop`**: 검증기를 신뢰하기 전에 ① known-good 통과 ② 일부러 망가뜨린 known-bad 거부 ③ **거부 사유가 심어둔 결함과 일치**(`--expect-bad`) ④ 명령/기대치 변경 시 지문 불일치로 **자동 stale**(신뢰 철회). 무관한 실패(환경 오류·실행 불가·타임아웃)는 탐지력으로 인정하지 않는다.
|
|
35
|
+
- **게이팅**: `verify-claim <T-ID> --referee <id>` — 미등록/미검증/stale 검증기는 실행 전에 거부(`referee_not_found`/`referee_stale`), 캘리브레이션된 검증기가 거부하면 `referee_failed`. MCP `leerness_referee`(safe-write) + `leerness_verify_claim` 의 `refereeId`.
|
|
36
|
+
- 적대적 FP 프로브 9종 실측 통과: 기준선 빨간 상태·bad 통과·사유 불일치·실행 불가·명령 변경 stale·미검증 사용·검증기 실패·없는 id·손상 스토어 fail-closed.
|
|
37
|
+
- **정직성**: ponytail 의 "코드 54%↓·비용 20%↓" 등 벤치 수치는 **인용하지 않는다**(모델 1개·저장소 1계열 기반이고 ponytail 자신도 해당 수치가 낡았다고 명시). leerness 가 효율을 주장하려면 품질을 고정한 on/off 대조 실험이 필요하다 — 이번 릴리스는 그런 주장을 하지 않는다.
|
|
38
|
+
- 기각(양 검토 합의): ponytail 명령군·intensity 모드(lens 토글 중복) · LOC 를 게이트로 · 마이그레이션 러너/빌드 데몬(정체성 변질) · **파일 존재=완료**(핵심 불변식 위반) · 실패 후 자동 룰 완화. 룰 스냅샷(P-0002)·최소성 사다리(P-0003)는 승인 대기.
|
|
39
|
+
- 검증: selftest 334(문서 drift 가드가 88→89 불일치를 잡아 README 정합), e2e +1.
|
|
40
|
+
|
|
3
41
|
## 1.36.79 — 2026-07-28 — 실 프로젝트 60개 도그푸딩: P1 5건 (합성 헌트 10회가 못 본 결함)
|
|
4
42
|
|
|
5
43
|
사용자의 **실제 프로젝트 60개**(v0.1.40 ~ 1.36.77)를 5개 렌즈로 병렬 감사(읽기 전용 + temp 복사본 실험). 합성 픽스처 기반 codex 헌트 10라운드가 한 번도 못 본 P1 5건이 나왔다 — 실데이터에서만 발화하는 조건들이었다.
|
package/README.ko.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
> **어떤 언어, 어떤 AI 에이전트로 작업하든 — "증거 없이는 끝났다고 말할 수 없게" 만드는 AI 코딩 운영 레이어.** 코드를 대신 쓰는 도구가 아니라, AI 에이전트의 **기억·인수인계·검증·감사·보안 가드**를 프로젝트에 영속화하는 CLI + MCP 서버입니다.
|
|
15
15
|
|
|
16
|
-
[](https://www.npmjs.com/package/leerness) · ](https://www.npmjs.com/package/leerness) ·  · **런타임 의존성 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
|
-
[](https://www.npmjs.com/package/leerness) · ](https://www.npmjs.com/package/leerness) ·  · **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.
|
|
125
|
+
이 프로젝트는 Leerness v1.36.81 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
126
126
|
|
|
127
127
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
128
128
|
|
|
@@ -176,7 +176,7 @@ leerness memory restore decision <date|title>
|
|
|
176
176
|
|
|
177
177
|
### MCP server (외부 AI 통합)
|
|
178
178
|
|
|
179
|
-
Leerness v1.36.
|
|
179
|
+
Leerness v1.36.81는 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.79는 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 응답:
|
|
192
|
+
// tools/list 응답: 89 도구
|
|
193
193
|
```
|
|
194
194
|
|
|
195
195
|
### Autonomous mode (자율 모드)
|
|
@@ -197,7 +197,7 @@ Leerness v1.36.79는 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.
|
|
200
|
+
현재 누적: **v1.9.x → 1.36.81 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
|
|
201
201
|
|
|
202
202
|
### 성능 가이드
|
|
203
203
|
|
|
@@ -235,5 +235,5 @@ leerness release pack --close --auto-main-push
|
|
|
235
235
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
236
236
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
237
237
|
|
|
238
|
-
Last synced by Leerness v1.36.
|
|
238
|
+
Last synced by Leerness v1.36.81: 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.
|
|
37
|
+
const VERSION = '1.36.81';
|
|
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()
|
|
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 ===
|
|
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; } },
|
|
@@ -4923,6 +4934,19 @@ function _selfTestCases() {
|
|
|
4923
4934
|
while ((m = re.exec(self)) !== null) { if (m[1].includes(NL)) offenders++; }
|
|
4924
4935
|
return offenders === 0;
|
|
4925
4936
|
} },
|
|
4937
|
+
{ name: '출하 소스 생 NUL 금지 (1.36.81, 클린룸 실측): bin/lib 전수 0 — 구분자를 이스케이프로 (메타가드)', run: () => {
|
|
4938
|
+
// 1.36.80 사고: 지문/ID 구분자를 소스에 **생 NUL 바이트**로 써서 도구가 파일을 바이너리로 취급했다(grep 차단).
|
|
4939
|
+
// 정규화 도구가 조용히 지우면 해시 입력이 바뀌어 전 프로젝트의 캘리브레이션/ID 가 한꺼번에 어긋난다.
|
|
4940
|
+
// 런타임 값이 동일한 유니코드 이스케이프 표기로만 쓴다 — 이 가드 자신은 이스케이프 표기라 자기참조 안전.
|
|
4941
|
+
// (codex 검수 ③) scripts/ 도 package.json files 에 포함돼 npm 으로 함께 실린다 — "출하 소스"라면 같이 봐야 한다.
|
|
4942
|
+
const NUL = String.fromCharCode(0);
|
|
4943
|
+
const files = [__filename];
|
|
4944
|
+
for (const sub of ['lib', 'scripts']) {
|
|
4945
|
+
const dir = path.join(path.dirname(__filename), '..', sub);
|
|
4946
|
+
if (exists(dir)) for (const f of fs.readdirSync(dir)) if (f.endsWith('.js')) files.push(path.join(dir, f));
|
|
4947
|
+
}
|
|
4948
|
+
return files.every(f => !read(f).includes(NUL));
|
|
4949
|
+
} },
|
|
4926
4950
|
{ name: 'CLI 영어화 Phase 1 (1.20.2, UR-0010): _uiLang 해석(flag>env>manifest>ko) + 첫화면 _t 적용 (행위+소스)', run: () => {
|
|
4927
4951
|
const save = process.argv; const saveEnv = process.env.LEERNESS_LANG;
|
|
4928
4952
|
try {
|
|
@@ -6028,7 +6052,7 @@ function _apiSkillsDir(root) {
|
|
|
6028
6052
|
// 정체성(같은 URL·다른 name 이 같은 id 로 붕괴하던 것), Windows 예약 이름(con/prn/aux/nul/com1..9/lpt1..9) 회피.
|
|
6029
6053
|
// 충돌 접미사(-hash) 추가 후에도 이 함수를 다시 거쳐 상한이 유지된다.
|
|
6030
6054
|
function _apiSkillIdCap(id, url, name) {
|
|
6031
|
-
const seed = String(url || '') + '
|
|
6055
|
+
const seed = String(url || '') + '\u0000' + String(name || '');
|
|
6032
6056
|
if (!id || id.length > 80) id = (id || 'api').slice(0, 72).replace(/-+$/, '') + '-' + _shortHash(seed);
|
|
6033
6057
|
if (/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(id)) id = id + '-' + _shortHash(seed);
|
|
6034
6058
|
return id;
|
|
@@ -6799,6 +6823,7 @@ function commandsCmd(root) {
|
|
|
6799
6823
|
{ cmd: 'clarify "<사용자 요청>" [--json]', desc: '요청 모호성 신호 감지 → 사용자에게 물을 질문 생성 (추측 구현 방지) — 1.36.51 UR-0061' },
|
|
6800
6824
|
{ cmd: 'tech [--json]', desc: '기술 프로필 — 개발 언어·연결 서비스 자동 감지 + 마이그레이션/언어전환 이력, 그래프 🛠 탭 표시 — 1.36.53 UR-0062' },
|
|
6801
6825
|
{ cmd: 'integrity check [--repair] [--json]', desc: 'managed 정책-문서 12종 무결성(부재/H1 상실/절단) 점검 — --repair: archive 대피 후 템플릿 재생성 — 1.36.57 감사 F-04' },
|
|
6826
|
+
{ cmd: 'referee add|verify|list|show|drop', desc: '검증기 캘리브레이션 — 신뢰 전에 탐지력 증명(known-good 통과 + 망가뜨린 known-bad 를 기대 사유로 거부). verify-claim --referee / gate 게이팅 — 1.36.80 P-0001' },
|
|
6802
6827
|
{ 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' },
|
|
6803
6828
|
{ cmd: 'review <file> --persona <ids>', desc: '페르소나 리뷰 (1.9.29)' },
|
|
6804
6829
|
{ cmd: 'brainstorm "<topic>" [--include-code]', desc: '워크스페이스 회수 + 코드 grep' }
|
|
@@ -9215,6 +9240,9 @@ function _saveTeams(root, teams) {
|
|
|
9215
9240
|
const _team = require('../lib/team');
|
|
9216
9241
|
const _tgl = require('../lib/toggles'); // 1.36.30: 기능 토글 (그래프 ⚙ 탭 연동 — gate/lens/auto-graph/delegation-brief)
|
|
9217
9242
|
const _clar = require('../lib/clarify'); // 1.36.51 (UR-0061): 모호성 질문 + 미리보기 승인 워크플로
|
|
9243
|
+
const _ref = require('../lib/referee'); // 1.36.80 (P-0001): 검증기 캘리브레이션 — 검증기 자신의 탐지력을 실행으로 증명
|
|
9244
|
+
const _pvsrv = require('../lib/preview-serve'); // 1.36.80 (UR-0067): 라이브 미리보기 — 임시 워크스페이스 + 로컬 서버 + 확인 후 정리
|
|
9245
|
+
function refereeCmd(root, sub, rest) { return _ref.refereeCmd(root, sub, rest, { has, arg, VERSION, _withLock }); }
|
|
9218
9246
|
const _tech = require('../lib/tech-profile'); // 1.36.53 (UR-0062): 기술 프로필 (언어·서비스 감지 + 변경 이력)
|
|
9219
9247
|
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
|
|
9220
9248
|
|
|
@@ -12926,6 +12954,21 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
12926
12954
|
const rows = readProgressRows(root);
|
|
12927
12955
|
const row = rows.find(r => r.id === taskId);
|
|
12928
12956
|
if (!row) { if (opts.collect) return { id: taskId, ok: false, reasons: ['not-found'] }; return failJson(_j, 'not_found', `progress-tracker.md에 ${taskId} 없음.`); }
|
|
12957
|
+
// 1.36.80 (P-0001): --referee <id> — 캘리브레이션된 검증기만 신뢰한다. 미검증/무효(stale) 검증기의 통과는 증거가 아니므로
|
|
12958
|
+
// 실행 전에 거부한다(검증기 자신의 탐지력이 증명되지 않은 상태에서 나온 초록은 정보가 없다).
|
|
12959
|
+
const _refId = opts.refereeId || arg('--referee', null);
|
|
12960
|
+
let _refereeResult = null;
|
|
12961
|
+
if (_refId && _refId !== true) {
|
|
12962
|
+
const _g = _ref.requireReferee(root, String(_refId));
|
|
12963
|
+
if (!_g.ok) { if (opts.collect) return { id: taskId, ok: false, reasons: [_g.code] }; return failJson(_j, _g.code, _g.message); }
|
|
12964
|
+
const _rr = cp.spawnSync(_g.referee.check, { cwd: root, shell: true, encoding: 'utf8', timeout: 600000, maxBuffer: 8 * 1024 * 1024 });
|
|
12965
|
+
_refereeResult = { id: _g.referee.id, check: _g.referee.check, exit: (_rr.status == null ? 1 : _rr.status), calibratedAt: _g.referee.calibration.at };
|
|
12966
|
+
if (_refereeResult.exit !== 0) {
|
|
12967
|
+
const _msg = `referee ${_g.referee.id} 검증 실패(exit ${_refereeResult.exit}) — 캘리브레이션된 검증기가 이 상태를 거부했습니다: ${_g.referee.check}`;
|
|
12968
|
+
if (opts.collect) return { id: taskId, ok: false, reasons: ['referee_failed'], referee: _refereeResult };
|
|
12969
|
+
return failJson(_j, 'referee_failed', _msg);
|
|
12970
|
+
}
|
|
12971
|
+
}
|
|
12929
12972
|
|
|
12930
12973
|
const evidence = row.evidence || '';
|
|
12931
12974
|
// 1.9.20: 파일 경로 추출 — 도메인 폴더 자동 인식 + 루트 메타파일
|
|
@@ -18898,7 +18941,7 @@ function _mcpToCliArgs(name, args, targetPath) {
|
|
|
18898
18941
|
case 'leerness_handoff': cliArgs = ['handoff', targetPath, '--compact', '--no-drift-check']; break;
|
|
18899
18942
|
case 'leerness_drift_check': cliArgs = ['drift', 'check', targetPath, '--json']; break;
|
|
18900
18943
|
case 'leerness_audit': cliArgs = ['audit', targetPath, '--json', ...(args.fix ? ['--fix'] : []), ...(args.strict ? ['--strict'] : [])]; break;
|
|
18901
|
-
case 'leerness_verify_claim': cliArgs = ['verify-claim', args.taskId, '--path', targetPath, ...(args.runTests ? ['--run-tests'] : []), ...(args.strictClaims ? ['--strict-claims'] : []), ...(args.lenient ? ['--lenient'] : [])]; break;
|
|
18944
|
+
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;
|
|
18902
18945
|
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 없음)
|
|
18903
18946
|
case 'leerness_contract_verify': cliArgs = ['contract', 'verify', args.spec, args.impl]; break;
|
|
18904
18947
|
case 'leerness_agents_list': cliArgs = ['agents', 'list', '--json']; break;
|
|
@@ -19105,6 +19148,20 @@ function _mcpToCliArgs(name, args, targetPath) {
|
|
|
19105
19148
|
case 'leerness_clarify':
|
|
19106
19149
|
cliArgs = ['clarify', String(args.text || ''), '--path', targetPath, '--json'];
|
|
19107
19150
|
break;
|
|
19151
|
+
case 'leerness_referee': { // 1.36.80 (P-0001)
|
|
19152
|
+
const _ra = String(args.action || 'list');
|
|
19153
|
+
cliArgs = ['referee', _ra, '--path', targetPath, '--json'];
|
|
19154
|
+
if (_ra === 'add') {
|
|
19155
|
+
cliArgs.splice(2, 0, String(args.id || ''));
|
|
19156
|
+
if (args.check) cliArgs.push('--check', String(args.check));
|
|
19157
|
+
if (args.good) cliArgs.push('--good', String(args.good));
|
|
19158
|
+
if (args.bad) cliArgs.push('--bad', String(args.bad));
|
|
19159
|
+
if (args.expectBad) cliArgs.push('--expect-bad', String(args.expectBad));
|
|
19160
|
+
} else if (['show', 'verify', 'drop'].includes(_ra)) {
|
|
19161
|
+
cliArgs.splice(2, 0, String(args.id || ''));
|
|
19162
|
+
}
|
|
19163
|
+
break;
|
|
19164
|
+
}
|
|
19108
19165
|
case 'leerness_preview': {
|
|
19109
19166
|
const _pa = String(args.action || 'list');
|
|
19110
19167
|
cliArgs = ['preview', _pa, '--path', targetPath, '--json'];
|
|
@@ -23437,7 +23494,20 @@ async function main() {
|
|
|
23437
23494
|
if (_pvRaw[i].startsWith('--')) { if (['--design', '--features', '--note', '--path', '--mockup'].includes(_pvRaw[i]) && _pvRaw[i + 1]) i++; continue; } // 1.36.75 (검수 #2): --mockup 값이 제목에 흡수되던 것
|
|
23438
23495
|
_pvToks.push(_pvRaw[i]);
|
|
23439
23496
|
}
|
|
23440
|
-
|
|
23497
|
+
// 1.36.80 (UR-0067): 라이브 미리보기 — serve(로컬 서버) / mode(self|project) 는 별도 모듈로 위임
|
|
23498
|
+
const _pvRoot = arg('--path', process.cwd());
|
|
23499
|
+
if (_pvToks[0] === 'serve') return _pvsrv.previewServeCmd(_pvRoot, _pvToks[1], { has, arg, previews: _clar._loadPreviews(_pvRoot) });
|
|
23500
|
+
if (_pvToks[0] === 'mode') return _pvsrv.previewModeCmd(_pvRoot, _pvToks[1], { has, arg });
|
|
23501
|
+
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: 승인/수정 시 임시 워크스페이스 정리
|
|
23502
|
+
}
|
|
23503
|
+
// 1.36.80 (P-0001): referee — 값 플래그가 positional 로 새지 않게 원시 argv 파싱(preview 와 동일 패턴)
|
|
23504
|
+
if (cmd === 'referee') {
|
|
23505
|
+
const _rvRaw = process.argv.slice(2); const _rvI = _rvRaw.indexOf('referee'); const _rvToks = [];
|
|
23506
|
+
for (let i = _rvI + 1; i < _rvRaw.length && _rvI >= 0; i++) {
|
|
23507
|
+
if (_rvRaw[i].startsWith('--')) { if (['--check', '--good', '--bad', '--expect-bad', '--path', '--timeout'].includes(_rvRaw[i]) && _rvRaw[i + 1]) i++; continue; }
|
|
23508
|
+
_rvToks.push(_rvRaw[i]);
|
|
23509
|
+
}
|
|
23510
|
+
return refereeCmd(arg('--path', process.cwd()), _rvToks[0], _rvToks.slice(1));
|
|
23441
23511
|
}
|
|
23442
23512
|
if (cmd === 'lens') return lensCmd(args[1]); // 1.18.3 (UR-0003): 분야별 자기질문 품질 렌즈
|
|
23443
23513
|
// 1.9.233: leerness commands — 카테고리화된 전체 CLI 명령 목록
|
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
|
-
'
|
|
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: '
|
|
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 이 이 집합만 노출.
|