leerness 1.9.53 → 1.9.55

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,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.55 — 2026-05-19
4
+
5
+ **MCP server 12 도구 — `leerness_skill_suggest` + `leerness_lessons` 노출**.
6
+
7
+ ### Added
8
+ - **MCP `leerness_skill_suggest`** (1.9.53 자동 학습을 외부 노출):
9
+ - Claude Code/Hermes/Cursor가 `tools/call`로 호출 가능
10
+ - args: `{ path, min, days }` → JSON candidates 반환
11
+ - **MCP `leerness_lessons`** (1.9.7/54 lessons를 외부 노출):
12
+ - args: `{ path, query, auto, limit }`
13
+ - MCP 총 **10 → 12 도구**
14
+
15
+ ## 1.9.54 — 2026-05-19
16
+
17
+ **`leerness lessons --auto` — 과거 lessons 자동 재상기**.
18
+
19
+ ### Added
20
+ - **`leerness lessons --auto [--path X]`**:
21
+ - 가장 최근 in-progress/planned task의 `request` 컬럼에서 키워드 자동 추출
22
+ - 그 키워드로 lessons 자동 검색 (decisions / review-evidence / task-log / handoff)
23
+ - 임계: 4자+ 키워드, 가장 긴 단어 선택
24
+ - stopword 자동 제외 (한국어 + 영어 20+ 단어)
25
+ - **stopword 확장** (1.9.55 패치): "프로젝트/관리/기능/시스템" 등 너무 일반적인 단어 제외
26
+
27
+ ### 검증 (stress-v6)
28
+ - Q1-Q3 (lessons --auto) 3/3 PASS
29
+ - R1-R3 (MCP 12 도구 + 신규 호출) 3/3 PASS
30
+ - S1-S4 (1.9.43~53 누적 회귀) 4/4 PASS
31
+ - **stress-v6: 10/10 PASS**, e2e: **208/208 PASS**
32
+
33
+ ### 발견·패치 (stress-v6)
34
+ - 🟡 stopword 부족 → "프로젝트"가 default task에서 키워드로 잡혀 false positive
35
+ - 1.9.55 패치: stopword 20+ 단어로 확장
36
+
3
37
  ## 1.9.53 — 2026-05-19
4
38
 
