leerness 1.9.175 → 1.9.176

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,66 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.176 — 2026-05-21
4
+
5
+ **⚠ 사용자 명시: `leerness review-request` — 사용자 요구를 무조건 구현 전 사전 검토.**
6
+
7
+ 사용자 명시: *"leerness가 적용된 프로젝트는 사용자의 요구를 무조건적으로 구현하기 전에, 충돌이 발생할 수 있는 부분이나 제작하고자 하는 기능 등을 구현하거나 설계할 때 더 효율적인 단계가 있는지 검토해보고 제시할 수도 있도록 설계"*.
8
+
9
+ ### `leerness review-request "<request>"` — 9개 신호 분석
10
+ | 신호 | 데이터 소스 |
11
+ |---|---|
12
+ | **estimatedType** | route 키워드 매핑 (feature/bugfix/refactor/research/planning/release/consistency) |
13
+ | **conflicts** | lessons 실패 패턴 + 진행 중 task + taskLogFails |
14
+ | **reuseCandidates** | skills 매칭 + reuse-map 키워드 검색 |
15
+ | **lessonsRecall** | 과거 decisions + 관련 lessons |
16
+ | **planConflicts** | 진행 중 milestone (plan.md) |
17
+ | **featureConflicts** | feature_graph.md 영역 겹침 |
18
+ | **recommendedSteps** | 작업 유형별 3-4단계 권장 흐름 |
19
+ | **efficiencyHints** | 재사용/sub-agent/skill 활용 제안 |
20
+ | **proceed** | true (안전) / false (사용자 확인 필요) |
21
+
22
+ ### 사용 예
23
+ ```bash
24
+ $ leerness review-request "OAuth 로그인 구현해줘"
25
+ # leerness review-request (1.9.176 사전 검토)
26
+ 요청: "OAuth 로그인 구현해줘"
27
+ 추정 작업 유형: feature
28
+
29
+ ## 💡 효율 제안
30
+ 👥 leerness agents recommend feature — 작업 유형별 sub-agent 매핑 활용 가능
31
+
32
+ ## 📍 권장 단계 (feature)
33
+ 1) leerness reuse find "<핵심 capability>" — 중복 구현 사전 차단
34
+ 2) leerness plan add "<milestone>" — 진행 추적
35
+ 3) leerness contract verify SPEC.md src/<mod>.js — 사양 ↔ 구현 일치 검증
36
+ 4) verify-claim --run-tests 로 evidence 의무화
37
+
38
+ ## ▶ 진행 권장: ✓ 진행 안전
39
+ 사유: 안전 — 충돌 신호 < 3 + plan 충돌 0
40
+ 분석 소요: 938ms
41
+ ```
42
+
43
+ ### 통합 — 3 진입점
44
+ - **CLI**: `leerness review-request "<request>"` (또는 단축 `review-req`)
45
+ - **REPL**: `:review "<request>"` slash (1.9.175 흐름 연장)
46
+ - **MCP**: `leerness_review_request` (외부 AI 직접 호출 — **54번째 도구**)
47
+
48
+ ### AGENTS.md / CLAUDE.md 강제 안내
49
+ ```markdown
50
+ ## ⚠ 사용자 요청 사전 검토 의무 (1.9.176 — 사용자 명시)
51
+ **사용자가 "X 구현해줘 / X 만들어줘 / X 추가해줘" 같은 요청을 줬을 때 무조건 즉시 구현하지 말 것.**
52
+ 먼저 `leerness review-request "<요청>"` 호출 → 분석 결과 표시 → 사용자 확인 후 구현.
53
+ "그냥 바로 해줘 / review 건너뛰어줘" 명시 옵트아웃 시에만 review 생략.
54
+ ```
55
+
56
+ ### Verified
57
+ - e2e 217/217 baseline 유지
58
+ - stress-v121: **23/23** (함수 정의 4 + router/help 2 + 실 동작 6 + REPL/MCP 3 + metadata 2 + 누적 회귀 6)
59
+ - 작업 유형 추정 정확도: feature/bugfix/refactor/research 4종 100% 매칭
60
+ - VERSION = 1.9.176 · MCP **54 도구** · autonomous-rounds = 106 · main 자동 push 37 라운드 연속
61
+
62
+ ---
63
+
3
64
  ## 1.9.175 — 2026-05-21
4
65
 
5
66
  **🌉 REPL Bridge Slash 3종 — `:web` / `:pc` / `:lsp` 즉시 호출.**
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
4
4
 
