leerness 1.36.24 → 1.36.26

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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.26 — 2026-07-15 — `leerness skill lint` — SKILL 메타 품질 lint (2티어 severity · 한국어 트리거절 · CJK 본문예산) (obra/superpowers P1/P2)
4
+
5
+ 1.36.25(P0 — description 데이터)로 선행조건이 충족된 후속. 판별 증거: **P0 없이 lint 를 먼저 붙였으면 내장 9/9 가 실패**했을 것 — 지금은 내장 9종이 lint clean(순서가 맞았다는 실증, selftest 로 고정).
6
+
7
+ - **`_lintSkillMeta(meta, body)` 순수함수** (pure-utils, 의존 0) — 2티어 severity 설계(메모리 교훈: 게이트 휴리스틱은 false-PASS 편향, false-BLOCK 금지):
8
+ - **ERROR**(기계적, FP 0 — 이것만 exit 1): name 부재/문자셋 위반(`^[a-z0-9][a-z0-9-]*$`), description 부재/1024자 초과.
9
+ - **WARNING**(저FP 형태): name 반복 / 40자 미만(표시명 합성 흔적) / capabilities 나열 형태. 기본 exit 0, `--strict` 시 승격.
10
+ - **INFO**(주관적, 절대 실패 아님): 트리거절 부재, 본문 예산 초과.
11
+ - **한국어 적응**: 트리거절 ko(`~할 때 사용`)·en(`Use when`) 모두 인정, 3인칭 검사는 한국어(인칭 무표지)에 부적용이라 제외, **본문 예산 CJK 분기**(비CJK=단어수/CJK=char — 공백분리 단어수를 한국어에 쓰면 어절 카운트라 전면 오발).
12
+ - **`leerness skill lint [id] [--all] [--json] [--strict]`** — 설치 스킬(SKILL.md 우선, skill.json 폴백) + 내장 카탈로그 9종 대상. `--json` 구조화(ok/errors/warnings/results).
13
+ - **검증**: selftest 296/296(ERROR 기계판정 2종 + clean 무오발 + WARNING 분리 + CJK lang + **내장 9종 clean 고정** + CLI 배선), 실측 5종(내장 --all exit 0 / description 없음 ERROR exit 1 / WARNING 기본 0·strict 1 / ko 트리거 무 INFO / --json), 게이트 e2e, 게시본 클린룸.
14
+
15
+ ## 1.36.25 — 2026-07-15 — 내장 스킬 9종 트리거 description + export/publish 합성 우선순위 — "언제 로드할지"를 알 수 있게 (obra/superpowers P0)
16
+
17
+ superpowers 검토(1.36.22)에서 이연한 P0. **결함(도그푸딩 실측)**: BUILTIN_CATALOG 9개 스킬 전부 `description` 0/9 → (1) 설치 스킬 리더가 capabilities 나열로 폴백("feature-contracts 작성, 재사용 우선 검사…" = 에이전트가 "지금 로드할까"를 판단할 수 없는 워크플로 요약), (2) `skill export`/`skill publish` 가 description 을 displayNameKo("기능 구현 표준 스킬")로 합성 — **좋은 description 이 있어도 우선순위가 displayNameKo 1순위라 무시**되는 구조였다.
18
+
19
+ - **BUILTIN_CATALOG 9/9 에 트리거 서술 description 신설** — superpowers 규율("description 은 트리거 조건 전용 — 워크플로 요약 금지") 적용: ko 트리거("~할 때 사용") + en 트리거절("Use when …"), 전부 40~200자(합성 시 `.slice(0,200)` 생존).
20
+ - **합성 우선순위 역전 3사이트** — `data.displayNameKo || data.description` → `data.description || data.displayNameKo` (export 1 + publish 계열 2). 실측: `skill export feature-implementation` 산출 SKILL.md frontmatter 가 "기능 구현 표준 스킬"(구) → "새 기능 구현을 시작할 때 사용 — … Use when starting to implement a new feature."(신). `skill list` 표시(displayNameKo)는 무변경.
21
+ - **검증**: selftest 295/295(9/9 존재 + 트리거 형태 + 슬라이스 생존 + displayNameKo 반복 아님 + 우선순위 역전 3사이트 — 자기참조 트랩은 split-literal 로 회피, 3번째 재발), export 실측, 게이트 e2e, 게시본 클린룸.
22
+ - 이연: `_lintSkillMeta` 순수 lint + `skill lint` CLI(P1/P2) — description 데이터가 먼저 채워져야 lint 가 의미 있음(이번에 충족). CJK 분기 본문 예산 포함해 다음 라운드 후보.
23
+
24
+ **하네스 문서 버전표기 제거 (사용자 지시, 동일 릴리스 합류)**: 설치되는 하네스 문서 템플릿(AGENTS/CLAUDE/session-workflow 등)의 지시문마다 붙어 있던 내부 버전표기(`(1.9.39+)`, `(1.9.8)`, `- 1.9.56+ …` 불릿 접두, `# 1.9.59+ —` 명령주석 등 **79건**)를 제거 — 사용자에게 무의미한 내부 이력 노이즈. 정보가 있는 괄호는 내용만 보존(`(1.9.78: 5신호+보안)`→`(5신호+보안)`), 사실 기록(migration-report `Version:`, task-log "vX initialized", 라운드 범위 데이터)은 유지. 저장소 자체 CLAUDE.md/AGENTS.md 지시 라인도 동일 정리(이력 섹션 `### 1.9.207 —` 은 역사 기록이라 불변). **실증**: fresh `init` 산출물 전수 스캔 — 지시문 버전표기 잔존 0(사실 기록 3건만).
25
+
3
26
  ## 1.36.24 — 2026-07-14 — requests 계열 positional 경로 인식 — 다른 프로젝트에 조용히 오기록되던 갭 (UR-0027 확장, 실피해 재현)
4
27
 
5
28
  - **결함(실피해 재현)**: `requests` 계열만 positional 경로를 지원하지 않아 `requests add "text" C:/other/proj` 가 **경로 인자를 조용히 무시하고 cwd 프로젝트에 기록** 후 성공을 보고했다. `task`/`rule` 은 `_taskPositionalPath` 로 지원 → 표면 간 불일치가 실수를 유도(1.36.21 라운드에서 본인이 직접 당해 leerness-pkg 에 UR-0060 오기록·정리한 건).
package/README.md CHANGED
@@ -121,7 +121,7 @@ MIT
121
121
  <!-- leerness:project-readme:start -->
122
122
  ## Leerness Project Harness
123
123
 
124
- 이 프로젝트는 Leerness v1.36.24 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
124
+ 이 프로젝트는 Leerness v1.36.26 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
125
 
126
126
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
127
127
 
@@ -175,7 +175,7 @@ leerness memory restore decision <date|title>
175
175
 
176
176
  ### MCP server (외부 AI 통합)
177
177
 
178
- Leerness v1.36.24는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
178
+ Leerness v1.36.26는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
179
 
180
180
  ```jsonc
181
181
  // 카테고리별
@@ -194,18 +194,18 @@ Leerness v1.36.24는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
194
194
  ### Autonomous mode (자율 모드)
195
195
 
196
196
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
197
- 1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
197
+ 1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push → 7) session close → 8) 다음 라운드 예약.
198
198
 
199
- 현재 누적: **70 라운드 (1.9.40 → 1.36.24)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
199
+ 현재 누적: **70 라운드 (1.9.40 → 1.36.26)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
200
200
 
201
- ### 성능 가이드 (1.9.140 측정)
201
+ ### 성능 가이드
202
202
 
203
203
  - `leerness handoff .` — 평균 ~1.5s (캐시 워밍업 후 ~0.6s)
204
204
  - `leerness memory status --json` — 평균 ~250ms
205
205
  - `leerness task list --json` — 평균 ~200ms
206
206
  - `leerness drift check --json` — 평균 ~400ms
207
207
  - MCP `tools/list` 응답 — 평균 ~150ms
208
- - usage-stats / lessons / listAllSkills 모두 메모리 캐싱 (1.9.65/66)
208
+ - usage-stats / lessons / listAllSkills 모두 메모리 캐싱
209
209
 
210
210
  ### 빠른 시작
211
211
 
@@ -222,7 +222,7 @@ leerness handoff . # 컨텍스트 자동 로드
222
222
  # 4. 세션 종료 시
223
223
  leerness session close . # 9 카테고리 + 룰 검증 + 다음 라운드 추천
224
224
 
225
- # 5. release 자동화 (1.9.140 main 자동 push 포함)
225
+ # 5. release 자동화 (main 자동 push 포함)
226
226
  leerness release pack --close --auto-main-push
227
227
  ```
228
228
 
@@ -234,6 +234,6 @@ leerness release pack --close --auto-main-push
234
234
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
235
235
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
236
236
 
237
- Last synced by Leerness v1.36.24: 2026-07-15
237
+ Last synced by Leerness v1.36.26: 2026-07-15
238
238
  <!-- leerness:project-readme:end -->
239
239
 
