leerness 1.9.53 → 1.9.57
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 +70 -0
- package/README.md +6 -2
- package/bin/harness.js +131 -12
- package/package.json +1 -1
- package/scripts/e2e.js +57 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.57 — 2026-05-19
|
|
4
|
+
|
|
5
|
+
**`session close --suggest` + 설치 가이드 갱신**.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`leerness session close --suggest`** — 라운드 마감 통합 보고:
|
|
9
|
+
- skill suggest 후보 (Hermes-style 자동 학습) 상위 3
|
|
10
|
+
- drift check 상태 + 임계 초과 신호
|
|
11
|
+
- usage stats 가장 많이 쓴 명령 Top 3
|
|
12
|
+
|
|
13
|
+
### 설치 가이드 갱신
|
|
14
|
+
- **`_banner` quickStart 재구성** — 1.9.57+ 워크플로 강조:
|
|
15
|
+
- `npx leerness handoff .` (lessons 자동 재상기 포함)
|
|
16
|
+
- `npx leerness session close . --suggest` (마감 + 다음 라운드)
|
|
17
|
+
- `npx leerness mcp serve` (메인 에이전트용 12 도구)
|
|
18
|
+
- **`.harness/session-workflow.md`** Step 6 갱신 — `--suggest`/1.9.56 lessons 자동 재상기 안내
|
|
19
|
+
|
|
20
|
+
## 1.9.56 — 2026-05-19
|
|
21
|
+
|
|
22
|
+
**`handoff`에 `lessons --auto` 자동 통합 — 매 세션 시작 시 과거 실패 자동 재상기**.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- **handoff 자동 lessons 재상기**:
|
|
26
|
+
- 가장 최근 in-progress/planned task의 `request`에서 키워드 자동 추출
|
|
27
|
+
- 그 키워드로 review-evidence.md의 과거 실패 매칭
|
|
28
|
+
- **🧠 과거 lessons 자동 재상기** 블록 출력 (관련 실패 ≥1건 시)
|
|
29
|
+
- 끄려면: `--no-lessons` 또는 `LEERNESS_NO_LESSONS=1`
|
|
30
|
+
- 매칭 실패 시 블록 자동 숨김 (false positive 차단)
|
|
31
|
+
|
|
32
|
+
### 검증 (stress-v7)
|
|
33
|
+
- T1-T3 (handoff 자동 lessons) 3/3 PASS
|
|
34
|
+
- U1-U3 (session close --suggest) 3/3 PASS
|
|
35
|
+
- V1-V2 (설치 가이드 갱신 — banner + session-workflow.md) 2/2 PASS
|
|
36
|
+
- W1-W4 (1.9.43~55 누적 회귀) 4/4 PASS
|
|
37
|
+
- **stress-v7: 12/12 PASS**, e2e: **210/210 PASS**
|
|
38
|
+
|
|
39
|
+
## 1.9.55 — 2026-05-19
|
|
40
|
+
|
|
41
|
+
**MCP server 12 도구 — `leerness_skill_suggest` + `leerness_lessons` 노출**.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- **MCP `leerness_skill_suggest`** (1.9.53 자동 학습을 외부 노출):
|
|
45
|
+
- Claude Code/Hermes/Cursor가 `tools/call`로 호출 가능
|
|
46
|
+
- args: `{ path, min, days }` → JSON candidates 반환
|
|
47
|
+
- **MCP `leerness_lessons`** (1.9.7/54 lessons를 외부 노출):
|
|
48
|
+
- args: `{ path, query, auto, limit }`
|
|
49
|
+
- MCP 총 **10 → 12 도구**
|
|
50
|
+
|
|
51
|
+
## 1.9.54 — 2026-05-19
|
|
52
|
+
|
|
53
|
+
**`leerness lessons --auto` — 과거 lessons 자동 재상기**.
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
- **`leerness lessons --auto [--path X]`**:
|
|
57
|
+
- 가장 최근 in-progress/planned task의 `request` 컬럼에서 키워드 자동 추출
|
|
58
|
+
- 그 키워드로 lessons 자동 검색 (decisions / review-evidence / task-log / handoff)
|
|
59
|
+
- 임계: 4자+ 키워드, 가장 긴 단어 선택
|
|
60
|
+
- stopword 자동 제외 (한국어 + 영어 20+ 단어)
|
|
61
|
+
- **stopword 확장** (1.9.55 패치): "프로젝트/관리/기능/시스템" 등 너무 일반적인 단어 제외
|
|
62
|
+
|
|
63
|
+
### 검증 (stress-v6)
|
|
64
|
+
- Q1-Q3 (lessons --auto) 3/3 PASS
|
|
65
|
+
- R1-R3 (MCP 12 도구 + 신규 호출) 3/3 PASS
|
|
66
|
+
- S1-S4 (1.9.43~53 누적 회귀) 4/4 PASS
|
|
67
|
+
- **stress-v6: 10/10 PASS**, e2e: **208/208 PASS**
|
|
68
|
+
|
|
69
|
+
### 발견·패치 (stress-v6)
|
|
70
|
+
- 🟡 stopword 부족 → "프로젝트"가 default task에서 키워드로 잡혀 false positive
|
|
71
|
+
- 1.9.55 패치: stopword 20+ 단어로 확장
|
|
72
|
+
|
|
3
73
|
## 1.9.53 — 2026-05-19
|
|
4
74
|
|
|
5
75
|
**`leerness skill suggest` — Hermes-style 자동 학습 (사용 패턴 → skill 후보 자동 제안)**.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []()
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
15
|
+
║ v1.9.57 AI Agent Reliability Harness ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · prevent drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
|
@@ -433,6 +433,10 @@ npm test # = node ./scripts/e2e.js
|
|
|
433
433
|
|
|
434
434
|
## 변경 이력 (최근)
|
|
435
435
|
|
|
436
|
+
- **1.9.57** — `session close --suggest` (마감 시 skill suggest + drift + 명령 통계 통합 보고) · install banner quickStart + session-workflow.md 갱신 (1.9.56/57 흐름 자동 안내).
|
|
437
|
+
- **1.9.56** — `handoff`에 lessons 자동 재상기 통합 (현재 in-progress task 키워드 매칭 → 과거 실패 자동 표시).
|
|
438
|
+
- **1.9.55** — MCP server에 `leerness_skill_suggest` + `leerness_lessons` 추가 (10 → 12 도구) · lessons --auto의 stopword 확장 (false positive 차단).
|
|
439
|
+
- **1.9.54** — `leerness lessons --auto` — 최근 in-progress task에서 키워드 자동 추출 → 과거 lessons 자동 매칭·재상기.
|
|
436
440
|
- **1.9.53** — `leerness skill suggest` — task-log / progress-tracker / usage-stats에서 반복 패턴 **자동 감지 → 새 skill 후보 제안** (Hermes-style 자동 학습의 leerness 버전).
|
|
437
441
|
- **1.9.52** — `skill discover` 카탈로그 형식 다양성 — JSON manifest / RSS·Atom / Markdown / llms.txt URL 4 형식 자동 감지 (`_parseSkillCatalog`).
|
|
438
442
|
- **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.
|
|
9
|
+
const VERSION = '1.9.57';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -276,14 +276,18 @@ leerness review <file> --persona security,performance,ux
|
|
|
276
276
|
- 메인이 직접 통합 시나리오 작성 + 실행 (independent 검증).
|
|
277
277
|
- Sub-agent 검수 vs 메인 검수 결과 *교차 일치* 확인.
|
|
278
278
|
|
|
279
|
-
## Step 6. 세션 마감 + 인계
|
|
279
|
+
## Step 6. 세션 마감 + 인계 + 다음 라운드 추천
|
|
280
280
|
\`\`\`bash
|
|
281
|
-
leerness session close .
|
|
282
|
-
|
|
283
|
-
leerness
|
|
281
|
+
leerness session close . --suggest # 1.9.57+ — 마감 + 다음 라운드 추천 통합
|
|
282
|
+
# 단독으로도 가능:
|
|
283
|
+
leerness session close . # handoff/current-state/task-log 자동 갱신
|
|
284
|
+
leerness skill suggest . # 1.9.53 — 반복 패턴 → 새 skill 후보
|
|
285
|
+
leerness drift check . # 4 신호 + 4 레벨 점검
|
|
286
|
+
leerness audit . --fix # 누락 메타 자동 보강
|
|
284
287
|
\`\`\`
|
|
285
288
|
- session close가 누락되면 다음 세션 시작 시 drift critical 발생.
|
|
286
289
|
- 자동 회복 옵션: \`drift check --auto-fix\` (critical 시 session close 자동 실행).
|
|
290
|
+
- 1.9.56+ handoff가 매 세션 시작 시 **과거 lessons 자동 재상기** (현재 task 키워드 기준).
|
|
287
291
|
|
|
288
292
|
---
|
|
289
293
|
|
|
@@ -1699,6 +1703,50 @@ function handoff(root) {
|
|
|
1699
1703
|
const cs = read(currentStatePath(root)).replace(/Updated: \d{4}-\d{2}-\d{2}/, `Updated: ${today()}`);
|
|
1700
1704
|
writeUtf8(currentStatePath(root), cs);
|
|
1701
1705
|
}
|
|
1706
|
+
// 1.9.56: handoff에 lessons --auto 자동 통합 — 현재 in-progress task와 관련된 과거 실수/결정 자동 재상기
|
|
1707
|
+
// 매 세션 시작 시 AI가 과거에 같은 키워드로 실패한 사례를 잊지 않도록.
|
|
1708
|
+
// 끄려면: --no-lessons 또는 LEERNESS_NO_LESSONS=1
|
|
1709
|
+
if (!has('--no-lessons') && !has('--compact') && process.env.LEERNESS_NO_LESSONS !== '1') {
|
|
1710
|
+
try {
|
|
1711
|
+
const lrows = readProgressRows(root);
|
|
1712
|
+
const latestRow = lrows.filter(r => r.status === 'in-progress' || r.status === 'planned').pop() || lrows[lrows.length - 1];
|
|
1713
|
+
if (latestRow && latestRow.request) {
|
|
1714
|
+
const stopwords = new Set([
|
|
1715
|
+
'이런','저런','하다','하고','있는','하지','에서',
|
|
1716
|
+
'작업','구현','추가','진행','수정','변경','검토','확인',
|
|
1717
|
+
'프로젝트','관리','기능','시스템','코드','파일','버전','정리','계획',
|
|
1718
|
+
'next','action','task','todo','work'
|
|
1719
|
+
]);
|
|
1720
|
+
const tokens = String(latestRow.request).toLowerCase().match(/[\w가-힣]{4,}/g) || [];
|
|
1721
|
+
const keyword = tokens.filter(t => !stopwords.has(t)).sort((a, b) => b.length - a.length)[0];
|
|
1722
|
+
if (keyword) {
|
|
1723
|
+
// lessons 검색
|
|
1724
|
+
const decisions = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
|
|
1725
|
+
const evidence = exists(evidencePath(root)) ? read(evidencePath(root)) : '';
|
|
1726
|
+
const matches = [];
|
|
1727
|
+
for (const block of evidence.split(/\n(?=## )/)) {
|
|
1728
|
+
if (block.startsWith('## ') && new RegExp(escapeRegex(keyword), 'i').test(block) && /✗|fail|롤백|버그|incomplete/i.test(block)) {
|
|
1729
|
+
const titleM = block.match(/^## (.+)$/m);
|
|
1730
|
+
if (titleM) matches.push({ source: 'review-evidence.md', title: titleM[1].trim(), block });
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
if (matches.length) {
|
|
1734
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
1735
|
+
const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
|
|
1736
|
+
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
1737
|
+
log('');
|
|
1738
|
+
log(yel(`## 🧠 과거 lessons 자동 재상기 (1.9.56) — 키워드 "${keyword}"`));
|
|
1739
|
+
log(dim(` 현재 task와 관련된 과거 실패/롤백 ${matches.length}건 — 같은 실수 반복 방지`));
|
|
1740
|
+
for (const m of matches.slice(0, 3)) {
|
|
1741
|
+
log(dim(` • [${m.source}] ${m.title}`));
|
|
1742
|
+
}
|
|
1743
|
+
log(dim(` → 전체: leerness lessons --auto --path .`));
|
|
1744
|
+
log('');
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
} catch {}
|
|
1749
|
+
}
|
|
1702
1750
|
// 1.9.41: 최근 migrate 차분 알림 — migration-report.md가 24h 내면 "AI must re-read" 블록 자동 표시
|
|
1703
1751
|
// 같은 채팅 세션의 AI 청크가 이전 버전 마인드셋이어도 새 도구를 즉시 인지하도록.
|
|
1704
1752
|
if (!has('--no-workflow-guide') && !has('--compact')) {
|
|
@@ -3044,11 +3092,15 @@ function _banner(opts = {}) {
|
|
|
3044
3092
|
lines.push('');
|
|
3045
3093
|
for (const ln of lines) log(ln);
|
|
3046
3094
|
if (opts.quickStart) {
|
|
3047
|
-
log(C.bold(C.cyan(' ✨ 빠른 시작')));
|
|
3048
|
-
log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트'));
|
|
3049
|
-
log(' ' + C.green('npx leerness
|
|
3050
|
-
log(' ' + C.green('npx leerness
|
|
3051
|
-
log(' ' + C.green('npx leerness
|
|
3095
|
+
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.57+ 워크플로)')));
|
|
3096
|
+
log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트 + 외부 AI CLI 설정'));
|
|
3097
|
+
log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 적재 + 과거 lessons 자동 재상기'));
|
|
3098
|
+
log(' ' + C.green('npx leerness verify-claim T-0001 --run-tests') + C.dim(' # AI 거짓 완료 자동 검증'));
|
|
3099
|
+
log(' ' + C.green('npx leerness session close . --suggest') + C.dim(' # 마감 + 다음 라운드 추천'));
|
|
3100
|
+
log('');
|
|
3101
|
+
log(C.bold(C.cyan(' 🤖 메인 에이전트 (Claude/Cursor/Copilot)용')));
|
|
3102
|
+
log(' ' + C.green('npx leerness mcp serve') + C.dim(' # MCP 서버 — 12 도구 노출'));
|
|
3103
|
+
log(' ' + C.green('npx leerness agents bench "<task>"') + C.dim(' # 3 CLI 동시 비교'));
|
|
3052
3104
|
log('');
|
|
3053
3105
|
}
|
|
3054
3106
|
}
|
|
@@ -3796,6 +3848,43 @@ function sessionClose(root) {
|
|
|
3796
3848
|
ok(`session-handoff.md and current-state.md updated`);
|
|
3797
3849
|
// 1.9.12: session close 끝에 roadmap.html 자동 갱신
|
|
3798
3850
|
_autoRoadmap(root, 'session-close');
|
|
3851
|
+
// 1.9.57: --suggest 옵션 — 마감 시 skill suggest + drift check + lessons 통합 보고
|
|
3852
|
+
if (has('--suggest')) {
|
|
3853
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
3854
|
+
const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
3855
|
+
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
3856
|
+
log('');
|
|
3857
|
+
log(cy('## 💡 다음 라운드 추천 (1.9.57 --suggest)'));
|
|
3858
|
+
// 1) skill suggest
|
|
3859
|
+
try {
|
|
3860
|
+
const r = cp.spawnSync(process.execPath, [__filename, 'skill', 'suggest', '--path', root, '--min', '3', '--json'],
|
|
3861
|
+
{ encoding: 'utf8', timeout: 15000, env: { ...process.env, LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' } });
|
|
3862
|
+
const j = JSON.parse(r.stdout);
|
|
3863
|
+
if (j.candidates && j.candidates.length) {
|
|
3864
|
+
log(dim(' 📌 신규 skill 후보 (Hermes-style 자동 학습):'));
|
|
3865
|
+
for (const c of j.candidates.slice(0, 3)) log(` • ${c.keyword} (${c.count}회 등장, 출처: ${c.source})`);
|
|
3866
|
+
}
|
|
3867
|
+
} catch {}
|
|
3868
|
+
// 2) drift check
|
|
3869
|
+
try {
|
|
3870
|
+
const r = cp.spawnSync(process.execPath, [__filename, 'drift', 'check', root, '--json'],
|
|
3871
|
+
{ encoding: 'utf8', timeout: 15000, env: { ...process.env, LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '0' } });
|
|
3872
|
+
const j = JSON.parse(r.stdout.trim());
|
|
3873
|
+
if (j.level) {
|
|
3874
|
+
log(dim(` 🩺 drift 상태: ${j.level} ${j.score}/200`));
|
|
3875
|
+
if (j.fired && j.fired.length) log(dim(` 🔥 ${j.fired.length}건 임계 초과 — \`leerness drift check\` 상세`));
|
|
3876
|
+
}
|
|
3877
|
+
} catch {}
|
|
3878
|
+
// 3) usage stats top
|
|
3879
|
+
try {
|
|
3880
|
+
const stats = _readUsageStats(root);
|
|
3881
|
+
const entries = Object.entries(stats.commands || {}).sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
3882
|
+
if (entries.length) {
|
|
3883
|
+
log(dim(` 📊 가장 많이 쓴 명령: ${entries.map(([c, n]) => `${c}(${n})`).join(', ')}`));
|
|
3884
|
+
}
|
|
3885
|
+
} catch {}
|
|
3886
|
+
log('');
|
|
3887
|
+
}
|
|
3799
3888
|
// 1.9.13: 세션 카운터 + 자동 한 줄 요약 + 5세션마다 깊은 회고
|
|
3800
3889
|
try {
|
|
3801
3890
|
const sc = readSessionCounter(root);
|
|
@@ -5354,8 +5443,34 @@ function verifyCodeCmd(root) {
|
|
|
5354
5443
|
// ===== 1.9.7 B: lessons — 과거 결정/실수 자동 회수 =====
|
|
5355
5444
|
function lessonsCmd(root) {
|
|
5356
5445
|
root = absRoot(root);
|
|
5357
|
-
|
|
5446
|
+
let query = arg('--query', null);
|
|
5358
5447
|
const limit = parseInt(arg('--limit', '10'), 10);
|
|
5448
|
+
// 1.9.54: --auto 옵션 — 현재 진행 중인 task의 키워드 자동 추출 → query로 사용
|
|
5449
|
+
if (has('--auto') && !query) {
|
|
5450
|
+
const rows = readProgressRows(root);
|
|
5451
|
+
// 가장 최근 in-progress 또는 가장 최근 row의 request에서 키워드 추출
|
|
5452
|
+
const latest = rows.filter(r => r.status === 'in-progress' || r.status === 'planned').pop()
|
|
5453
|
+
|| rows[rows.length - 1];
|
|
5454
|
+
if (latest && latest.request) {
|
|
5455
|
+
// 4자+ 키워드 중 가장 긴 단어 1개 선택
|
|
5456
|
+
const tokens = String(latest.request).toLowerCase().match(/[\w가-힣]{4,}/g) || [];
|
|
5457
|
+
// 1.9.55: stopword 확장 — 너무 일반적인 단어 제외 (lessons 매칭에 도움 안 됨)
|
|
5458
|
+
const stopwords = new Set([
|
|
5459
|
+
'이런', '저런', '하다', '하고', '있는', '하지', '에서',
|
|
5460
|
+
'작업', '구현', '추가', '진행', '수정', '변경', '검토', '확인',
|
|
5461
|
+
'프로젝트', '관리', '기능', '시스템', '코드', '파일', '버전', '정리', '계획',
|
|
5462
|
+
'next', 'action', 'task', 'todo', 'work'
|
|
5463
|
+
]);
|
|
5464
|
+
const candidate = tokens.filter(t => !stopwords.has(t)).sort((a, b) => b.length - a.length)[0];
|
|
5465
|
+
if (candidate) query = candidate;
|
|
5466
|
+
}
|
|
5467
|
+
if (!query) {
|
|
5468
|
+
log('# Lessons --auto');
|
|
5469
|
+
log('(현재 작업에서 추출할 키워드 없음 — 새 task 등록 후 다시 시도)');
|
|
5470
|
+
return;
|
|
5471
|
+
}
|
|
5472
|
+
log(`# Lessons --auto (1.9.54): 추출 키워드 "${query}"`);
|
|
5473
|
+
}
|
|
5359
5474
|
const decisions = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
|
|
5360
5475
|
const evidence = exists(evidencePath(root)) ? read(evidencePath(root)) : '';
|
|
5361
5476
|
const tlog = exists(taskLogPath(root)) ? read(taskLogPath(root)) : '';
|
|
@@ -6606,7 +6721,9 @@ function mcpServeCmd(root) {
|
|
|
6606
6721
|
{ name: 'leerness_reuse_map', description: '워크스페이스 중복 함수/capability 자동 감지 (--all-apps + fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, allApps: { type: 'boolean' }, strictElements: { type: 'boolean' } } } },
|
|
6607
6722
|
{ name: 'leerness_whats_new', description: 'CHANGELOG 차분 자동 추출 (from → to 사이 신규 명령/플래그/파일)', inputSchema: { type: 'object', properties: { from: { type: 'string' }, to: { type: 'string' } } } },
|
|
6608
6723
|
{ 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' } } } }
|
|
6724
|
+
{ name: 'leerness_session_close', description: '세션 마감 — handoff/current-state/task-log 자동 갱신', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
6725
|
+
{ name: 'leerness_skill_suggest', description: '1.9.53 — 사용 패턴 자동 분석 → 새 skill 후보 제안 (Hermes-style 자동 학습)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, min: { type: 'number' }, days: { type: 'number' } } } },
|
|
6726
|
+
{ 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
6727
|
];
|
|
6611
6728
|
|
|
6612
6729
|
function send(obj) {
|
|
@@ -6646,6 +6763,8 @@ function mcpServeCmd(root) {
|
|
|
6646
6763
|
case 'leerness_whats_new': cliArgs = ['whats-new', '--path', targetPath, ...(args.from ? ['--from', args.from] : []), ...(args.to ? ['--to', args.to] : []), '--json']; break;
|
|
6647
6764
|
case 'leerness_usage_stats': cliArgs = ['usage', 'stats', targetPath, '--json']; break;
|
|
6648
6765
|
case 'leerness_session_close': cliArgs = ['session', 'close', targetPath]; break;
|
|
6766
|
+
case 'leerness_skill_suggest': cliArgs = ['skill', 'suggest', '--path', targetPath, '--json', ...(args.min ? ['--min', String(args.min)] : []), ...(args.days ? ['--days', String(args.days)] : [])]; break;
|
|
6767
|
+
case 'leerness_lessons': cliArgs = ['lessons', '--path', targetPath, ...(args.auto ? ['--auto'] : []), ...(args.query ? ['--query', args.query] : []), ...(args.limit ? ['--limit', String(args.limit)] : [])]; break;
|
|
6649
6768
|
default:
|
|
6650
6769
|
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
|
|
6651
6770
|
}
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -950,6 +950,63 @@ total++;
|
|
|
950
950
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
|
|
951
951
|
}
|
|
952
952
|
|
|
953
|
+
// 1.9.56/57 회귀
|
|
954
|
+
total++;
|
|
955
|
+
{
|
|
956
|
+
// handoff 자동 lessons 재상기
|
|
957
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ha-'));
|
|
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: ✗ webhook 처리 실패\n', 'utf8');
|
|
961
|
+
cp.spawnSync(process.execPath, [CLI, 'task', 'add', 'webhook 처리 개선', '--status', 'in-progress', '--path', tmpC], { stdio: 'ignore', timeout: 10000 });
|
|
962
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'handoff', tmpC, '--no-drift-check', '--no-workflow-guide'], { encoding: 'utf8', timeout: 15000 });
|
|
963
|
+
const ok = /lessons 자동 재상기.*webhook|🧠.*webhook/.test(r.stdout);
|
|
964
|
+
console.log(ok ? '✓ B(1.9.56) handoff: lessons 자동 재상기 (현재 task 키워드 매칭)' : `✗ handoff lessons 실패`);
|
|
965
|
+
if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
total++;
|
|
969
|
+
{
|
|
970
|
+
// session close --suggest
|
|
971
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sc-'));
|
|
972
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
973
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'session', 'close', tmpC, '--suggest'], { encoding: 'utf8', timeout: 30000 });
|
|
974
|
+
const ok = /다음 라운드 추천|drift 상태/.test(r.stdout);
|
|
975
|
+
console.log(ok ? '✓ B(1.9.57) session close --suggest: drift + skill suggest 통합' : `✗ --suggest 실패`);
|
|
976
|
+
if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// 1.9.54/55 회귀
|
|
980
|
+
total++;
|
|
981
|
+
{
|
|
982
|
+
// lessons --auto 키워드 자동 추출
|
|
983
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-la-'));
|
|
984
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
985
|
+
fs.writeFileSync(path.join(tmpC, '.harness', 'review-evidence.md'),
|
|
986
|
+
'## 2026-04-01\nNote: ✗ payment 처리 실패 — 검수 누락\n', 'utf8');
|
|
987
|
+
cp.spawnSync(process.execPath, [CLI, 'task', 'add', 'payment 검증 작업', '--status', 'in-progress', '--path', tmpC], { stdio: 'ignore', timeout: 10000 });
|
|
988
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'lessons', '--path', tmpC, '--auto', '--limit', '5'], { encoding: 'utf8', timeout: 15000 });
|
|
989
|
+
const ok = /추출 키워드.*payment/.test(r.stdout) && /payment.*실패/.test(r.stdout);
|
|
990
|
+
console.log(ok ? '✓ B(1.9.54) lessons --auto: in-progress task 키워드 자동 추출 + 매칭' : `✗ lessons --auto 실패`);
|
|
991
|
+
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
total++;
|
|
995
|
+
{
|
|
996
|
+
// MCP server tools/list 12 도구 (1.9.55)
|
|
997
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], {
|
|
998
|
+
encoding: 'utf8', timeout: 8000,
|
|
999
|
+
input: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }) + '\n'
|
|
1000
|
+
});
|
|
1001
|
+
let resp = null;
|
|
1002
|
+
try { resp = JSON.parse(r.stdout.split('\n').filter(Boolean)[0]); } catch {}
|
|
1003
|
+
const tools = resp && resp.result && resp.result.tools;
|
|
1004
|
+
const hasNew = tools && tools.some(t => t.name === 'leerness_skill_suggest') && tools.some(t => t.name === 'leerness_lessons');
|
|
1005
|
+
const ok = tools && tools.length >= 12 && hasNew;
|
|
1006
|
+
console.log(ok ? `✓ B(1.9.55) MCP: ${tools.length} 도구 (skill_suggest + lessons 추가)` : `✗ MCP 12 도구 실패`);
|
|
1007
|
+
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1008
|
+
}
|
|
1009
|
+
|
|
953
1010
|
// 1.9.53 회귀: skill suggest 자동 학습
|
|
954
1011
|
total++;
|
|
955
1012
|
{
|