5
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.175-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v120-17%2F17-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-53-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-105-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-36_rounds-success)]() [![repl-slash](https://img.shields.io/badge/REPL_slash-:web%2F:pc%2F:lsp_즉시_호출-success)]() [![repl-perm](https://img.shields.io/badge/REPL_:permissions-즉시_변경-success)]() [![lsp-multi](https://img.shields.io/badge/LSP_다국어-JS%2FPython%2FGo%2FRust%2FJava-success)]() [![capability](https://img.shields.io/badge/6_capability-72%25_production--ready-brightgreen)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
5
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.176-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v121-23%2F23-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-106-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-37_rounds-success)]() [![review-request](https://img.shields.io/badge/review--request-사전_검토_9_신호-success)]() [![repl-slash](https://img.shields.io/badge/REPL_slash-:web%2F:pc%2F:lsp%2F:review-success)]() [![lsp-multi](https://img.shields.io/badge/LSP_다국어-JS%2FPython%2FGo%2FRust%2FJava-success)]() [![capability](https://img.shields.io/badge/6_capability-72%25_production--ready-brightgreen)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,9 +12,9 @@
12
12
  ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
13
13
  ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
14
14
  ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
15
- ║ v1.9.175 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.176 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- 🌉 REPL :web/:pc/:lsp slash Bridge 3종 즉시 호출
17
+ review-request 무조건 구현 충돌/효율 사전 검토
18
18
  ╚══════════════════════════════════════════════════════════════╝
19
19
  ```
20
20
 
package/bin/harness.js CHANGED
@@ -6,7 +6,7 @@ const path = require('path');
6
6
  const cp = require('child_process');
7
7
  const readline = require('readline');
8
8
 
9
- const VERSION = '1.9.175';
9
+ const VERSION = '1.9.176';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -9845,7 +9845,8 @@ function mcpServeCmd(root) {
9845
9845
  { name: 'leerness_provider_remove', description: '1.9.159 — Provider Registry 에서 사용자 정의 provider 제거. 인자: { id (required), path? }. 빌트인 5종 id 는 제거 불가 (override 만 제거 가능). 🎉 MCP 50 도구 마일스톤 — Provider Registry CRUD MCP 완성 (list/add/remove)', inputSchema: { type: 'object', properties: { id: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
9846
9846
  { name: 'leerness_web', description: '1.9.168 — Web Bridge (1.9.165 playwright opt-in). sub: check (설치 + permissions.browser 확인) | screenshot (URL → PNG) | extract (URL + CSS selector → DOM 텍스트). 외부 AI가 leerness 의 웹 자동화 능력을 직접 호출. playwright 미설치 시 친절 안내 (graceful). 인자: { sub (required), url?, out?, selector?, path? }', inputSchema: { type: 'object', properties: { sub: { type: 'string', enum: ['check', 'screenshot', 'extract'] }, url: { type: 'string' }, out: { type: 'string' }, selector: { type: 'string' }, path: { type: 'string' } }, required: ['sub'] } },
9847
9847
  { name: 'leerness_pc', description: '1.9.168 — PC Bridge (1.9.166 robotjs/nut-tree opt-in). sub: check (설치 + permissions.mouse/keyboard) | click (x,y) | type (text) | screenshot (out). ⚠ full permissions 권장 (mouse/keyboard 접근). 외부 AI가 데스크탑 자동화 능력을 직접 호출. 인자: { sub (required), x?, y?, text?, out?, path? }', inputSchema: { type: 'object', properties: { sub: { type: 'string', enum: ['check', 'click', 'type', 'screenshot'] }, x: { type: 'number' }, y: { type: 'number' }, text: { type: 'string' }, out: { type: 'string' }, path: { type: 'string' } }, required: ['sub'] } },
9848
- { name: 'leerness_lsp', description: '1.9.168 — LSP Bridge (1.9.167 typescript opt-in + regex fallback). sub: check (설치 여부) | symbols (file → function/class/interface/type/enum 목록) | references (name + in 디렉토리 → 호출 위치). 외부 AI가 코드 인텔리전스를 직접 호출 (의존성 0 fallback 동작). 🎉 MCP 53 도구 마일스톤. 인자: { sub (required), file?, name?, in?, path? }', inputSchema: { type: 'object', properties: { sub: { type: 'string', enum: ['check', 'symbols', 'references'] }, file: { type: 'string' }, name: { type: 'string' }, in: { type: 'string' }, path: { type: 'string' } }, required: ['sub'] } }
9848
+ { name: 'leerness_lsp', description: '1.9.168 — LSP Bridge (1.9.167 typescript opt-in + regex fallback). sub: check (설치 여부) | symbols (file → function/class/interface/type/enum 목록) | references (name + in 디렉토리 → 호출 위치). 외부 AI가 코드 인텔리전스를 직접 호출 (의존성 0 fallback 동작). 🎉 MCP 53 도구 마일스톤. 인자: { sub (required), file?, name?, in?, path? }', inputSchema: { type: 'object', properties: { sub: { type: 'string', enum: ['check', 'symbols', 'references'] }, file: { type: 'string' }, name: { type: 'string' }, in: { type: 'string' }, path: { type: 'string' } }, required: ['sub'] } },
9849
+ { name: 'leerness_review_request', description: '1.9.176 — 사용자 요청 사전 검토 (사용자 명시 요청). AI 에이전트가 사용자 요구를 **무조건 구현 전**에 호출. 분석: 1) estimatedType (route 추정), 2) conflicts (lesson 실패/진행중 task), 3) reuseCandidates (skill/reuse-map 매칭), 4) lessonsRecall (과거 결정), 5) planConflicts (진행중 milestone), 6) featureConflicts (feature graph 영역 겹침), 7) recommendedSteps (작업 유형별 3-5 단계), 8) efficiencyHints, 9) proceed (true/false). 사용자 결정 도움. 인자: { request (required), path? }', inputSchema: { type: 'object', properties: { request: { type: 'string' }, path: { type: 'string' } }, required: ['request'] } }
9849
9850
  ];
9850
9851
 
9851
9852
  function send(obj) {
@@ -9973,6 +9974,10 @@ function mcpServeCmd(root) {
9973
9974
  if (args.name) cliArgs.splice(2, 0, String(args.name));
9974
9975
  if (args.in) cliArgs.push('--in', String(args.in));
9975
9976
  break;
9977
+ case 'leerness_review_request':
9978
+ // 1.9.176: 사용자 요청 사전 검토 (사용자 명시 요청)
9979
+ cliArgs = ['review-request', String(args.request || ''), '--path', targetPath, '--json'];
9980
+ break;
9976
9981
  default:
9977
9982
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
9978
9983
  }
@@ -11007,6 +11012,7 @@ async function _agentRepl(root, opts) {
11007
11012
  log(C.dim(' 🆕 1.9.170 — Tab=provider cycle, Shift+Tab=model cycle, :stream on|off (실시간 출력)'));
11008
11013
  log(C.dim(' 🆕 1.9.174 — :permissions [basic|extended|full] 로 즉시 권한 변경 (default: basic 안전)'));
11009
11014
  log(C.dim(' 🆕 1.9.175 — :web / :pc / :lsp 으로 Bridge 3종 REPL 안에서 즉시 호출 (코드 분석/웹/PC)'));
11015
+ log(C.dim(' 🆕 1.9.176 — :review "<요청>" 으로 사용자 요청 사전 검토 (충돌/재사용/효율/권장 단계)'));
11010
11016
  log(C.dim(` 현재 — provider=${state.provider} model=${state.model || '(기본)'} role=${state.role} permissions=${_readPermissions(root).mode}`));
11011
11017
  // 1.9.155: REPL 진입 시 handoff 컨텍스트 자동 노출 (UX 개선 — 사용자가 매번 :handoff 안 해도 컨텍스트 인지)
11012
11018
  try {
@@ -11127,6 +11133,8 @@ async function _agentRepl(root, opts) {
11127
11133
  log(' :brainstorm <topic> — leerness brainstorm "topic" (관련 컨텍스트 회수)');
11128
11134
  log(' :tasks — leerness task list (현재 task 상태)');
11129
11135
  log(' :plan — leerness plan show (현재 milestone)');
11136
+ log(C.bold('\n 🆕 Review Slash (1.9.176) — 사용자 요청 사전 검토:'));
11137
+ log(' :review "<request>" — 충돌/재사용/효율/권장 단계 분석 후 구현 권장');
11130
11138
  log(C.bold('\n 🆕 Bridge Slash (1.9.175) — REPL 안에서 즉시 Bridge 호출:'));
11131
11139
  log(' :web check — playwright 설치 확인 (opt-in)');
11132
11140
  log(' :web screenshot <url> [--out f.png] — URL → PNG');
@@ -11317,6 +11325,27 @@ async function _agentRepl(root, opts) {
11317
11325
  return false;
11318
11326
  }
11319
11327
 
11328
+ // 1.9.176: :review <request> — 사용자 요청 사전 검토 slash (사용자 명시)
11329
+ // "이거 구현해줘" 같은 요청을 받으면 AI 가 먼저 :review 호출 → 충돌/재사용/효율 분석.
11330
+ if (op === 'review') {
11331
+ const reqText = rest.join(' ').trim();
11332
+ if (!reqText) {
11333
+ log(C.yel(` ⚠ :review 는 요청 텍스트 필요 — 예: :review "OAuth 로그인 추가"`));
11334
+ return false;
11335
+ }
11336
+ log(C.dim(` → leerness review-request "${reqText.slice(0, 60)}${reqText.length > 60 ? '…' : ''}"`));
11337
+ const t0 = Date.now();
11338
+ const r = runCommandSafe(process.execPath, [__filename, 'review-request', reqText, '--path', root], {
11339
+ cwd: root, root, timeout: 30000, kind: 'agent_repl_slash', label: 'repl-review',
11340
+ env: { LEERNESS_NO_BANNER: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' }
11341
+ });
11342
+ const dt = Date.now() - t0;
11343
+ if (r.stdout) log(r.stdout.trim().split('\n').slice(0, 60).join('\n'));
11344
+ if (r.status === 0) log(C.green(` ✓ :review 완료 (${dt}ms)`));
11345
+ else log(C.yel(` ⚠ :review 실패 (exit ${r.status}, ${dt}ms)`));
11346
+ return false;
11347
+ }
11348
+
11320
11349
  // 1.9.150: leerness 내부 명령 slash-commands — :verify / :audit / :handoff / :health
11321
11350
  // 1.9.161: Memory Surface 조회 slash 추가 — :lessons / :brainstorm / :tasks / :plan
11322
11351
  if (op === 'verify' || op === 'audit' || op === 'handoff' || op === 'health'
@@ -12825,6 +12854,274 @@ function lspCmd(root, sub, ...args) {
12825
12854
  fail(`알 수 없는 sub: ${sub} (check / symbols / references)`);
12826
12855
  }
12827
12856
 
12857
+ // 1.9.176: 사용자 요청 사전 검토 (사용자 명시 요청)
12858
+ // AI 에이전트가 사용자 요구를 **무조건 구현하기 전**에 먼저:
12859
+ // 1) 충돌 위험 (같은 키워드의 과거 실패/lessons)
12860
+ // 2) 기존 자원 재사용 후보 (reuse-map / skills)
12861
+ // 3) 더 효율적인 단계 제안 (route + plan)
12862
+ // 4) 권장 단계 리스트 (작업 유형별)
12863
+ // 를 분석하여 사용자에게 제시. 사용자 결정 후 구현 진행.
12864
+ //
12865
+ // 사용 예:
12866
+ // leerness review-request "OAuth 로그인 구현해줘"
12867
+ // leerness review-request "..." --json # MCP/외부 AI 통합
12868
+ //
12869
+ // REPL: :review <request> (1.9.175 slash 패턴)
12870
+ // MCP : leerness_review_request (외부 AI 직접 호출)
12871
+ function reviewRequestCmd(root, request) {
12872
+ root = absRoot(root || process.cwd());
12873
+ if (!request || !String(request).trim()) {
12874
+ return fail('leerness review-request "<request>" — 사용자 요청 텍스트 필요');
12875
+ }
12876
+ const t0 = Date.now();
12877
+ const text = String(request).trim();
12878
+
12879
+ // 1) 작업 유형 추정 (route 기반 키워드 매핑)
12880
+ const lower = text.toLowerCase();
12881
+ const routeKw = {
12882
+ bugfix: ['버그', '오류', '에러', '수정', '고쳐', '실패', 'fix', 'bug', 'error'],
12883
+ refactor: ['리팩토', '재구성', '정리', '개선', 'refactor', 'cleanup'],
12884
+ feature: ['추가', '구현', '만들', '새', '기능', 'add', 'implement', 'feature', 'create', 'new'],
12885
+ research: ['조사', '분석', '비교', '검토', '연구', 'research', 'analyze', 'compare', 'investigate'],
12886
+ planning: ['계획', '설계', '로드맵', 'plan', 'design', 'architecture', 'roadmap'],
12887
+ release: ['배포', '릴리즈', '버전', 'release', 'deploy', 'publish'],
12888
+ consistency: ['일관성', '통합', '동기화', '맞춰', 'consistency', 'sync', 'align']
12889
+ };
12890
+ let estimatedType = 'feature'; // default
12891
+ let maxScore = 0;
12892
+ for (const [type, kws] of Object.entries(routeKw)) {
12893
+ const score = kws.filter(k => lower.includes(k)).length;
12894
+ if (score > maxScore) { maxScore = score; estimatedType = type; }
12895
+ }
12896
+
12897
+ // 2) 기존 자원 회수 — brainstorm spawn (모든 surface 통합 회수)
12898
+ const conflictHints = []; // ⚠ 같은 키워드 + 실패/오류 패턴
12899
+ const reuseCandidates = []; // 🔁 기존 skill / reuse-map / decision 후보
12900
+ const lessonsRecall = []; // 🧠 과거 lesson
12901
+ const planConflicts = []; // 📋 진행 중 milestone과 충돌 가능
12902
+
12903
+ // brainstorm 호출 (1.9.13~) — JSON 결과 회수
12904
+ try {
12905
+ const r = cp.spawnSync(process.execPath, [__filename, 'brainstorm', text, '--path', root, '--json'], {
12906
+ encoding: 'utf8', timeout: 12000,
12907
+ env: { ...process.env, LEERNESS_NO_BANNER: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' }
12908
+ });
12909
+ if (r.stdout) {
12910
+ const j = JSON.parse(r.stdout);
12911
+ const hits = j.hits || {};
12912
+ // decisions — 과거 결정 후보
12913
+ (hits.decisions || []).slice(0, 5).forEach(d => {
12914
+ lessonsRecall.push({ kind: 'decision', title: d.title, line: d.line, preview: (d.preview || '').slice(0, 100) });
12915
+ });
12916
+ // lessons — 과거 교훈 (특히 실패 키워드)
12917
+ (hits.lessons || []).slice(0, 5).forEach(l => {
12918
+ const preview = (l.text || l.preview || '').slice(0, 100);
12919
+ const isFailure = /실패|오류|에러|fail|error|bug|문제|warning/i.test(preview);
12920
+ if (isFailure) {
12921
+ conflictHints.push({ kind: 'lesson-failure', preview, tags: l.tags });
12922
+ } else {
12923
+ lessonsRecall.push({ kind: 'lesson', preview, tags: l.tags });
12924
+ }
12925
+ });
12926
+ // skills — 기존 skill 후보
12927
+ (hits.skills || []).slice(0, 3).forEach(s => {
12928
+ reuseCandidates.push({ kind: 'skill', id: s.id, displayNameKo: s.displayNameKo, capabilities: s.capabilities });
12929
+ });
12930
+ // tasks — 진행 중 task 충돌
12931
+ (hits.tasks || []).slice(0, 3).forEach(tsk => {
12932
+ if (tsk.status && /in-progress|진행/.test(String(tsk.status))) {
12933
+ conflictHints.push({ kind: 'task-in-progress', id: tsk.id, title: tsk.title });
12934
+ }
12935
+ });
12936
+ // plan milestones — 진행 중 milestone
12937
+ (hits.planMilestones || []).slice(0, 3).forEach(m => {
12938
+ if (m.status && /in-progress|진행/.test(String(m.status))) {
12939
+ planConflicts.push({ kind: 'milestone-in-progress', id: m.id, title: m.title });
12940
+ }
12941
+ });
12942
+ // taskLogFails — 과거 같은 키워드 실패 흔적
12943
+ (hits.taskLogFails || []).slice(0, 3).forEach(f => {
12944
+ conflictHints.push({ kind: 'task-log-failure', preview: (f.preview || f.text || '').slice(0, 100) });
12945
+ });
12946
+ }
12947
+ } catch {}
12948
+
12949
+ // 3) reuse-map 매칭 — 기존 capability 등록 후보
12950
+ try {
12951
+ const reusePath = path.join(root, '.harness/reuse-map.md');
12952
+ if (exists(reusePath)) {
12953
+ const reuseLines = read(reusePath).split('\n');
12954
+ const tokens = lower.split(/\s+/).filter(t => t.length >= 3);
12955
+ for (const line of reuseLines) {
12956
+ if (!/^\| /.test(line)) continue; // 테이블 row만
12957
+ const ll = line.toLowerCase();
12958
+ const matched = tokens.filter(t => ll.includes(t)).length;
12959
+ if (matched > 0) {
12960
+ const cols = line.split('|').map(s => s.trim());
12961
+ if (cols[1]) {
12962
+ reuseCandidates.push({ kind: 'reuse-map', capability: cols[1], where: cols[2] || '', note: cols[3] || '' });
12963
+ }
12964
+ }
12965
+ }
12966
+ }
12967
+ } catch {}
12968
+
12969
+ // 4) feature_graph — 같은 영역 변경 가능성
12970
+ const featureConflicts = [];
12971
+ try {
12972
+ const fgPath = path.join(root, '.harness/feature_graph.md');
12973
+ if (exists(fgPath)) {
12974
+ const fg = read(fgPath);
12975
+ const tokens = lower.split(/\s+/).filter(t => t.length >= 4);
12976
+ // F-XXXX 노드 라인 추출
12977
+ const nodeBlocks = fg.split(/\n### /);
12978
+ for (const blk of nodeBlocks.slice(1)) {
12979
+ const bl = blk.toLowerCase();
12980
+ const matched = tokens.filter(t => bl.includes(t)).length;
12981
+ if (matched > 0) {
12982
+ const titleMatch = blk.match(/^([^\n]+)/);
12983
+ const idMatch = blk.match(/F-\d+/);
12984
+ if (titleMatch && idMatch) {
12985
+ featureConflicts.push({ kind: 'feature', id: idMatch[0], title: titleMatch[1].trim() });
12986
+ }
12987
+ }
12988
+ }
12989
+ }
12990
+ } catch {}
12991
+
12992
+ // 5) 권장 단계 (작업 유형별)
12993
+ const recommendedSteps = {
12994
+ feature: [
12995
+ '1) leerness reuse find "<핵심 capability>" — 중복 구현 사전 차단',
12996
+ '2) leerness plan add "<milestone>" — 진행 추적',
12997
+ '3) leerness contract verify SPEC.md src/<mod>.js — 사양 ↔ 구현 일치 검증',
12998
+ '4) verify-claim --run-tests 로 evidence 의무화'
12999
+ ],
13000
+ bugfix: [
13001
+ '1) leerness brainstorm "<버그 키워드>" — 과거 같은 영역 lesson 회수',
13002
+ '2) leerness verify-claim T-XXX --strict-claims — 낙관적 표시 사전 감지',
13003
+ '3) verify-code --run-tests — 재현 + fix 검증',
13004
+ '4) leerness lesson save "<root cause>" — 같은 실수 재발 차단'
13005
+ ],
13006
+ refactor: [
13007
+ '1) leerness reuse-map — 영향 범위 파악',
13008
+ '2) leerness impact <file> — 강한/약한 참조 분리',
13009
+ '3) leerness contract verify — 외부 인터페이스 보존 확인',
13010
+ '4) verify-code --run-tests + 회귀 테스트'
13011
+ ],
13012
+ research: [
13013
+ '1) leerness brainstorm "<주제>" — 누적 컨텍스트 회수',
13014
+ '2) leerness lessons --query "<주제>" — 과거 같은 영역 결정',
13015
+ '3) leerness review <file> --persona research — 깊이 검토',
13016
+ '4) leerness decision add "<결론>" — 회수 가능하게 영구화'
13017
+ ],
13018
+ planning: [
13019
+ '1) leerness plan add "<milestone>" — 분해 시작',
13020
+ '2) leerness reuse-map — 기존 자원 인벤토리',
13021
+ '3) leerness agents recommend planning — sub-agent 분배',
13022
+ '4) leerness session close — 결정 영구화'
13023
+ ],
13024
+ release: [
13025
+ '1) leerness health — production-ready 확인',
13026
+ '2) leerness audit + verify-code — 보안 + 검수',
13027
+ '3) leerness release bump + note + publish'
13028
+ ],
13029
+ consistency: [
13030
+ '1) leerness audit — design/reuse/handoff 일관성 검사',
13031
+ '2) leerness consistency check — 잠재 일관성 위반',
13032
+ '3) leerness drift check --auto-fix — 자동 회복'
13033
+ ]
13034
+ }[estimatedType] || [];
13035
+
13036
+ // 6) 효율 제안 (적용 가능한 sub-agent + skill)
13037
+ const efficiencyHints = [];
13038
+ if (reuseCandidates.length > 0) {
13039
+ efficiencyHints.push(`🔁 기존 자원 ${reuseCandidates.length}건 발견 — 신규 구현 전 재사용 검토 권장`);
13040
+ }
13041
+ if (conflictHints.length > 0) {
13042
+ efficiencyHints.push(`⚠ 충돌 신호 ${conflictHints.length}건 — 과거 실패 lesson / 진행 중 task 확인 필요`);
13043
+ }
13044
+ if (planConflicts.length > 0) {
13045
+ efficiencyHints.push(`📋 진행 중 milestone ${planConflicts.length}건과 영역 겹침 가능 — plan 정렬 권장`);
13046
+ }
13047
+ if (featureConflicts.length > 0) {
13048
+ efficiencyHints.push(`🕸 Feature Graph ${featureConflicts.length}건 영역 겹침 — 의존성 사전 확인`);
13049
+ }
13050
+ // 다중 에이전트 분배 추천
13051
+ if (estimatedType === 'feature' || estimatedType === 'planning') {
13052
+ efficiencyHints.push(`👥 leerness agents recommend ${estimatedType} — 작업 유형별 sub-agent 매핑 활용 가능`);
13053
+ }
13054
+ if (efficiencyHints.length === 0) {
13055
+ efficiencyHints.push('✨ 충돌 신호 없음 — 즉시 진행 안전');
13056
+ }
13057
+
13058
+ // 7) proceed 권장 (충돌 critical 시 false)
13059
+ const proceed = conflictHints.length < 3 && planConflicts.length === 0;
13060
+
13061
+ const dt = Date.now() - t0;
13062
+ const out = {
13063
+ request: text,
13064
+ estimatedType,
13065
+ conflicts: conflictHints,
13066
+ reuseCandidates,
13067
+ lessonsRecall,
13068
+ planConflicts,
13069
+ featureConflicts,
13070
+ recommendedSteps,
13071
+ efficiencyHints,
13072
+ proceed,
13073
+ proceedReason: proceed ? '안전 — 충돌 신호 < 3 + plan 충돌 0' : '⚠ 충돌 critical — 사용자 확인 후 진행',
13074
+ durationMs: dt
13075
+ };
13076
+
13077
+ try { _recordRun(root, { kind: 'review_request', estimatedType, conflicts: conflictHints.length, reuse: reuseCandidates.length, durationMs: dt, ok: true }); } catch {}
13078
+
13079
+ if (has('--json')) {
13080
+ log(JSON.stringify(out, null, 2));
13081
+ return;
13082
+ }
13083
+
13084
+ log(`# leerness review-request (1.9.176 사전 검토)`);
13085
+ log(`요청: "${text.slice(0, 200)}${text.length > 200 ? '…' : ''}"`);
13086
+ log(`추정 작업 유형: ${estimatedType}`);
13087
+ log('');
13088
+ if (conflictHints.length) {
13089
+ log(`## ⚠ 충돌 신호 (${conflictHints.length})`);
13090
+ conflictHints.slice(0, 5).forEach(c => log(` - [${c.kind}] ${c.title || c.id || ''} ${c.preview || ''}`.trim()));
13091
+ log('');
13092
+ }
13093
+ if (reuseCandidates.length) {
13094
+ log(`## 🔁 재사용 후보 (${reuseCandidates.length}) — 신규 구현 전 검토`);
13095
+ reuseCandidates.slice(0, 5).forEach(r => {
13096
+ if (r.kind === 'skill') log(` - [skill] ${r.id}${r.displayNameKo ? ' · ' + r.displayNameKo : ''}`);
13097
+ else if (r.kind === 'reuse-map') log(` - [reuse] ${r.capability} @ ${r.where}`);
13098
+ });
13099
+ log('');
13100
+ }
13101
+ if (lessonsRecall.length) {
13102
+ log(`## 🧠 과거 컨텍스트 (${lessonsRecall.length}) — 관련 결정/교훈`);
13103
+ lessonsRecall.slice(0, 3).forEach(l => log(` - [${l.kind}] ${l.title || l.preview}`));
13104
+ log('');
13105
+ }
13106
+ if (planConflicts.length || featureConflicts.length) {
13107
+ log(`## 📋 진행 중 영역 (${planConflicts.length + featureConflicts.length})`);
13108
+ planConflicts.forEach(m => log(` - [milestone] ${m.id}: ${m.title}`));
13109
+ featureConflicts.slice(0, 5).forEach(f => log(` - [feature] ${f.id}: ${f.title}`));
13110
+ log('');
13111
+ }
13112
+ log(`## 💡 효율 제안`);
13113
+ efficiencyHints.forEach(h => log(` ${h}`));
13114
+ log('');
13115
+ if (recommendedSteps.length) {
13116
+ log(`## 📍 권장 단계 (${estimatedType})`);
13117
+ recommendedSteps.forEach(s => log(` ${s}`));
13118
+ log('');
13119
+ }
13120
+ log(`## ▶ 진행 권장: ${proceed ? '✓ 진행 안전' : '⚠ 사용자 확인 필요'}`);
13121
+ log(` 사유: ${out.proceedReason}`);
13122
+ log(` 분석 소요: ${dt}ms`);
13123
+ }
13124
+
12828
13125
  // 1.9.164: leerness which — 진단 도구 (구버전 충돌 / npx 캐시 / PATH 충돌 해결)
12829
13126
  // 사용자가 "최신 버전 작동 안 함" 의심 시: 실제 실행 중인 leerness 의 경로 / 버전 / npm 캐시 / PATH 후보 표시.
12830
13127
  function whichCmd() {
@@ -12906,7 +13203,7 @@ function whichCmd() {
12906
13203
  }
12907
13204
 
12908
13205
  function help() {
12909
- log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 활성 N개 일괄 dispatch (--execute: 실 spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry — 사용자 정의 CLI provider 동적 추가 (OpenRouter/Bedrock 흡수)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi 모드 alias (또는 --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness which [--json] # 1.9.164 진단: 현재 실행 경로/버전 + npm 캐시 + PATH 후보 (구버전 충돌 해결)\n leerness web check|screenshot|extract <url> [--out file.png] [--selector "css"] # 1.9.165 playwright bridge (opt-in: npm i -g playwright + permissions.browser)\n leerness pc check|click|type|screenshot [--x N --y N] [--text "s"] [--out f.png] # 1.9.166 robotjs/nut-tree bridge (opt-in: npm i -g robotjs + permissions.mouse/keyboard, ⚠ full 모드 권장)\n leerness lsp check|symbols|references <file/name> [--in dir] [--json] # 1.9.167 LSP 어댑터 MVP (typescript opt-in + regex fallback, 코드 인텔리전스)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
13206
+ log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 활성 N개 일괄 dispatch (--execute: 실 spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry — 사용자 정의 CLI provider 동적 추가 (OpenRouter/Bedrock 흡수)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi 모드 alias (또는 --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness which [--json] # 1.9.164 진단: 현재 실행 경로/버전 + npm 캐시 + PATH 후보 (구버전 충돌 해결)\n leerness web check|screenshot|extract <url> [--out file.png] [--selector "css"] # 1.9.165 playwright bridge (opt-in: npm i -g playwright + permissions.browser)\n leerness pc check|click|type|screenshot [--x N --y N] [--text "s"] [--out f.png] # 1.9.166 robotjs/nut-tree bridge (opt-in: npm i -g robotjs + permissions.mouse/keyboard, ⚠ full 모드 권장)\n leerness lsp check|symbols|references <file/name> [--in dir] [--json] # 1.9.167 LSP 어댑터 MVP (typescript opt-in + regex fallback, 코드 인텔리전스)\n leerness review-request "<request>" [--json] # 1.9.176 사용자 요청 사전 검토 (충돌/재사용/효율/권장 단계 — 사용자 명시)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
12910
13207
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
12911
13208
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
12912
13209
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -12988,6 +13285,13 @@ async function main() {
12988
13285
  if (cmd === 'pc') return pcCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
12989
13286
 
12990
13287
  if (cmd === 'lsp') return lspCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
13288
+
13289
+ // 1.9.176: leerness review-request "<request>" — 사용자 요청 사전 검토 (사용자 명시)
13290
+ // AI 에이전트가 무조건 구현 전에 충돌/재사용/효율/권장 단계 분석.
13291
+ if (cmd === 'review-request' || cmd === 'review-req') {
13292
+ const reqText = args.slice(1).filter(x => !x.startsWith('-')).join(' ');
13293
+ return reviewRequestCmd(arg('--path', process.cwd()), reqText);
13294
+ }
12991
13295
  if (cmd === 'contract' && args[1] === 'verify') return contractVerifyCmd(args[2], args[3]);
12992
13296
  if (cmd === 'drift' && (args[1] === 'check' || !args[1])) return driftCheckCmd(args[2] || arg('--path', process.cwd()));
12993
13297
  if (cmd === 'usage' && (args[1] === 'stats' || !args[1])) return usageStatsCmd(args[2] || arg('--path', process.cwd()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.175",
3
+ "version": "1.9.176",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",