5
39
  **`leerness skill suggest` — Hermes-style 자동 학습 (사용 패턴 → skill 후보 자동 제안)**.
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.53-green)]() [![tests](https://img.shields.io/badge/e2e-206%2F206-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.55-green)]() [![tests](https://img.shields.io/badge/e2e-208%2F208-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,7 +12,7 @@
12
12
  ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
13
13
  ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
14
14
  ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
15
- ║ v1.9.53 AI Agent Reliability Harness ║
15
+ ║ v1.9.55 AI Agent Reliability Harness ║
16
16
  ║ verify · remember · orchestrate · audit · prevent drift ║
17
17
  ╚══════════════════════════════════════════════════════════════╝
18
18
  ```
@@ -433,6 +433,8 @@ npm test # = node ./scripts/e2e.js
433
433
 
434
434
  ## 변경 이력 (최근)
435
435
 
436
+ - **1.9.55** — MCP server에 `leerness_skill_suggest` + `leerness_lessons` 추가 (10 → 12 도구) · lessons --auto의 stopword 확장 (false positive 차단).
437
+ - **1.9.54** — `leerness lessons --auto` — 최근 in-progress task에서 키워드 자동 추출 → 과거 lessons 자동 매칭·재상기.
436
438
  - **1.9.53** — `leerness skill suggest` — task-log / progress-tracker / usage-stats에서 반복 패턴 **자동 감지 → 새 skill 후보 제안** (Hermes-style 자동 학습의 leerness 버전).
437
439
  - **1.9.52** — `skill discover` 카탈로그 형식 다양성 — JSON manifest / RSS·Atom / Markdown / llms.txt URL 4 형식 자동 감지 (`_parseSkillCatalog`).
438
440
  - **1.9.51** — `benchmark --scenario <id|all>` — leerness 고유 가치 시나리오 4종 (거짓 완료 / 사양 불일치 / drift / BOM) **command 한 번에 정량 증명**.
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.53';
9
+ const VERSION = '1.9.55';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -5354,8 +5354,34 @@ function verifyCodeCmd(root) {
5354
5354
  // ===== 1.9.7 B: lessons — 과거 결정/실수 자동 회수 =====
5355
5355
  function lessonsCmd(root) {
5356
5356
  root = absRoot(root);
5357
- const query = arg('--query', null);
5357
+ let query = arg('--query', null);
5358
5358
  const limit = parseInt(arg('--limit', '10'), 10);
5359
+ // 1.9.54: --auto 옵션 — 현재 진행 중인 task의 키워드 자동 추출 → query로 사용
5360
+ if (has('--auto') && !query) {
5361
+ const rows = readProgressRows(root);
5362
+ // 가장 최근 in-progress 또는 가장 최근 row의 request에서 키워드 추출
5363
+ const latest = rows.filter(r => r.status === 'in-progress' || r.status === 'planned').pop()
5364
+ || rows[rows.length - 1];
5365
+ if (latest && latest.request) {
5366
+ // 4자+ 키워드 중 가장 긴 단어 1개 선택
5367
+ const tokens = String(latest.request).toLowerCase().match(/[\w가-힣]{4,}/g) || [];
5368
+ // 1.9.55: stopword 확장 — 너무 일반적인 단어 제외 (lessons 매칭에 도움 안 됨)
5369
+ const stopwords = new Set([
5370
+ '이런', '저런', '하다', '하고', '있는', '하지', '에서',
5371
+ '작업', '구현', '추가', '진행', '수정', '변경', '검토', '확인',
5372
+ '프로젝트', '관리', '기능', '시스템', '코드', '파일', '버전', '정리', '계획',
5373
+ 'next', 'action', 'task', 'todo', 'work'
5374
+ ]);
5375
+ const candidate = tokens.filter(t => !stopwords.has(t)).sort((a, b) => b.length - a.length)[0];
5376
+ if (candidate) query = candidate;
5377
+ }
5378
+ if (!query) {
5379
+ log('# Lessons --auto');
5380
+ log('(현재 작업에서 추출할 키워드 없음 — 새 task 등록 후 다시 시도)');
5381
+ return;
5382
+ }
5383
+ log(`# Lessons --auto (1.9.54): 추출 키워드 "${query}"`);
5384
+ }
5359
5385
  const decisions = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
5360
5386
  const evidence = exists(evidencePath(root)) ? read(evidencePath(root)) : '';
5361
5387
  const tlog = exists(taskLogPath(root)) ? read(taskLogPath(root)) : '';
@@ -6606,7 +6632,9 @@ function mcpServeCmd(root) {
6606
6632
  { name: 'leerness_reuse_map', description: '워크스페이스 중복 함수/capability 자동 감지 (--all-apps + fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, allApps: { type: 'boolean' }, strictElements: { type: 'boolean' } } } },
6607
6633
  { name: 'leerness_whats_new', description: 'CHANGELOG 차분 자동 추출 (from → to 사이 신규 명령/플래그/파일)', inputSchema: { type: 'object', properties: { from: { type: 'string' }, to: { type: 'string' } } } },
6608
6634
  { name: 'leerness_usage_stats', description: 'leerness 명령별 누적 호출 통계 + drift 통계', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
6609
- { name: 'leerness_session_close', description: '세션 마감 — handoff/current-state/task-log 자동 갱신', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } }
6635
+ { name: 'leerness_session_close', description: '세션 마감 — handoff/current-state/task-log 자동 갱신', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
6636
+ { name: 'leerness_skill_suggest', description: '1.9.53 — 사용 패턴 자동 분석 → 새 skill 후보 제안 (Hermes-style 자동 학습)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, min: { type: 'number' }, days: { type: 'number' } } } },
6637
+ { name: 'leerness_lessons', description: '1.9.7/54 — 과거 결정·실수 자동 회수 (--auto: 현재 task 키워드 자동 추출)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, query: { type: 'string' }, auto: { type: 'boolean' }, limit: { type: 'number' } } } }
6610
6638
  ];
6611
6639
 
6612
6640
  function send(obj) {
@@ -6646,6 +6674,8 @@ function mcpServeCmd(root) {
6646
6674
  case 'leerness_whats_new': cliArgs = ['whats-new', '--path', targetPath, ...(args.from ? ['--from', args.from] : []), ...(args.to ? ['--to', args.to] : []), '--json']; break;
6647
6675
  case 'leerness_usage_stats': cliArgs = ['usage', 'stats', targetPath, '--json']; break;
6648
6676
  case 'leerness_session_close': cliArgs = ['session', 'close', targetPath]; break;
6677
+ case 'leerness_skill_suggest': cliArgs = ['skill', 'suggest', '--path', targetPath, '--json', ...(args.min ? ['--min', String(args.min)] : []), ...(args.days ? ['--days', String(args.days)] : [])]; break;
6678
+ case 'leerness_lessons': cliArgs = ['lessons', '--path', targetPath, ...(args.auto ? ['--auto'] : []), ...(args.query ? ['--query', args.query] : []), ...(args.limit ? ['--limit', String(args.limit)] : [])]; break;
6649
6679
  default:
6650
6680
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
6651
6681
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.53",
3
+ "version": "1.9.55",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -950,6 +950,37 @@ total++;
950
950
  if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
951
951
  }
952
952
 
953
+ // 1.9.54/55 회귀
954
+ total++;
955
+ {
956
+ // lessons --auto 키워드 자동 추출
957
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-la-'));
958
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
959
+ fs.writeFileSync(path.join(tmpC, '.harness', 'review-evidence.md'),
960
+ '## 2026-04-01\nNote: ✗ payment 처리 실패 — 검수 누락\n', 'utf8');
961
+ cp.spawnSync(process.execPath, [CLI, 'task', 'add', 'payment 검증 작업', '--status', 'in-progress', '--path', tmpC], { stdio: 'ignore', timeout: 10000 });
962
+ const r = cp.spawnSync(process.execPath, [CLI, 'lessons', '--path', tmpC, '--auto', '--limit', '5'], { encoding: 'utf8', timeout: 15000 });
963
+ const ok = /추출 키워드.*payment/.test(r.stdout) && /payment.*실패/.test(r.stdout);
964
+ console.log(ok ? '✓ B(1.9.54) lessons --auto: in-progress task 키워드 자동 추출 + 매칭' : `✗ lessons --auto 실패`);
965
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
966
+ }
967
+
968
+ total++;
969
+ {
970
+ // MCP server tools/list 12 도구 (1.9.55)
971
+ const r = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], {
972
+ encoding: 'utf8', timeout: 8000,
973
+ input: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }) + '\n'
974
+ });
975
+ let resp = null;
976
+ try { resp = JSON.parse(r.stdout.split('\n').filter(Boolean)[0]); } catch {}
977
+ const tools = resp && resp.result && resp.result.tools;
978
+ const hasNew = tools && tools.some(t => t.name === 'leerness_skill_suggest') && tools.some(t => t.name === 'leerness_lessons');
979
+ const ok = tools && tools.length >= 12 && hasNew;
980
+ console.log(ok ? `✓ B(1.9.55) MCP: ${tools.length} 도구 (skill_suggest + lessons 추가)` : `✗ MCP 12 도구 실패`);
981
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
982
+ }
983
+
953
984
  // 1.9.53 회귀: skill suggest 자동 학습
954
985
  total++;
955
986
  {