package/bin/leerness.js CHANGED
@@ -22,7 +22,7 @@ const { _isSecretKey, _isPlaceholderSecret, _looksSecretLike, _mergeLines, _merg
22
22
  _detectOptimism: _puDetectOptimism, _computeConfidence: _puComputeConfidence,
23
23
  _personaSummaries, _translate,
24
24
  _decisionsFromMd, _renderDecisionsMd, _renderLessonsMd,
25
- _withBuiltinSource, _esc, _roadmapTokenStyles, _parseSkillMd,
25
+ _withBuiltinSource, _esc, _roadmapTokenStyles, _parseSkillMd, _lintSkillMeta,
26
26
  _migrationGuideText, _parseContractSpec, _gitignoreMatch,
27
27
  _featureGraphTemplate, _parseFeatureGraph, _nextFeatureId, _featureBlock, _featureImpactBfs,
28
28
  _parseChangelogBetween, _cellSafe, _cellUnescape, _lineSafe, _parseLimit, _parseAddTitle, _parseImplExports, _taskPositionalPath, _completionClaimAllowed, _minorKey, _shouldPublishNpm,
@@ -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.24';
37
+ const VERSION = '1.36.26';
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') 시 호스트 프로세스 오염.
@@ -366,18 +366,18 @@ function managedReadmeBlock(project) {
366
366
  '### Autonomous mode (자율 모드)',
367
367
  '',
368
368
  '`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:',
369
- '1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.',
369
+ '1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push → 7) session close → 8) 다음 라운드 예약.',
370
370
  '',
371
371
  `현재 누적: **70 라운드 (1.9.40 → ${VERSION})** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.`,
372
372
  '',
373
- '### 성능 가이드 (1.9.140 측정)',
373
+ '### 성능 가이드',
374
374
  '',
375
375
  '- `leerness handoff .` — 평균 ~1.5s (캐시 워밍업 후 ~0.6s)',
376
376
  '- `leerness memory status --json` — 평균 ~250ms',
377
377
  '- `leerness task list --json` — 평균 ~200ms',
378
378
  '- `leerness drift check --json` — 평균 ~400ms',
379
379
  '- MCP `tools/list` 응답 — 평균 ~150ms',
380
- '- usage-stats / lessons / listAllSkills 모두 메모리 캐싱 (1.9.65/66)',
380
+ '- usage-stats / lessons / listAllSkills 모두 메모리 캐싱',
381
381
  '',
382
382
  '### 빠른 시작',
383
383
  '',
@@ -394,7 +394,7 @@ function managedReadmeBlock(project) {
394
394
  '# 4. 세션 종료 시',
395
395
  'leerness session close . # 9 카테고리 + 룰 검증 + 다음 라운드 추천',
396
396
  '',
397
- '# 5. release 자동화 (1.9.140 main 자동 push 포함)',
397
+ '# 5. release 자동화 (main 자동 push 포함)',
398
398
  'leerness release pack --close --auto-main-push',
399
399
  '```',
400
400
  '',
@@ -428,8 +428,8 @@ function coreFiles(root, lang = 'ko', selectedSkills = [], opts = {}) {
428
428
  const project = detectProjectName(root);
429
429
  const skillRows = Object.entries(skillCatalog).map(([k, v]) => `| ${k} | ${v.displayNameKo} | ${v.capabilities.join(', ')} | ${v.lastUpdated} | ${v.verification} |`).join('\n');
430
430
  const _files = {
431
- 'AGENTS.md': `${MARK}\n# Leerness Agent Instructions\n\n## ⭐ 매 세션 첫 행동 (1.9.39+)\n**반드시 \`.harness/session-workflow.md\`를 먼저 읽고 6단계 워크플로를 따른다**: 요청분석→계획→분배→sub-agent작업→종합검증→마감. 라운드 길이/복잡도 무관, drift 방지를 위해 모든 작업에 동일 흐름 유지.\n\n## 정적 vs 동적 — leerness 역할 경계 (1.9.282, UR-0035)\n**AGENTS.md = 정적 프로젝트 지침** (코딩 규칙·테스트 명령·금지 사항·배포 절차 — 자주 안 변함).\n**leerness = 동적 작업 상태·기억·검증·인수인계** (현재 목표·수정 파일·실패 시도·검증 결과·다음 에이전트 인계 — 매 작업 변함).\n- 규칙/명령/금지는 여기 AGENTS.md 에 적는다.\n- 동적 상태(결정/교훈/계획/진행/검증/인수인계)는 leerness 가 **기본 워크스페이스 \`.harness/\`** 에 기록한다 (decisions.md / lessons.md / plan.md / progress-tracker.md / session-handoff.md). \`leerness handoff\` · \`decision add\` · \`lesson save\` 등이 여기에 쓴다.\n- (선택) \`leerness state show|start|record|verify|handoff\` (또는 MCP \`leerness_state_*\`) 의 JSON 상태 substrate 는 \`.leerness/\` (에이전트 간 인수인계 표준, 1.9.278 — state 명령 사용 시 생성). 메인 워크스페이스(.harness)와 별개.\n- leerness 는 AGENTS.md 를 **대체하지 않고 보완**한다. 정적 지침은 여기, 동적 상태는 leerness.\n\n## Mandatory read order (session start)\n1. **.harness/session-workflow.md** (1.9.39+ 6단계 워크플로 — 최우선)\n2. .harness/context-routing.md\n3. .harness/session-handoff.md\n4. .harness/current-state.md\n5. .harness/plan.md\n6. .harness/progress-tracker.md\n7. .harness/guideline.md\n8. .harness/protected-files.md\n9. .harness/writeback-policy.md\n10. .harness/anti-lazy-work-policy.md\n11. **.harness/rules.md** (사용자 정의 영구 룰 — 매 세션 반드시 따름)\n\n## Required behavior\n- 작업 시작 시 \`leerness handoff .\`를 실행해 컨텍스트를 적재합니다 (handoff가 active rules를 자동 출력).\n- 작업 분류는 \`leerness route <task-type>\`로 확인합니다 (planning, feature, bugfix, refactor, research, consistency, release, migration, session-start, session-close, harness-maintenance).\n- 보호 파일/관리 섹션을 삭제하지 않습니다. 머지·아카이브·deprecated 표시를 사용합니다.\n- 의미 있는 변경 후 progress-tracker, current-state, task-log, session-handoff를 갱신합니다.\n- 완료 선언 전 \`leerness check .\` 또는 \`leerness lazy detect .\`로 자기검증하고, \`leerness lens\`의 분야별 자기질문에 답합니다 (코드: "선임 개발자가 복잡하다고 느끼지 않을까?" / 디자인: "선임 디자이너와 일반 사용자가 이쁘고 직관적이라 느낄까?" — 1.18.3).\n- 변경 전 secret/encoding 가드: \`leerness scan secrets .\`, \`leerness encoding check .\`.\n- 같은 기능 중복 생성 전 design-system.md, consistency-policy.md, reuse-map.md를 확인합니다.\n- 매 세션 종료 시 \`leerness session close .\`로 9개 카테고리(완료/진행중/미완료/예정/대기/보류/차단/드랍/검증) + **활성 룰 검증 결과**를 보고합니다.\n- 업데이트는 \`leerness update --check\` (감지) → \`leerness update --yes\` (자동 마이그레이션).\n\n## 자연어 회고/통찰/브레인스토밍 (1.9.13)\n사용자가 자연어로 회고/통찰/브레인스토밍을 요청하면 즉시 leerness 명령으로 호출합니다.\n\n| 사용자 발화 (자연어) | 즉시 실행할 명령 |\n|---|---|\n| "회고해줘 / 돌아보자 / 정리해줘" | \`leerness retro\` |\n| "최근 N일 회고" | \`leerness retro --days N\` |\n| "통계 / 누적 지표 / insights" | \`leerness insights\` |\n| "X에 대해 브레인스토밍 / X 관련 자료 / X 시작 전 검토" | \`leerness brainstorm "X"\` |\n\nsession close가 매번 자동으로 한 줄 요약을 출력하고, 5세션마다 자동 깊은 회고를 실행합니다. 사용자가 명시 요청 시 즉시 호출.\n\n## 자연어 룰 처리 (1.9.8)\n사용자가 자연어로 영구 룰을 요청하면 즉시 leerness rule 명령으로 등록합니다.\n\n| 사용자 발화 (자연어) | 즉시 실행할 명령 |\n|---|---|\n| "매 업데이트마다 버전 bump해줘" | \`leerness rule add "버전을 patch로 bump" --trigger every-update\` |\n| "매 커밋마다 패치노트 추가해줘" | \`leerness rule add "패치노트 추가" --trigger every-commit\` |\n| "세션 종료마다 배포해줘" | \`leerness rule add "배포 (release publish)" --trigger session-close\` |\n| "X 룰 중지/그만/끄기" | \`leerness rule pause <ID>\` (해당 룰 ID는 list로 확인) |\n| "X 룰 제거/삭제" | \`leerness rule remove <ID>\` |\n| "모든 룰 중지" | \`leerness rule stop\` |\n| "룰 다시 켜줘" | \`leerness rule resume-all\` 또는 \`leerness rule resume <ID>\` |\n\n룰을 등록한 후 사용자에게 등록 결과(ID + trigger + 설명)를 보고하고, 그 이후 매 세션마다 자동 적용합니다. 사용자가 "중지" 또는 "제거"를 명시적으로 말하기 전까지는 룰을 비활성화하지 않습니다.\n\n## 룰 자동 적용 (1.9.8)\nleerness가 자동 검증 가능한 trigger:\n- **every-update / version bump 키워드 룰**: package.json의 version이 갱신됐는지 검사 (handoff/session close가 baseline 캐시와 비교).\n- **CHANGELOG / 패치노트 키워드 룰**: CHANGELOG.md의 mtime이 갱신됐는지 검사.\n- **test / 테스트 / verify 키워드 룰**: review-evidence.md에 오늘 verify-code 흔적이 있는지 검사.\n- **배포 / publish / push 키워드 룰**: 자동 검증 불가 → 사용자에게 release publish 명령을 안내.\n\n자동 검증 가능한 룰의 실행은 \`leerness release bump\`, \`leerness release note "..."\`, \`leerness release publish\`를 사용해 자동화합니다.\n`,
432
- 'CLAUDE.md': `${MARK}\n# Claude Code Instructions\n\nFollow AGENTS.md. Always run \`leerness handoff .\` at the start and \`leerness session close .\` before ending a session.\n\n**⭐ 매 세션 첫 행동 (1.9.39+)**: \`.harness/session-workflow.md\`의 6단계 워크플로(요청분석→계획→분배→sub-agent→종합검증→마감)를 따라야 함. drift critical 시 \`leerness drift check --auto-fix\`로 자동 회복.\n\nProtected files must not be deleted. Read .harness/anti-lazy-work-policy.md before claiming completion.\n\n## 자연어 영구 룰 (1.9.8)\n사용자가 "매 X마다 Y를 해줘" 같은 자연어 룰을 말하면 즉시 \`leerness rule add "Y" --trigger every-X\`로 등록하세요. 등록된 룰은 매 세션 \`handoff\`가 자동 출력하고, \`session close\`가 자동 검증해 보고합니다. 사용자가 "중지" / "그만" / "끄기"를 명시할 때만 \`rule pause/remove\`를 호출합니다.\n\n자세한 매핑은 AGENTS.md의 "자연어 룰 처리" 표를 참고하세요.\n`,
431
+ 'AGENTS.md': `${MARK}\n# Leerness Agent Instructions\n\n## ⭐ 매 세션 첫 행동\n**반드시 \`.harness/session-workflow.md\`를 먼저 읽고 6단계 워크플로를 따른다**: 요청분석→계획→분배→sub-agent작업→종합검증→마감. 라운드 길이/복잡도 무관, drift 방지를 위해 모든 작업에 동일 흐름 유지.\n\n## 정적 vs 동적 — leerness 역할 경계\n**AGENTS.md = 정적 프로젝트 지침** (코딩 규칙·테스트 명령·금지 사항·배포 절차 — 자주 안 변함).\n**leerness = 동적 작업 상태·기억·검증·인수인계** (현재 목표·수정 파일·실패 시도·검증 결과·다음 에이전트 인계 — 매 작업 변함).\n- 규칙/명령/금지는 여기 AGENTS.md 에 적는다.\n- 동적 상태(결정/교훈/계획/진행/검증/인수인계)는 leerness 가 **기본 워크스페이스 \`.harness/\`** 에 기록한다 (decisions.md / lessons.md / plan.md / progress-tracker.md / session-handoff.md). \`leerness handoff\` · \`decision add\` · \`lesson save\` 등이 여기에 쓴다.\n- (선택) \`leerness state show|start|record|verify|handoff\` (또는 MCP \`leerness_state_*\`) 의 JSON 상태 substrate 는 \`.leerness/\` (에이전트 간 인수인계 표준 — state 명령 사용 시 생성). 메인 워크스페이스(.harness)와 별개.\n- leerness 는 AGENTS.md 를 **대체하지 않고 보완**한다. 정적 지침은 여기, 동적 상태는 leerness.\n\n## Mandatory read order (session start)\n1. **.harness/session-workflow.md** (6단계 워크플로 — 최우선)\n2. .harness/context-routing.md\n3. .harness/session-handoff.md\n4. .harness/current-state.md\n5. .harness/plan.md\n6. .harness/progress-tracker.md\n7. .harness/guideline.md\n8. .harness/protected-files.md\n9. .harness/writeback-policy.md\n10. .harness/anti-lazy-work-policy.md\n11. **.harness/rules.md** (사용자 정의 영구 룰 — 매 세션 반드시 따름)\n\n## Required behavior\n- 작업 시작 시 \`leerness handoff .\`를 실행해 컨텍스트를 적재합니다 (handoff가 active rules를 자동 출력).\n- 작업 분류는 \`leerness route <task-type>\`로 확인합니다 (planning, feature, bugfix, refactor, research, consistency, release, migration, session-start, session-close, harness-maintenance).\n- 보호 파일/관리 섹션을 삭제하지 않습니다. 머지·아카이브·deprecated 표시를 사용합니다.\n- 의미 있는 변경 후 progress-tracker, current-state, task-log, session-handoff를 갱신합니다.\n- 완료 선언 전 \`leerness check .\` 또는 \`leerness lazy detect .\`로 자기검증하고, \`leerness lens\`의 분야별 자기질문에 답합니다 (코드: "선임 개발자가 복잡하다고 느끼지 않을까?" / 디자인: "선임 디자이너와 일반 사용자가 이쁘고 직관적이라 느낄까?").\n- 변경 전 secret/encoding 가드: \`leerness scan secrets .\`, \`leerness encoding check .\`.\n- 같은 기능 중복 생성 전 design-system.md, consistency-policy.md, reuse-map.md를 확인합니다.\n- 매 세션 종료 시 \`leerness session close .\`로 9개 카테고리(완료/진행중/미완료/예정/대기/보류/차단/드랍/검증) + **활성 룰 검증 결과**를 보고합니다.\n- 업데이트는 \`leerness update --check\` (감지) → \`leerness update --yes\` (자동 마이그레이션).\n\n## 자연어 회고/통찰/브레인스토밍\n사용자가 자연어로 회고/통찰/브레인스토밍을 요청하면 즉시 leerness 명령으로 호출합니다.\n\n| 사용자 발화 (자연어) | 즉시 실행할 명령 |\n|---|---|\n| "회고해줘 / 돌아보자 / 정리해줘" | \`leerness retro\` |\n| "최근 N일 회고" | \`leerness retro --days N\` |\n| "통계 / 누적 지표 / insights" | \`leerness insights\` |\n| "X에 대해 브레인스토밍 / X 관련 자료 / X 시작 전 검토" | \`leerness brainstorm "X"\` |\n\nsession close가 매번 자동으로 한 줄 요약을 출력하고, 5세션마다 자동 깊은 회고를 실행합니다. 사용자가 명시 요청 시 즉시 호출.\n\n## 자연어 룰 처리\n사용자가 자연어로 영구 룰을 요청하면 즉시 leerness rule 명령으로 등록합니다.\n\n| 사용자 발화 (자연어) | 즉시 실행할 명령 |\n|---|---|\n| "매 업데이트마다 버전 bump해줘" | \`leerness rule add "버전을 patch로 bump" --trigger every-update\` |\n| "매 커밋마다 패치노트 추가해줘" | \`leerness rule add "패치노트 추가" --trigger every-commit\` |\n| "세션 종료마다 배포해줘" | \`leerness rule add "배포 (release publish)" --trigger session-close\` |\n| "X 룰 중지/그만/끄기" | \`leerness rule pause <ID>\` (해당 룰 ID는 list로 확인) |\n| "X 룰 제거/삭제" | \`leerness rule remove <ID>\` |\n| "모든 룰 중지" | \`leerness rule stop\` |\n| "룰 다시 켜줘" | \`leerness rule resume-all\` 또는 \`leerness rule resume <ID>\` |\n\n룰을 등록한 후 사용자에게 등록 결과(ID + trigger + 설명)를 보고하고, 그 이후 매 세션마다 자동 적용합니다. 사용자가 "중지" 또는 "제거"를 명시적으로 말하기 전까지는 룰을 비활성화하지 않습니다.\n\n## 룰 자동 적용\nleerness가 자동 검증 가능한 trigger:\n- **every-update / version bump 키워드 룰**: package.json의 version이 갱신됐는지 검사 (handoff/session close가 baseline 캐시와 비교).\n- **CHANGELOG / 패치노트 키워드 룰**: CHANGELOG.md의 mtime이 갱신됐는지 검사.\n- **test / 테스트 / verify 키워드 룰**: review-evidence.md에 오늘 verify-code 흔적이 있는지 검사.\n- **배포 / publish / push 키워드 룰**: 자동 검증 불가 → 사용자에게 release publish 명령을 안내.\n\n자동 검증 가능한 룰의 실행은 \`leerness release bump\`, \`leerness release note "..."\`, \`leerness release publish\`를 사용해 자동화합니다.\n`,
432
+ 'CLAUDE.md': `${MARK}\n# Claude Code Instructions\n\nFollow AGENTS.md. Always run \`leerness handoff .\` at the start and \`leerness session close .\` before ending a session.\n\n**⭐ 매 세션 첫 행동**: \`.harness/session-workflow.md\`의 6단계 워크플로(요청분석→계획→분배→sub-agent→종합검증→마감)를 따라야 함. drift critical 시 \`leerness drift check --auto-fix\`로 자동 회복.\n\nProtected files must not be deleted. Read .harness/anti-lazy-work-policy.md before claiming completion.\n\n## 자연어 영구 룰\n사용자가 "매 X마다 Y를 해줘" 같은 자연어 룰을 말하면 즉시 \`leerness rule add "Y" --trigger every-X\`로 등록하세요. 등록된 룰은 매 세션 \`handoff\`가 자동 출력하고, \`session close\`가 자동 검증해 보고합니다. 사용자가 "중지" / "그만" / "끄기"를 명시할 때만 \`rule pause/remove\`를 호출합니다.\n\n자세한 매핑은 AGENTS.md의 "자연어 룰 처리" 표를 참고하세요.\n`,
433
433
  '.cursor/rules/leerness.mdc': `${MARK}\n---\nalwaysApply: true\n---\nFollow AGENTS.md and .harness/context-routing.md.\nRun: \`leerness handoff .\` at session start.\nRun: \`leerness session close .\` at session end.\nPreserve Leerness protected files.\n`,
434
434
  '.github/copilot-instructions.md': `${MARK}\n# Copilot Instructions\n\nUse AGENTS.md and .harness/ as project memory.\nDo not remove protected Leerness files.\nBefore completion, ensure plan.md, progress-tracker.md, current-state.md, session-handoff.md are updated.\n`,
435
435
  '.harness/HARNESS_VERSION': VERSION + '\n',
@@ -456,7 +456,7 @@ function coreFiles(root, lang = 'ko', selectedSkills = [], opts = {}) {
456
456
  '.harness/reuse-map.md': fm('reuse-map', ['새 컴포넌트/API/helper 생성 전','중복 기능 감지'], ['재사용 가능한 요소 추가'], `# Reuse Map\n\n| Capability | Existing Element | Reuse Method | Notes |\n|---|---|---|---|\n`),
457
457
  '.harness/feature-contracts.md': fm('feature-contracts', ['기능 구현/수정 전'], ['기능 입출력/상태/오류 변경'], `# Feature Contracts\n\n## Template\n- Feature:\n- Input:\n- Output:\n- States:\n- Errors:\n- Related files:\n- Test evidence ID:\n`),
458
458
  // 1.9.141: Feature Causality Graph — 신규 기능/형식 변경 시 영향 범위 자동 추적 (사용자 요청)
459
- '.harness/feature-graph.md': fm('feature-graph', ['신규 기능 추가 전','데이터 형식 변경 전','외부 API 매칭 작업 전'], ['feature 등록 / 링크 / impact 회수'], `# Feature Graph (1.9.141)\n\n> **목적**: 각 기능의 인과관계를 정확히 정리해서 코드 작성 전 영향 범위를 자동 추적.\n> 신규 기능 추가, 데이터 형식 변경, 외부 API 매칭 작업 전 \`leerness feature impact <id>\`로 확인.\n> handoff가 현재 task 키워드로 자동 매칭해서 영향받는 feature 목록을 회수.\n\n## How to use\n\n\`\`\`bash\nleerness feature add "User Auth" # F-0001 자동 부여\nleerness feature link F-0002 --depends-on F-0001 # 의존 관계\nleerness feature link F-0001 --affects F-0002,F-0005 # 영향 관계 (다수)\nleerness feature link F-0001 --co-changes-with F-0011 # 함께 변해야 하는 기능\nleerness feature impact F-0001 # 영향받는 전체 (transitive)\nleerness feature list --json # 그래프 JSON\nleerness feature show F-0001 # 단일 상세\n\`\`\`\n\n## Nodes\n\n`),
459
+ '.harness/feature-graph.md': fm('feature-graph', ['신규 기능 추가 전','데이터 형식 변경 전','외부 API 매칭 작업 전'], ['feature 등록 / 링크 / impact 회수'], `# Feature Graph\n\n> **목적**: 각 기능의 인과관계를 정확히 정리해서 코드 작성 전 영향 범위를 자동 추적.\n> 신규 기능 추가, 데이터 형식 변경, 외부 API 매칭 작업 전 \`leerness feature impact <id>\`로 확인.\n> handoff가 현재 task 키워드로 자동 매칭해서 영향받는 feature 목록을 회수.\n\n## How to use\n\n\`\`\`bash\nleerness feature add "User Auth" # F-0001 자동 부여\nleerness feature link F-0002 --depends-on F-0001 # 의존 관계\nleerness feature link F-0001 --affects F-0002,F-0005 # 영향 관계 (다수)\nleerness feature link F-0001 --co-changes-with F-0011 # 함께 변해야 하는 기능\nleerness feature impact F-0001 # 영향받는 전체 (transitive)\nleerness feature list --json # 그래프 JSON\nleerness feature show F-0001 # 단일 상세\n\`\`\`\n\n## Nodes\n\n`),
460
460
  '.harness/testing-strategy.md': fm('testing-strategy', ['검증 전','릴리즈 전'], ['테스트 전략 변경'], `# Testing Strategy\n\n- Typecheck (\`tsc --noEmit\` 또는 동등)\n- Lint (\`npm run lint\` 등)\n- Unit/Integration/E2E\n- Manual smoke test\n- Browser/UI smoke (frontend 변경 시)\n\n## Evidence Format\nEach completed task must reference an evidence ID stored in .harness/review-evidence.md.\n`),
461
461
  '.harness/review-checklist.md': fm('review-checklist', ['PR/리뷰 전'], ['리뷰 기준 변경'], `# Review Checklist\n\n- [ ] 계획과 정렬되어 있는가\n- [ ] progress-tracker가 갱신되었는가\n- [ ] 보호 파일을 삭제하지 않았는가\n- [ ] 디자인/기능 재사용을 확인했는가\n- [ ] 시크릿이 코드에 들어가지 않았는가 (\`leerness scan secrets\`)\n- [ ] 한글 인코딩 OK (\`leerness encoding check\`)\n- [ ] 게으름 평가 통과 (\`leerness lazy detect\`)\n`),
462
462
  '.harness/release-checklist.md': fm('release-checklist', ['배포 전'], ['배포 조건/환경변수/롤백 변경'], `# Release Checklist\n\n- [ ] \`leerness verify .\`\n- [ ] \`leerness audit .\`\n- [ ] \`leerness scan secrets .\`\n- [ ] \`leerness encoding check .\`\n- [ ] 프로젝트 typecheck/lint/test\n- [ ] 환경변수 (.env.example) 동기화\n- [ ] 롤백 방법 확인\n- [ ] CHANGELOG 갱신\n`),
@@ -488,7 +488,7 @@ leerness agents dispatch "<task>" --to <id> # 작업 유형 추천 자동
488
488
  - 작업 유형별 최적 sub-agent:
489
489
  - 텍스트/번역/분석 → claude (1.7× 빠름)
490
490
  - 깊은 코드 추론 → codex (가장 상세)
491
- - 파일 직접 수정 → agy --yolo (정확, Antigravity CLI 1.9.248)
491
+ - 파일 직접 수정 → agy --yolo (정확, Antigravity CLI)
492
492
  - 보안 리뷰 → \`leerness review --persona security\`
493
493
  - **충돌 방지 규칙 (필수)**:
494
494
  - 각 sub-agent에 *자신만 수정할 파일 경로* 명시
@@ -510,22 +510,22 @@ leerness review <file> --persona security,performance,ux
510
510
 
511
511
  ## Step 6. 세션 마감 + 인계 + 다음 라운드 추천
512
512
  \`\`\`bash
513
- leerness session close . # 1.9.59+ — --suggest default 활성 (마감 + 다음 라운드 자동)
513
+ leerness session close . # --suggest default 활성 (마감 + 다음 라운드 자동)
514
514
  leerness session close . --no-suggest # suggest 비활성 (이전 동작)
515
515
 
516
516
  # 분리 호출도 가능:
517
- leerness skill suggest . # 1.9.53 — 반복 패턴 → 새 skill 후보
517
+ leerness skill suggest . # 반복 패턴 → 새 skill 후보
518
518
  leerness drift check . # 4 신호 + 4 레벨 점검
519
519
  leerness audit . --fix # 누락 메타 자동 보강
520
520
  \`\`\`
521
521
 
522
- ## 🧠 Memory CRUD Quick Reference (1.9.107~135)
522
+ ## 🧠 Memory CRUD Quick Reference
523
523
 
524
524
  5 Memory Surface 모두 CRUD CLI + MCP 노출 완성:
525
525
 
526
526
  | Surface | CREATE | READ | DELETE | RESTORE |
527
527
  |---|---|---|---|---|
528
- | **tasks** | task add | task list --json (1.9.134) | task drop | task update |
528
+ | **tasks** | task add | task list --json | task drop | task update |
529
529
  | **decisions** | decision add | decision list --json | decision drop | memory restore decisions |
530
530
  | **lessons** | lesson save | lesson list [--tag] | lesson drop | memory restore lessons |
531
531
  | **plan** | plan add | plan list --json | plan remove | memory restore plan |
@@ -534,69 +534,69 @@ leerness audit . --fix # 누락 메타 자동 보강
534
534
  \`\`\`bash
535
535
  leerness memory status [--json] # 5종 상태 통합 조회 (T/D/R/P/L 카운트)
536
536
  leerness memory archive list [--surface s] # DELETE archive 통합 조회 (복원 후보)
537
- leerness memory restore <surface> <target> # archive → active 복귀 (DELETE→RESTORE cycle, 1.9.128)
537
+ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE→RESTORE cycle)
538
538
  \`\`\`
539
539
 
540
540
  **잘못 저장한 항목 복구**:
541
541
  1. \`memory archive list\` — 복원 후보 확인
542
542
  2. \`memory restore decisions "PostgreSQL"\` — archive → active
543
- 3. handoff 가 매 세션 자동으로 24h 내 archive 활동 알림 (1.9.129)
543
+ 3. handoff 가 매 세션 자동으로 24h 내 archive 활동 알림
544
544
 
545
545
 
546
546
  - session close가 누락되면 다음 세션 시작 시 drift critical 발생.
547
547
  - 자동 회복 옵션: \`drift check --auto-fix\` (critical 시 session close 자동 실행).
548
- - 1.9.56+ handoff가 매 세션 시작 시 **과거 lessons 자동 재상기** (현재 task 키워드 기준).
549
- - 1.9.67+ handoff가 현재 task와 매칭되는 **설치된 skill을 자동 추천** (jaccard 기반, default ON, \`--no-skill-suggest\`로 끄기).
550
- - 1.9.67+ lessons 인덱스에 \`task-log.md\` 실패 라인까지 포함 → 회수 범위 확장.
551
- - 1.9.69+ handoff가 \`skill-suggestions.md\` rolling history (과거 같은 키워드 매칭 결과)도 자동 노출.
552
- - 1.9.76+ handoff에 보안 요약 1~2 line 자동 (\`.env\` ↔ \`.env.example\` 동기화 + \`.gitignore\` 시크릿 누락).
553
- - 1.9.80+ \`.env\` 가 \`.gitignore\` 에 누락 시 🚨 CRITICAL + \`LEERNESS_AUTO_SECURITY_FIX=1\` 환경변수 시 \`audit --fix\` 자동 실행.
554
- - 1.9.81+ handoff Date/Project 직후 통합 헤드라인 한 줄 (drift / 보안 / MCP / skill query / 설치 skill 수).
555
- - 1.9.85+ \`leerness health\` 한 줄로 종합 점검 (drift + 보안 + skills + usage + tasks).
556
- - 1.9.78/82+ \`leerness drift check --auto-fix\` 가 보안 신호 발견 시 \`audit --fix\` 자동 실행 → 재검사.
557
- - 1.9.86+ MCP server **18 도구** (handoff/drift/audit/verify_claim/contract/agents/reuse/whats_new/usage_stats/session_close/skill_suggest/lessons/task_export/env_check/brainstorm/skill_match/skill_list/health).
558
- - 1.9.94+ MCP server **21 도구** (skill_search/skill_info/benchmark 추가).
559
- - 1.9.96+ \`leerness handoff --json\` (외부 AI/MCP 통합용 구조화 출력).
560
- - 1.9.98+ \`leerness skill publish\` 보안 사전 점검 통합 (health 통과 후 publish).
561
- - 1.9.99+ \`leerness handoff --quiet\` (자동화/CI 모드 — 자동 회수 라인 비활성).
562
- - 1.9.100 🏆 마일스톤 — 30 라운드 자율 누적, stress-v45 30/30 PASS, e2e 219/219 PASS.
563
- - 1.9.101+ \`leerness lazy detect --json\` + MCP **22 도구** (\`leerness_lazy_detect\` 추가 — 거짓 완료/empty handoff/no test run/TODO 미추적 신호 JSON).
564
- - 1.9.102+ \`leerness audit --json\` 구조화 출력 (findings 11종 kind: 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). MCP \`leerness_audit\`도 JSON 자동.
565
- - 1.9.103+ \`leerness session close --json\` 마감 통계 JSON (taskCounts/rules/skillCandidates/drift/topCommands/mcpStats/workspacePeers). MCP \`leerness_session_close\`도 JSON 자동.
566
- - 1.9.104+ MCP **23 도구** (\`leerness_retro\` 추가 — 4세션 누적 회고 JSON 외부 AI 노출).
567
- - 1.9.105+ MCP **24 도구** (\`leerness_task_add\` 추가 — 외부 AI 가 task 즉시 등록, 양방향 제어 완성).
568
- - 1.9.106+ MCP **25 도구** (\`leerness_task_update\` 추가 — task 상태/evidence/nextAction 갱신, read+add+update 3종 surface 완성).
569
- - 1.9.107+ MCP **26 도구** (\`leerness_task_drop\` 추가 — task 폐기, **task CRUD 완성**: read/add/update/drop).
570
- - 1.9.108+ \`leerness decision add\` CLI + MCP **27 도구** (\`leerness_decision_add\` — decisions.md 영구화 + handoff lessons 회수와 통합).
571
- - 1.9.109+ \`leerness rule list --json\` + MCP **29 도구** (\`leerness_rule_add\` + \`leerness_rule_list\` — 자연어 영구 룰 R/W).
572
- - 1.9.110+ MCP **30 도구 🎉 30 도구 마일스톤** (\`leerness_plan_add\` — plan.md milestone + progress-tracker 자동 동기화).
573
- - 1.9.112+ MCP **31 도구** (\`leerness_lesson_save\` — lessons.md 직접 write, **Memory Write Surface 5종 완성**: tasks/decisions/rules/plan/lessons).
574
- - 1.9.113+ handoff 통합 헤드라인에 **🧠 mem T/D/R/P/L 카운트** 추가 — 5종 메모리 영구화 상태 한눈에 확인.
575
- - 1.9.114+ \`leerness memory status [--json]\` + MCP **32 도구** (\`leerness_memory_status\`) — 상세 상태 + 최근 항목 조회.
576
- - 1.9.115+ \`leerness handoff --json\` 응답에 **\`memorySurface\` 필드 통합** — 단일 호출로 컨텍스트 + 5종 메모리 상태 동시 회수.
577
- - 1.9.116+ \`leerness brainstorm\` 회수 범위에 **lessons.md + plan.md** milestone 추가 — Memory Surface 5종 완전 통합.
578
- - 1.9.117+ \`leerness lesson list [--tag] [--json]\` + MCP **33 도구** (\`leerness_lesson_list\`) — lessons.md 전용 조회 + tag 필터.
579
- - 1.9.118+ \`leerness decision list [--json]\` + MCP **34 도구** (\`leerness_decision_list\`) — decisions.md 전체 조회 (Decision/Reason/Alternatives/Impact 메타).
580
- - 1.9.119+ \`leerness plan list [--json]\` + MCP **35 도구** (\`leerness_plan_list\`) — plan.md milestone 전체 (Status/Progress/Tasks). **Memory Surface READ 5종 완전 완성**.
581
- - 1.9.121+ handoff 6번째 자동 회수 \`🆕 최근 24h 메모리 변동\` — 5종 surface 의 24h 내 추가 항목 자동 노출.
582
- - 1.9.122+ \`session close --json\` 응답에도 \`memorySurface\` 필드 통합 — 마감 시 5종 메모리 상태 동시 회수.
583
- - 1.9.123+ \`health --json\` 응답에도 \`memorySurface\` 필드 통합 — handoff/session close/memory status 모든 JSON 명령 일관성.
584
- - 1.9.124+ \`leerness lesson drop <target>\` + MCP **36 도구** (\`leerness_lesson_drop\`) — 잘못 저장한 lesson 제거 (archive 자동 보존).
585
- - 1.9.125+ \`leerness decision drop <target>\` + MCP **37 도구** (\`leerness_decision_drop\`) — 잘못 저장한 결정 제거 (archive 보존).
586
- - 1.9.126+ \`leerness plan remove <M-XXXX|title>\` + MCP **38 도구** (\`leerness_plan_remove\`) — milestone 영구 제거 (archive 보존). **Memory Surface DELETE 5종 완전 완성** 🎉.
587
- - 1.9.127+ \`leerness memory archive list [--surface decisions|lessons|plan] [--json]\` + MCP **39 도구** (\`leerness_memory_archive_list\`) — DELETE 5종 archive 통합 조회 (복원 후보 회수).
588
- - 1.9.128+ \`leerness memory restore <surface> <target>\` + MCP **40 도구 🎉** (\`leerness_memory_restore\`) — archive → active 복귀 (DELETE→RESTORE cycle 완성). **MCP 40 도구 마일스톤**.
589
- - 1.9.129+ handoff **7번째 자동 회수** — \`🗑 최근 24h archive\` (D/L/P 카운트 + 복원 후보 안내). DELETE 활동 자동 인지.
590
- - 1.9.130+ 🎉 **60 라운드 자율 모드 마일스톤** — JSON 4종 (handoff/memory status/session close/health) \`memorySurface.archive\` 필드 통합. MCP 40 / handoff auto-recovery 7 / DELETE-RESTORE cycle 완성.
591
- - 1.9.131+ \`brainstorm\` 회수 범위에 3 archive 파일 (decisions/lessons/plan archive) 통합 — 과거 제거된 ideas 가 새 brainstorm 시 다시 후보로 노출. \`hits.archive\` 필드 + 복원 안내 라인.
592
- - 1.9.132+ \`session close\` 텍스트 모드에 archive 누적 라인 추가 — 마감 시점 DELETE 활동 가시화 (handoff 7번째 회수와 symmetric). archive 가시성 6 surface 완성.
593
- - 1.9.133+ \`brainstorm\` 텍스트 모드 lessonsExplicit / planMilestones display 추가 — 1.9.116에서 데이터 수집은 했지만 display 누락된 pre-existing gap fix.
594
- - 1.9.134+ \`leerness task list --json\` + MCP **41 도구** (\`leerness_task_list\`) — progress-tracker.md task 전체 JSON 조회 + \`--status\` 필터. Task surface CRUD MCP 완전 완성 (add/list/update/drop).
595
- - 1.9.135+ MCP **42 도구** (\`leerness_rule_remove\`) — rules.md 에서 특정 rule 제거 + archive 보존. **5 surface CRUD MCP 완전 완성** (task/decision/lesson/plan/rule 모두 add/list/delete MCP 노출).
596
- - 1.9.136+ MCP \`leerness_drift_check\` JSON 응답 fix — \`--json\` 플래그 자동 추가하여 외부 AI가 구조화된 drift 신호 회수 (score, level, signals[], healthy).
597
- - 1.9.137+ \`.harness/session-workflow.md\` 템플릿에 **🧠 Memory CRUD Quick Reference** 섹션 추가 — 5 surface × CRUD 매트릭스 + archive cycle 워크플로 가이드. 신규 \`init\` 워크스페이스 즉시 적용.
598
- - 1.9.138+ \`leerness memory archive list --query <keyword>\` + MCP \`leerness_memory_archive_list\` query 인자 — archive 항목 키워드 case-insensitive 검색 (target/originalHeader 매칭).
599
- - 1.9.139+ \`leerness lesson list --query\` + \`leerness decision list --query\` + MCP 동일 인자 — active Memory 항목 키워드 검색 (lesson: text/tag, decision: title/decision/reason/alternatives/impact).
548
+ - handoff가 매 세션 시작 시 **과거 lessons 자동 재상기** (현재 task 키워드 기준).
549
+ - handoff가 현재 task와 매칭되는 **설치된 skill을 자동 추천** (jaccard 기반, default ON, \`--no-skill-suggest\`로 끄기).
550
+ - lessons 인덱스에 \`task-log.md\` 실패 라인까지 포함 → 회수 범위 확장.
551
+ - handoff가 \`skill-suggestions.md\` rolling history (과거 같은 키워드 매칭 결과)도 자동 노출.
552
+ - handoff에 보안 요약 1~2 line 자동 (\`.env\` ↔ \`.env.example\` 동기화 + \`.gitignore\` 시크릿 누락).
553
+ - \`.env\` 가 \`.gitignore\` 에 누락 시 🚨 CRITICAL + \`LEERNESS_AUTO_SECURITY_FIX=1\` 환경변수 시 \`audit --fix\` 자동 실행.
554
+ - handoff Date/Project 직후 통합 헤드라인 한 줄 (drift / 보안 / MCP / skill query / 설치 skill 수).
555
+ - \`leerness health\` 한 줄로 종합 점검 (drift + 보안 + skills + usage + tasks).
556
+ - \`leerness drift check --auto-fix\` 가 보안 신호 발견 시 \`audit --fix\` 자동 실행 → 재검사.
557
+ - MCP server **18 도구** (handoff/drift/audit/verify_claim/contract/agents/reuse/whats_new/usage_stats/session_close/skill_suggest/lessons/task_export/env_check/brainstorm/skill_match/skill_list/health).
558
+ - MCP server **21 도구** (skill_search/skill_info/benchmark 추가).
559
+ - \`leerness handoff --json\` (외부 AI/MCP 통합용 구조화 출력).
560
+ - \`leerness skill publish\` 보안 사전 점검 통합 (health 통과 후 publish).
561
+ - \`leerness handoff --quiet\` (자동화/CI 모드 — 자동 회수 라인 비활성).
562
+ - 🏆 마일스톤 — 30 라운드 자율 누적, stress-v45 30/30 PASS, e2e 219/219 PASS.
563
+ - \`leerness lazy detect --json\` + MCP **22 도구** (\`leerness_lazy_detect\` 추가 — 거짓 완료/empty handoff/no test run/TODO 미추적 신호 JSON).
564
+ - \`leerness audit --json\` 구조화 출력 (findings 11종 kind: 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). MCP \`leerness_audit\`도 JSON 자동.
565
+ - \`leerness session close --json\` 마감 통계 JSON (taskCounts/rules/skillCandidates/drift/topCommands/mcpStats/workspacePeers). MCP \`leerness_session_close\`도 JSON 자동.
566
+ - MCP **23 도구** (\`leerness_retro\` 추가 — 4세션 누적 회고 JSON 외부 AI 노출).
567
+ - MCP **24 도구** (\`leerness_task_add\` 추가 — 외부 AI 가 task 즉시 등록, 양방향 제어 완성).
568
+ - MCP **25 도구** (\`leerness_task_update\` 추가 — task 상태/evidence/nextAction 갱신, read+add+update 3종 surface 완성).
569
+ - MCP **26 도구** (\`leerness_task_drop\` 추가 — task 폐기, **task CRUD 완성**: read/add/update/drop).
570
+ - \`leerness decision add\` CLI + MCP **27 도구** (\`leerness_decision_add\` — decisions.md 영구화 + handoff lessons 회수와 통합).
571
+ - \`leerness rule list --json\` + MCP **29 도구** (\`leerness_rule_add\` + \`leerness_rule_list\` — 자연어 영구 룰 R/W).
572
+ - MCP **30 도구 🎉 30 도구 마일스톤** (\`leerness_plan_add\` — plan.md milestone + progress-tracker 자동 동기화).
573
+ - MCP **31 도구** (\`leerness_lesson_save\` — lessons.md 직접 write, **Memory Write Surface 5종 완성**: tasks/decisions/rules/plan/lessons).
574
+ - handoff 통합 헤드라인에 **🧠 mem T/D/R/P/L 카운트** 추가 — 5종 메모리 영구화 상태 한눈에 확인.
575
+ - \`leerness memory status [--json]\` + MCP **32 도구** (\`leerness_memory_status\`) — 상세 상태 + 최근 항목 조회.
576
+ - \`leerness handoff --json\` 응답에 **\`memorySurface\` 필드 통합** — 단일 호출로 컨텍스트 + 5종 메모리 상태 동시 회수.
577
+ - \`leerness brainstorm\` 회수 범위에 **lessons.md + plan.md** milestone 추가 — Memory Surface 5종 완전 통합.
578
+ - \`leerness lesson list [--tag] [--json]\` + MCP **33 도구** (\`leerness_lesson_list\`) — lessons.md 전용 조회 + tag 필터.
579
+ - \`leerness decision list [--json]\` + MCP **34 도구** (\`leerness_decision_list\`) — decisions.md 전체 조회 (Decision/Reason/Alternatives/Impact 메타).
580
+ - \`leerness plan list [--json]\` + MCP **35 도구** (\`leerness_plan_list\`) — plan.md milestone 전체 (Status/Progress/Tasks). **Memory Surface READ 5종 완전 완성**.
581
+ - handoff 6번째 자동 회수 \`🆕 최근 24h 메모리 변동\` — 5종 surface 의 24h 내 추가 항목 자동 노출.
582
+ - \`session close --json\` 응답에도 \`memorySurface\` 필드 통합 — 마감 시 5종 메모리 상태 동시 회수.
583
+ - \`health --json\` 응답에도 \`memorySurface\` 필드 통합 — handoff/session close/memory status 모든 JSON 명령 일관성.
584
+ - \`leerness lesson drop <target>\` + MCP **36 도구** (\`leerness_lesson_drop\`) — 잘못 저장한 lesson 제거 (archive 자동 보존).
585
+ - \`leerness decision drop <target>\` + MCP **37 도구** (\`leerness_decision_drop\`) — 잘못 저장한 결정 제거 (archive 보존).
586
+ - \`leerness plan remove <M-XXXX|title>\` + MCP **38 도구** (\`leerness_plan_remove\`) — milestone 영구 제거 (archive 보존). **Memory Surface DELETE 5종 완전 완성** 🎉.
587
+ - \`leerness memory archive list [--surface decisions|lessons|plan] [--json]\` + MCP **39 도구** (\`leerness_memory_archive_list\`) — DELETE 5종 archive 통합 조회 (복원 후보 회수).
588
+ - \`leerness memory restore <surface> <target>\` + MCP **40 도구 🎉** (\`leerness_memory_restore\`) — archive → active 복귀 (DELETE→RESTORE cycle 완성). **MCP 40 도구 마일스톤**.
589
+ - handoff **7번째 자동 회수** — \`🗑 최근 24h archive\` (D/L/P 카운트 + 복원 후보 안내). DELETE 활동 자동 인지.
590
+ - 🎉 **60 라운드 자율 모드 마일스톤** — JSON 4종 (handoff/memory status/session close/health) \`memorySurface.archive\` 필드 통합. MCP 40 / handoff auto-recovery 7 / DELETE-RESTORE cycle 완성.
591
+ - \`brainstorm\` 회수 범위에 3 archive 파일 (decisions/lessons/plan archive) 통합 — 과거 제거된 ideas 가 새 brainstorm 시 다시 후보로 노출. \`hits.archive\` 필드 + 복원 안내 라인.
592
+ - \`session close\` 텍스트 모드에 archive 누적 라인 추가 — 마감 시점 DELETE 활동 가시화 (handoff 7번째 회수와 symmetric). archive 가시성 6 surface 완성.
593
+ - \`brainstorm\` 텍스트 모드 lessonsExplicit / planMilestones display 추가 — 데이터 수집은 됐지만 display 누락돼 있던 gap 보완 fix.
594
+ - \`leerness task list --json\` + MCP **41 도구** (\`leerness_task_list\`) — progress-tracker.md task 전체 JSON 조회 + \`--status\` 필터. Task surface CRUD MCP 완전 완성 (add/list/update/drop).
595
+ - MCP **42 도구** (\`leerness_rule_remove\`) — rules.md 에서 특정 rule 제거 + archive 보존. **5 surface CRUD MCP 완전 완성** (task/decision/lesson/plan/rule 모두 add/list/delete MCP 노출).
596
+ - MCP \`leerness_drift_check\` JSON 응답 fix — \`--json\` 플래그 자동 추가하여 외부 AI가 구조화된 drift 신호 회수 (score, level, signals[], healthy).
597
+ - \`.harness/session-workflow.md\` 템플릿에 **🧠 Memory CRUD Quick Reference** 섹션 추가 — 5 surface × CRUD 매트릭스 + archive cycle 워크플로 가이드. 신규 \`init\` 워크스페이스 즉시 적용.
598
+ - \`leerness memory archive list --query <keyword>\` + MCP \`leerness_memory_archive_list\` query 인자 — archive 항목 키워드 case-insensitive 검색 (target/originalHeader 매칭).
599
+ - \`leerness lesson list --query\` + \`leerness decision list --query\` + MCP 동일 인자 — active Memory 항목 키워드 검색 (lesson: text/tag, decision: title/decision/reason/alternatives/impact).
600
600
 
601
601
  ---
602
602
 
@@ -606,11 +606,11 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
606
606
  - [ ] plan/progress-tracker에 이번 라운드 task 등록됨 (또는 task sync)
607
607
  - [ ] 모든 done 항목에 evidence 첨부됨 (verify-claim PASS)
608
608
  - [ ] sub-agent 사용 시 contract verify PASS
609
- - [ ] drift 점수 ≤ 30 (attention 이하) — \`leerness drift check\` (1.9.78: 5신호 + 보안)
609
+ - [ ] drift 점수 ≤ 30 (attention 이하) — \`leerness drift check\` (5신호 + 보안)
610
610
  - [ ] session close 호출됨
611
- - [ ] (1.9.85+) \`leerness health\`로 종합 점검 — drift + 보안 + skill + MCP + tasks
612
- - [ ] (1.9.75/76+) \`.env\` 사용 중이면 \`.gitignore\` 시크릿 패턴 OK + \`.env.example\` 동기화
613
- - [ ] (1.9.80+) 보안 critical 시 \`LEERNESS_AUTO_SECURITY_FIX=1\` 또는 \`audit --fix\`로 자동 회복
611
+ - [ ] \`leerness health\`로 종합 점검 — drift + 보안 + skill + MCP + tasks
612
+ - [ ] \`.env\` 사용 중이면 \`.gitignore\` 시크릿 패턴 OK + \`.env.example\` 동기화
613
+ - [ ] 보안 critical 시 \`LEERNESS_AUTO_SECURITY_FIX=1\` 또는 \`audit --fix\`로 자동 회복
614
614
 
615
615
  ## Anti-pattern (drift 신호)
616
616
 
@@ -620,7 +620,7 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
620
620
  - ⚠ "테스트 돌렸으니 PASS" 자기 보고만 → verify-claim --run-tests 미실행
621
621
  - ⚠ contract verify 생략 → 사양 불일치 BUG가 사용자에게 노출
622
622
  `),
623
- '.harness/anti-lazy-work-policy.md': fm('anti-lazy-work-policy', ['완료 선언 전'], ['게으른 작업 방지 기준 변경'], `# Anti Lazy Work Policy\n\n## Rules\n1. **증거 없는 완료 금지**: \"완료\"를 선언하려면 progress-tracker의 evidence 컬럼에 명령 출력/테스트 결과/스크린샷 경로 등이 있어야 합니다.\n2. **빈 핸드오프 금지**: 세션 종료 시 session-handoff.md의 Completed/In Progress/Next Exact Step이 모두 비어 있으면 close가 \"insufficient\" 상태로 표시됩니다.\n3. **부분 구현 자기보고**: 완전 구현이 아니면 status를 \`incomplete\`로, Next Exact Step에 \"무엇을 추가해야 끝나는지\" 한 줄을 적습니다.\n4. **검증 기록**: typecheck/lint/test 결과를 review-evidence.md에 누적 기록합니다.\n5. **TODO 표지**: 코드에 \`TODO\`/\`FIXME\`/\`XXX\`를 새로 도입하면 progress-tracker에 동일 ID로 추적합니다.\n6. **거짓 완료 자동 감지**: \`leerness lazy detect\`는 다음을 자동 점검합니다.\n - progress-tracker에 done인데 evidence가 비어있는 row\n - session-handoff의 Completed가 비어있고 Next Exact Step도 비어있음\n - 코드에 새 TODO/FIXME 추가 + progress-tracker에 추적 항목 없음\n - test 명령 실행 흔적 없음 (review-evidence.md 또는 task-log.md에 명령 기록)\n7. **품질 렌즈 자가질문 (1.18.3)**: 완료 선언 전 \`leerness lens\`의 분야별 질문에 스스로 답합니다 — 코드: "선임 개발자가 이 코드를 보고 복잡하다고 느끼지 않을까?" / 디자인: "선임 디자이너와 일반 사용자가 봤을 때 이쁘고 편하고 직관적인가?". "그렇다(통과)"라고 답할 수 없으면 완료가 아닙니다. 분야를 바꾸면 인과관계로 연결된 분야(\`lens\` 출력의 ↔ 인과)의 질문도 다시 확인합니다.\n`),
623
+ '.harness/anti-lazy-work-policy.md': fm('anti-lazy-work-policy', ['완료 선언 전'], ['게으른 작업 방지 기준 변경'], `# Anti Lazy Work Policy\n\n## Rules\n1. **증거 없는 완료 금지**: \"완료\"를 선언하려면 progress-tracker의 evidence 컬럼에 명령 출력/테스트 결과/스크린샷 경로 등이 있어야 합니다.\n2. **빈 핸드오프 금지**: 세션 종료 시 session-handoff.md의 Completed/In Progress/Next Exact Step이 모두 비어 있으면 close가 \"insufficient\" 상태로 표시됩니다.\n3. **부분 구현 자기보고**: 완전 구현이 아니면 status를 \`incomplete\`로, Next Exact Step에 \"무엇을 추가해야 끝나는지\" 한 줄을 적습니다.\n4. **검증 기록**: typecheck/lint/test 결과를 review-evidence.md에 누적 기록합니다.\n5. **TODO 표지**: 코드에 \`TODO\`/\`FIXME\`/\`XXX\`를 새로 도입하면 progress-tracker에 동일 ID로 추적합니다.\n6. **거짓 완료 자동 감지**: \`leerness lazy detect\`는 다음을 자동 점검합니다.\n - progress-tracker에 done인데 evidence가 비어있는 row\n - session-handoff의 Completed가 비어있고 Next Exact Step도 비어있음\n - 코드에 새 TODO/FIXME 추가 + progress-tracker에 추적 항목 없음\n - test 명령 실행 흔적 없음 (review-evidence.md 또는 task-log.md에 명령 기록)\n7. **품질 렌즈 자가질문**: 완료 선언 전 \`leerness lens\`의 분야별 질문에 스스로 답합니다 — 코드: "선임 개발자가 이 코드를 보고 복잡하다고 느끼지 않을까?" / 디자인: "선임 디자이너와 일반 사용자가 봤을 때 이쁘고 편하고 직관적인가?". "그렇다(통과)"라고 답할 수 없으면 완료가 아닙니다. 분야를 바꾸면 인과관계로 연결된 분야(\`lens\` 출력의 ↔ 인과)의 질문도 다시 확인합니다.\n`),
624
624
  '.harness/rules.md': _rulesHeader() + '\n',
625
625
  '.harness/session-handoff.md': fm('session-handoff', ['세션 시작','다음 작업 이어받기'], ['세션 종료'], `# Session Handoff\n\nLast generated: (자동)\n\n## Completed\n-\n\n## In Progress\n-\n\n## Incomplete / Waiting / On Hold / Blocked\n-\n\n## Dropped\n-\n\n## Verification\n-\n\n## Recommended Direction\n-\n\n## Next Exact Step\n-\n`),
626
626
  '.harness/leerness-maintenance.md': fm('leerness-maintenance', ['작업 시작','마이그레이션/릴리즈 전'], ['버전 정책 변경'], `# Leerness Maintenance\n\nAI agents should check:\n\n\`\`\`bash\nleerness --version\nleerness self check .\nleerness update --check # 24h 캐시 자동 감지\nleerness update --yes # 새 버전 발견 시 자동 마이그레이션\ncat .harness/HARNESS_VERSION\nnpm view leerness version\n\`\`\`\n`),
@@ -1188,6 +1188,50 @@ function addSkill(root, name, silent = false) {
1188
1188
 
1189
1189
  // ===== Skill registry (catalog + user-defined merged) =====
1190
1190
  function userSkillsDir(root) { return path.join(absRoot(root), '.harness/skills'); }
1191
+
1192
+ // 1.36.26 (obra/superpowers P2): skill 메타 품질 lint — `leerness skill lint [id] [--all] [--json] [--strict]`.
1193
+ // 대상: 설치 스킬(.harness/skills/<id>/SKILL.md 우선, 없으면 skill.json) + 내장 카탈로그 9종(skill.json 형태로 lint).
1194
+ // 기본 exit 0, ERROR 있을 때만 exit 1. --strict 는 WARNING 도 승격. INFO 는 절대 실패 아님(false-BLOCK 방지 설계).
1195
+ function skillLintCmd(root, id) {
1196
+ root = absRoot(root);
1197
+ const jsonMode = has('--json');
1198
+ const strict = has('--strict');
1199
+ const targets = []; // { id, source, meta, body }
1200
+ const seen = new Set();
1201
+ const dir = userSkillsDir(root);
1202
+ const wantAll = !id || has('--all');
1203
+ const pushInstalled = (sid) => {
1204
+ const md = path.join(dir, sid, 'SKILL.md');
1205
+ const sj = path.join(dir, sid, 'skill.json');
1206
+ if (exists(md)) { const p = _parseSkillMd(read(md)); targets.push({ id: sid, source: 'SKILL.md', meta: p.meta, body: p.body }); seen.add(sid); return true; }
1207
+ if (exists(sj)) { try { const j = JSON.parse(read(sj)); targets.push({ id: sid, source: 'skill.json', meta: { name: j.name || sid, description: j.description || '' }, body: '' }); seen.add(sid); return true; } catch {} }
1208
+ return false;
1209
+ };
1210
+ if (wantAll) {
1211
+ if (exists(dir)) for (const e of fs.readdirSync(dir, { withFileTypes: true })) if (e.isDirectory()) pushInstalled(e.name);
1212
+ for (const bid of Object.keys(BUILTIN_CATALOG)) if (!seen.has(bid)) { const d = BUILTIN_CATALOG[bid]; targets.push({ id: bid, source: 'builtin', meta: { name: bid, description: d.description || '' }, body: '' }); }
1213
+ } else {
1214
+ if (!pushInstalled(id)) {
1215
+ if (BUILTIN_CATALOG[id]) { const d = BUILTIN_CATALOG[id]; targets.push({ id, source: 'builtin', meta: { name: id, description: d.description || '' }, body: '' }); }
1216
+ else { failJson(jsonMode, 'skill_not_found', `스킬 없음: ${id} (설치 스킬 또는 내장 카탈로그에 없음)`); return; }
1217
+ }
1218
+ }
1219
+ const results = targets.map(t => ({ id: t.id, source: t.source, ...(_lintSkillMeta(t.meta, t.body)) }));
1220
+ const errN = results.reduce((s, r) => s + r.errors.length, 0);
1221
+ const warnN = results.reduce((s, r) => s + r.warnings.length, 0);
1222
+ const failed = errN > 0 || (strict && warnN > 0);
1223
+ if (failed) process.exitCode = 1;
1224
+ if (jsonMode) { log(JSON.stringify({ version: VERSION, total: results.length, errors: errN, warnings: warnN, strict, ok: !failed, results }, null, 2)); return; }
1225
+ log(`# leerness skill lint — ${results.length}개 (ERROR ${errN} · WARNING ${warnN}${strict ? ' · --strict' : ''})`);
1226
+ for (const r of results) {
1227
+ const flat = [...r.errors.map(x => ['E', x]), ...r.warnings.map(x => ['W', x]), ...r.infos.map(x => ['i', x])];
1228
+ if (!flat.length) { ok(`${r.id} (${r.source}) — clean`); continue; }
1229
+ log(`\n## ${r.id} (${r.source})`);
1230
+ for (const [lv, x] of flat) log(` ${lv === 'E' ? '✗' : lv === 'W' ? '⚠' : 'ⓘ'} [${x.code}] ${x.msg}`);
1231
+ }
1232
+ log('');
1233
+ log(failed ? `✗ lint 실패 (ERROR ${errN}${strict ? ` + strict WARNING ${warnN}` : ''})` : `✓ lint 통과 (ERROR 0${warnN ? ` · WARNING ${warnN}건은 exit 에 미반영 — --strict 로 승격` : ''})`);
1234
+ }
1191
1235
  function userSkillFile(root, id) { return path.join(userSkillsDir(root), id, 'skill.json'); }
1192
1236
 
1193
1237
  function loadUserSkill(root, id) {
@@ -2983,6 +3027,41 @@ function _selfTestCases() {
2983
3027
  const encOk = s.includes("(result.applied || []).some(a => a.action === 'failed')) process.exitCode = 1"); // encoding: 실패 exit1
2984
3028
  return reuseOk && releaseOk && encOk;
2985
3029
  } },
3030
+ { name: 'skill lint (1.36.26, obra/superpowers P1/P2): 2티어 severity(ERROR 만 exit1) + 한국어 트리거절 + CJK 본문예산 분기 — 순수 행위검사', run: () => {
3031
+ const p = require('../lib/pure-utils');
3032
+ if (typeof p._lintSkillMeta !== 'function') return false;
3033
+ // ERROR: name/description 기계 판정
3034
+ const e1 = p._lintSkillMeta({ name: 'bad-one' }, 'b');
3035
+ const errMissing = e1.errors.some(x => x.code === 'description_missing');
3036
+ const errCharset = p._lintSkillMeta({ name: 'Bad_Name!', description: 'x'.repeat(50) }, 'b').errors.some(x => x.code === 'name_charset');
3037
+ // clean 케이스: 트리거 서술(ko) → ERROR/WARNING/no_trigger 전부 0 (false-BLOCK 방지 핵심)
3038
+ const clean = p._lintSkillMeta({ name: 'ok-skill', description: '문서 산출물이 필요할 때 사용 — Word 파일을 만들거나 편집할 때 로드하는 스킬. Use when producing documents.' }, '본문');
3039
+ const cleanOk = clean.errors.length === 0 && clean.warnings.length === 0 && !clean.infos.some(i => i.code === 'no_trigger_clause');
3040
+ // WARNING 은 exit 판정과 분리(짧은 설명) + CJK lang 분기
3041
+ const w1 = p._lintSkillMeta({ name: 'w', description: '짧은 설명' }, 'b');
3042
+ const warnShort = w1.errors.length === 0 && w1.warnings.some(x => x.code === 'description_too_short');
3043
+ const cjkLang = p._lintSkillMeta({ name: 'a', description: 'x'.repeat(50) }, '한글본문 '.repeat(50)).lang === 'cjk';
3044
+ // 내장 카탈로그 9종이 lint clean(1.36.25 데이터 선행 조건 검증 — P0 없이 lint 먼저면 9/9 실패였음)
3045
+ const c = require('../lib/catalogs');
3046
+ const builtinClean = Object.keys(c.BUILTIN_CATALOG).every(k => p._lintSkillMeta({ name: k, description: c.BUILTIN_CATALOG[k].description || '' }, '').errors.length === 0);
3047
+ // CLI 배선
3048
+ const wired = typeof skillLintCmd === 'function' && read(__filename).includes("args[1] === 'lint'");
3049
+ return errMissing && errCharset && cleanOk && warnShort && cjkLang && builtinClean && wired;
3050
+ } },
3051
+ { name: 'skill 트리거 description (1.36.25, obra/superpowers P0): BUILTIN_CATALOG 9/9 description(트리거 서술) + export/publish 합성 우선순위 description 우선 — 행위검사', run: () => {
3052
+ const c = require('../lib/catalogs');
3053
+ const ids = Object.keys(c.BUILTIN_CATALOG);
3054
+ // 9/9 description 존재 + 트리거 형태(ko "사용" + en "Use when") + 200자 슬라이스 생존 + displayNameKo 반복 아님
3055
+ const allDesc = ids.every(k => {
3056
+ const e = c.BUILTIN_CATALOG[k]; const d = e.description || '';
3057
+ return d.length >= 40 && d.length <= 200 && /사용/.test(d) && /Use when/i.test(d) && d !== e.displayNameKo;
3058
+ });
3059
+ // 합성 우선순위: description 이 displayNameKo 보다 먼저 (export 1 + publish 계열 2 = 총 3사이트)
3060
+ const s = read(__filename);
3061
+ const flipped = (s.match(/data\.description \|\| data\.displayNameKo/g) || []).length >= 3
3062
+ && !s.includes('data.displayNameKo || ' + 'data.description'); // split-literal: 이 줄 자신이 매치되는 자기참조 트랩 회피
3063
+ return allDesc && flipped;
3064
+ } },
2986
3065
  { name: 'requests positional 경로 (1.36.24, UR-0027 확장): requests 계열도 task/rule 과 동일하게 positional 경로 인식 — 다른 프로젝트 cwd 오기록 차단 — 소스가드+행위', run: () => {
2987
3066
  const s = read(__filename);
2988
3067
  const wired = s.includes("requestsCmd(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd()");
@@ -7622,7 +7701,7 @@ function skillExportCmd(root, id) {
7622
7701
  if (!id) { fail('사용법: leerness skill export <id>'); return process.exit(1); }
7623
7702
  const data = loadUserSkill(root, id) || (skillCatalog[id] ? { ...skillCatalog[id], name: id } : null);
7624
7703
  if (!data) { fail(`skill 없음: ${id}`); return process.exit(1); }
7625
- const description = data.displayNameKo || data.description || (data.capabilities && data.capabilities[0]) || id;
7704
+ const description = data.description || data.displayNameKo || (data.capabilities && data.capabilities[0]) || id; // 1.36.25 (P0): 트리거 서술 description 이 있으면 displayNameKo("~스킬")보다 우선 — 종전 순서는 좋은 description 을 무시했다
7626
7705
  const body = `---\nname: ${id}\ndescription: ${description.slice(0, 200)}\n---\n\n# ${data.displayNameKo || id}\n\n## Capabilities\n${(data.capabilities || []).map(c => '- ' + c).join('\n') || '-'}\n\n## Sources\n${(data.sources || []).map(s => '- ' + (s.url || s)).join('\n') || '-'}\n\n## Patterns\n${(data.patterns || []).map(p => `- \`${p.command}\` — ${p.note || ''}`).join('\n') || '-'}\n`;
7627
7706
  const outDir = arg('--out', path.join(root, '.harness', 'skills-export', id));
7628
7707
  mkdirp(outDir);
@@ -16377,7 +16456,7 @@ function skillPublishCmd(root) {
16377
16456
  // 각 skill을 SKILL.md로 export
16378
16457
  for (const id of ids) {
16379
16458
  const data = all[id];
16380
- const description = (data.displayNameKo || data.description || (data.capabilities && data.capabilities[0]) || id).slice(0, 200);
16459
+ const description = (data.description || data.displayNameKo || (data.capabilities && data.capabilities[0]) || id).slice(0, 200); // 1.36.25 (P0): description(트리거 서술) 우선 — 종전 displayNameKo 1순위는 좋은 description 을 무시
16381
16460
  const body = `---\nname: ${id}\ndescription: ${description}\nlicense: MIT\npublisher: leerness\nversion: ${VERSION}\n---\n\n# ${data.displayNameKo || id}\n\n## Capabilities\n${(data.capabilities || []).map(c => '- ' + c).join('\n') || '-'}\n\n## Sources\n${(data.sources || []).map(s => '- ' + (s.url || s)).join('\n') || '-'}\n\n## Usage\n\n\`\`\`bash\nleerness skill install <이 SKILL.md path or URL>\n\`\`\`\n`;
16382
16461
  const skillDir = path.join(exportDir, id);
16383
16462
  mkdirp(skillDir);
@@ -16971,7 +17050,7 @@ function skillExportAllCmd(root) {
16971
17050
  log('');
16972
17051
  for (const id of ids) {
16973
17052
  const data = all[id];
16974
- const description = (data.displayNameKo || data.description || (data.capabilities && data.capabilities[0]) || id).slice(0, 200);
17053
+ const description = (data.description || data.displayNameKo || (data.capabilities && data.capabilities[0]) || id).slice(0, 200); // 1.36.25 (P0): description(트리거 서술) 우선 — 종전 displayNameKo 1순위는 좋은 description 을 무시
16975
17054
  const body = `---\nname: ${id}\ndescription: ${description}\n---\n\n# ${data.displayNameKo || id}\n\n## Capabilities\n${(data.capabilities || []).map(c => '- ' + c).join('\n') || '-'}\n\n## Sources\n${(data.sources || []).map(s => '- ' + (s.url || s)).join('\n') || '-'}\n`;
16976
17055
  const skillDir = path.join(outDir, id);
16977
17056
  mkdirp(skillDir);
@@ -21294,6 +21373,7 @@ async function main() {
21294
21373
  if (cmd === 'readme' && args[1] === 'sync') return readmeCmd(arg('--path', args[2] || process.cwd()));
21295
21374
  if (cmd === 'consistency' && args[1] === 'check') return consistencyCheck(arg('--path', args[2] || process.cwd()));
21296
21375
  if (cmd === 'consistency' && args[1] === 'merge-design-guide') return mergeDesign(arg('--path', args[2] || process.cwd()));
21376
+ if (cmd === 'skill' && args[1] === 'lint') return skillLintCmd(arg('--path', args[2] && !args[2].startsWith('-') && /^([A-Za-z]:[\\/]|\/|\.\.?[\\/])/.test(args[2]) ? args[2] : process.cwd()), args[2] && !args[2].startsWith('-') && !/^([A-Za-z]:[\\/]|\/|\.\.?[\\/])/.test(args[2]) ? args[2] : null); // 1.36.26: 메타 품질 lint
21297
21377
  if (cmd === 'skill' && args[1] === 'list') return skillList(args[2] || arg('--path', process.cwd()));
21298
21378
  if (cmd === 'skill' && args[1] === 'info') return skillInfo(args[2], absRoot(arg('--path', process.cwd())));
21299
21379
  if (cmd === 'skill' && args[1] === 'add') return addSkill(absRoot(arg('--path', process.cwd())), args[2]);
package/lib/catalogs.js CHANGED
@@ -349,16 +349,16 @@ 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: '마이크로소프트 오피스 자동화 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Word/Excel/PowerPoint 문서 자동화', '템플릿 기반 문서 생성', '표/차트/요약 문서화', '민감정보 제외 규칙 적용'] },
353
- 'commerce-api': { displayNameKo: '커머스 API 연동 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['쿠팡·롯데온·스마트스토어 API 연동 설계', '주문/상품/매출 동기화', '환경변수 기반 인증 분리', '레이트리밋/재시도/오류 처리'] },
354
- 'crawling': { displayNameKo: '크롤링·브라우저 자동화 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Playwright 기반 자동화', '다운로드/로그인 세션 처리', '스크린샷 기반 실패 진단', '약관/권한/차단 위험 점검'] },
355
- 'firebase': { displayNameKo: 'Firebase·Cloud Functions 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Firebase Functions 배포 구조', '환경변수/시크릿 분리', '권한/IAM 점검', '로컬 에뮬레이터 검증'] },
356
- 'ads-analytics': { displayNameKo: '광고·GA4 분석 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['GA4 이벤트/전환 점검', '광고 데이터 수집 구조화', '소스/매체 분석', '리포트 자동화'] },
357
- 'appstore-review': { displayNameKo: '앱스토어 심사 대응 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['심사 문구 분석', '개인정보 라벨 점검', '리젝 대응 초안', '웹뷰/앱 데이터 수집 구분'] },
358
- 'ai-verified-skill-publisher': { displayNameKo: 'AI 검증 스킬 업로드·라이브러리화 스킬', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['검증된 스킬 정규화', '민감정보 스캔', 'AI 검증 메타데이터 작성', 'npm/git 업로드 dry-run 및 실행 게이트'] },
359
- 'feature-implementation': { displayNameKo: '기능 구현 표준 스킬', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['feature-contracts 작성', '재사용 우선 검사', '테스트 증거 수집', '핸드오프 트리거'] },
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 작성', '재사용 우선 검사', '테스트 증거 수집', '핸드오프 트리거'] },
360
360
  // 1.9.11: 기본 내장 — 로드맵 자동 생성 스킬
361
- 'project-roadmap-generator': { displayNameKo: '프로젝트 로드맵 자동 생성 스킬', 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)'] }
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)'] }
362
362
  };
363
363
 
364
364
  // 1.9.342 (UR-0025 심층): roadmap.html 상태 라벨/색상 맵 (status → ko 라벨 / hex 색상) — harness 에서 분리.
package/lib/pure-utils.js CHANGED
@@ -461,6 +461,42 @@ function _parseSkillMd(text) {
461
461
  return { meta, body: m[2] };
462
462
  }
463
463
 
464
+ // 1.36.26 (obra/superpowers P1): SKILL 메타 품질 lint — 순수함수(의존 0), _parseSkillMd 산출(meta, body) 을 받는다.
465
+ // 설계 원칙(메모리 교훈 — 게이트 휴리스틱은 false-PASS 로 편향, false-BLOCK 금지):
466
+ // · ERROR = 기계적 판정만(FP 0 이 확실한 것) — 이것만 exit 1 대상.
467
+ // · WARNING = 형태 검사(저FP — displayNameKo 합성 흔적 등). 기본 exit 0, --strict 시에만 승격.
468
+ // · INFO = 주관적 권고(트리거절 부재 등) — 절대 실패 아님.
469
+ // 한국어 적응: 트리거절은 ko("~할 때 사용"/"언제 사용")·en("Use when/for") 모두 인정. 3인칭 검사는 한국어(인칭 무표지)에
470
+ // 부적용이라 제외. 본문 예산은 CJK 분기 — 공백분리 단어수를 한국어에 쓰면 어절 카운트라 전면 오발(비CJK=단어수, CJK=char).
471
+ function _lintSkillMeta(meta, body, opts = {}) {
472
+ const errors = [], warnings = [], infos = [];
473
+ const m = meta || {};
474
+ const b = String(body || '');
475
+ const name = String(m.name || '').trim();
476
+ const desc = String(m.description || '').trim();
477
+ // ERROR — 기계적
478
+ if (!name) errors.push({ code: 'name_missing', msg: 'frontmatter name 필수' });
479
+ else if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) errors.push({ code: 'name_charset', msg: `name 은 소문자/숫자/하이픈만: "${name}"` });
480
+ if (!desc) errors.push({ code: 'description_missing', msg: 'frontmatter description 필수 — 에이전트가 "지금 로드할까"를 판단하는 유일한 신호' });
481
+ else if (desc.length > 1024) errors.push({ code: 'description_too_long', msg: `description ${desc.length}자 > 1024 (트리거 조건만 담을 것)` });
482
+ // WARNING — 저FP 형태
483
+ if (desc && name && desc.toLowerCase() === name.toLowerCase()) warnings.push({ code: 'description_repeats_name', msg: 'description 이 name 반복 — 트리거 조건이 없음' });
484
+ if (desc && desc.length < 40) warnings.push({ code: 'description_too_short', msg: `description ${desc.length}자 < 40 — 표시명 합성 흔적("~스킬" 류)일 가능성` });
485
+ if (desc && /^[^,]+(, [^,]+){2,}$/.test(desc) && !/(사용|때|Use when|use this)/i.test(desc)) warnings.push({ code: 'description_capability_list', msg: 'description 이 capabilities 나열 형태 — 트리거 조건으로 재작성 권장' });
486
+ // INFO — 주관적(절대 실패 아님)
487
+ const hasTrigger = /Use (when|this when|for)\b/i.test(desc) || /(할|일|하는|이런)\s*(때|경우)에?\s*사용|언제\s*사용|때\s*사용/.test(desc);
488
+ if (desc && !hasTrigger) infos.push({ code: 'no_trigger_clause', msg: '트리거절 없음 — "~할 때 사용" / "Use when …" 형태 권장' });
489
+ // 본문 예산 — CJK 분기 (Hangul/한자/가나 비율 30%+ 면 CJK 로 판단; 문자 기반)
490
+ const cjkChars = (b.match(/[ㄱ-힝一-鿿぀-ヿ]/g) || []).length;
491
+ const totalChars = b.replace(/\s/g, '').length;
492
+ const isCjk = totalChars > 0 && cjkChars / totalChars >= 0.3;
493
+ const wordCount = b.split(/\s+/).filter(Boolean).length;
494
+ const charCount = b.length;
495
+ if (isCjk) { if (charCount > 12000) infos.push({ code: 'body_budget', msg: `본문 ${charCount}자 (CJK 기준 12000자 초과) — 자주 로드되는 스킬이면 분할 권장` }); }
496
+ else if (wordCount > 5000) infos.push({ code: 'body_budget', msg: `본문 ${wordCount}단어 (5000 초과) — 분할 권장` });
497
+ return { errors, warnings, infos, wordCount, charCount, lang: isCjk ? 'cjk' : 'latin' };
498
+ }
499
+
464
500
  // 1.9.333 (UR-0025 심층): 순수 플랫폼 제약 매칭 — catalog + 텍스트 → 매칭 플랫폼/제약/제안 (fs 의존 0, catalog 주입).
465
501
  // 1.31.2 (UR-0010): optional lang ('en') → 영어 suggestion. 기본 'ko' (무회귀, selftest 2-arg 호출 보존).
466
502
  function _matchConstraints(catalog, text, lang) {
@@ -1105,7 +1141,7 @@ module.exports = {
1105
1141
  // 1.9.346 (UR-0025 심층): roadmap CSS 변수 빌더
1106
1142
  _roadmapTokenStyles,
1107
1143
  // 1.9.347 (UR-0025 심층): SKILL.md frontmatter 파서 (BOM-aware)
1108
- _parseSkillMd,
1144
+ _parseSkillMd, _lintSkillMeta,
1109
1145
  // 1.9.333 (UR-0025 심층): 순수 플랫폼 제약 매칭
1110
1146
  _matchConstraints,
1111
1147
  // 1.9.333 패턴 적용: 순수 도메인 매칭
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.24",
3
+ "version": "1.36.26",
4
4
  "description": "The AI-coding operations layer that makes \"done\" require evidence — persistent memory, evidence-gated completion checks, and clean handoffs for any AI agent (Claude Code, Codex, Cursor). State lives as plain files in your repo. CLI + MCP, 0 runtime dependencies.",
5
5
  "keywords": [
6
6
  "leerness",