leerness 1.9.52 → 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 +65 -0
- package/README.md +5 -2
- package/bin/harness.js +111 -3
- package/package.json +1 -1
- package/scripts/e2e.js +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
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
|
+
|
|
37
|
+
## 1.9.53 — 2026-05-19
|
|
38
|
+
|
|
39
|
+
**`leerness skill suggest` — Hermes-style 자동 학습 (사용 패턴 → skill 후보 자동 제안)**.
|
|
40
|
+
|
|
41
|
+
### 배경
|
|
42
|
+
1.9.2부터 `skill learn` / `skill use` / `skill optimize` / `skill consolidate` / `lessons` / `rule add` 등 자체 학습 인프라가 있었으나, **모두 명시 호출 필요**. Hermes처럼 *사용 중* 자동으로 새 skill을 만들지 못함.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- **`leerness skill suggest [--min N] [--days N] [--json]`** — Hermes-style 자동 학습의 leerness 버전:
|
|
46
|
+
- **task-log.md** — `` `leerness X` `` 명령 인용 패턴 감지
|
|
47
|
+
- **progress-tracker.md** — request/nextAction 컬럼의 4자+ 키워드
|
|
48
|
+
- **usage-stats.json** — 명령별 누적 카운트
|
|
49
|
+
- 임계 (`--min`, 기본 3회) 이상 + **기존 skill에 없는** 키워드만 후보로
|
|
50
|
+
- `--days N` lookback (기본 30일)
|
|
51
|
+
- 출처 (`task-log` / `progress` / `usage`) 자동 분류
|
|
52
|
+
- 실 워크스페이스 검증: 본 프로젝트에서 6 후보 자동 감지 (leerness 22회, publish 14회, github 5회 등)
|
|
53
|
+
|
|
54
|
+
### Hermes vs leerness 학습 비교 (1.9.53 후)
|
|
55
|
+
| 영역 | Hermes | leerness |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| 새 skill 자동 생성 | ✅ LLM 기반 | ⚠ 후보 제안만 (수동 등록 권장) |
|
|
58
|
+
| **반복 패턴 감지** | ✅ | ✅ **1.9.53 신규** |
|
|
59
|
+
| 사용 카운트 추적 | ✅ | ✅ 1.9.38 |
|
|
60
|
+
| 중복 자동 통합 | ✅ | ✅ 1.9.2 `skill consolidate` |
|
|
61
|
+
| 외부 docs 학습 | ✅ | ✅ 1.9.2 `skill learn --doc` |
|
|
62
|
+
|
|
63
|
+
### 검증 (필수 stress-v5)
|
|
64
|
+
- O1-O5 (skill suggest 시나리오) 5/5 PASS
|
|
65
|
+
- P1-P5 (1.9.43~52 누적 회귀) 5/5 PASS
|
|
66
|
+
- **stress-v5: 10/10 PASS** + e2e: **206/206 PASS**
|
|
67
|
+
|
|
3
68
|
## 1.9.52 — 2026-05-19
|
|
4
69
|
|
|
5
70
|
**`skill discover` 카탈로그 형식 다양성 (JSON/RSS/Markdown/llms.txt 자동 감지)**.
|
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.55 AI Agent Reliability Harness ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · prevent drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
|
@@ -433,6 +433,9 @@ 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 자동 매칭·재상기.
|
|
438
|
+
- **1.9.53** — `leerness skill suggest` — task-log / progress-tracker / usage-stats에서 반복 패턴 **자동 감지 → 새 skill 후보 제안** (Hermes-style 자동 학습의 leerness 버전).
|
|
436
439
|
- **1.9.52** — `skill discover` 카탈로그 형식 다양성 — JSON manifest / RSS·Atom / Markdown / llms.txt URL 4 형식 자동 감지 (`_parseSkillCatalog`).
|
|
437
440
|
- **1.9.51** — `benchmark --scenario <id|all>` — leerness 고유 가치 시나리오 4종 (거짓 완료 / 사양 불일치 / drift / BOM) **command 한 번에 정량 증명**.
|
|
438
441
|
- **1.9.50** — `skill match --embedding` — Ollama API 코사인 유사도 매칭 (opt-in, 실패 시 jaccard fallback).
|
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.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
|
-
|
|
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)) : '';
|
|
@@ -6361,6 +6387,83 @@ function benchmarkCmd(root) {
|
|
|
6361
6387
|
log('💡 시뮬레이션은 정성적 추정 — 실 측정은 별도 환경 필요 (사용자 환경)');
|
|
6362
6388
|
}
|
|
6363
6389
|
|
|
6390
|
+
// 1.9.53: leerness skill suggest — task-log + usage-stats에서 반복 패턴 감지 → 새 skill 후보 제안
|
|
6391
|
+
// Hermes-style 자동 학습의 leerness 버전. 명시적 `skill learn` 호출 없이도 패턴 추출.
|
|
6392
|
+
function skillSuggestCmd(root) {
|
|
6393
|
+
root = absRoot(root || process.cwd());
|
|
6394
|
+
const minOccurrence = parseInt(arg('--min', '3'), 10);
|
|
6395
|
+
const lookbackDays = parseInt(arg('--days', '30'), 10);
|
|
6396
|
+
const cutoff = Date.now() - lookbackDays * 86400000;
|
|
6397
|
+
const seen = {}; // keyword → { count, samples, files }
|
|
6398
|
+
// 1) task-log.md 라인 분석
|
|
6399
|
+
const taskLog = taskLogPath(root);
|
|
6400
|
+
if (exists(taskLog)) {
|
|
6401
|
+
const body = read(taskLog);
|
|
6402
|
+
// 날짜 헤더 ## YYYY-MM-DD 안의 라인들
|
|
6403
|
+
const blocks = body.split(/^## \d{4}-\d{2}-\d{2}/m);
|
|
6404
|
+
for (const block of blocks) {
|
|
6405
|
+
// 명령 인용 `leerness X` 또는 키워드 (3+ chars)
|
|
6406
|
+
for (const m of block.matchAll(/`leerness\s+([a-z][\w-]+(?:\s+[a-z][\w-]+)?)`/g)) {
|
|
6407
|
+
const cmd = m[1].trim();
|
|
6408
|
+
seen[cmd] = seen[cmd] || { count: 0, samples: [], source: 'task-log' };
|
|
6409
|
+
seen[cmd].count++;
|
|
6410
|
+
if (seen[cmd].samples.length < 3) seen[cmd].samples.push(block.slice(0, 80).replace(/\n/g, ' '));
|
|
6411
|
+
}
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
6414
|
+
// 2) progress-tracker request 컬럼 분석
|
|
6415
|
+
const rows = readProgressRows(root);
|
|
6416
|
+
for (const row of rows) {
|
|
6417
|
+
const text = (row.request || '') + ' ' + (row.nextAction || '');
|
|
6418
|
+
// 도메인 키워드 (한글 + 영어 단어, 3자 이상)
|
|
6419
|
+
for (const m of text.toLowerCase().matchAll(/[\w가-힣]{4,}/g)) {
|
|
6420
|
+
const kw = m[0];
|
|
6421
|
+
if (/^\d+$/.test(kw)) continue;
|
|
6422
|
+
if (['이런', '저런', '하다', '하고', '있는', '하지', '에서'].includes(kw)) continue;
|
|
6423
|
+
seen[kw] = seen[kw] || { count: 0, samples: [], source: 'progress' };
|
|
6424
|
+
seen[kw].count++;
|
|
6425
|
+
if (seen[kw].samples.length < 3) seen[kw].samples.push((row.request || '').slice(0, 60));
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6428
|
+
// 3) usage-stats의 명령 카운트
|
|
6429
|
+
try {
|
|
6430
|
+
const stats = _readUsageStats(root);
|
|
6431
|
+
for (const [cmd, n] of Object.entries(stats.commands || {})) {
|
|
6432
|
+
if (n >= minOccurrence) {
|
|
6433
|
+
seen[`cmd:${cmd}`] = seen[`cmd:${cmd}`] || { count: 0, samples: [], source: 'usage' };
|
|
6434
|
+
seen[`cmd:${cmd}`].count = n;
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
} catch {}
|
|
6438
|
+
// 4) 임계 이상 + 기존 skill에 없는 키워드만 필터
|
|
6439
|
+
const existing = new Set(Object.keys(listAllSkills(root)));
|
|
6440
|
+
const installed = _readInstalledSkills(root);
|
|
6441
|
+
const installedTokens = new Set(installed.flatMap(s => [..._tokenize(s.name + ' ' + s.description)]));
|
|
6442
|
+
const candidates = Object.entries(seen)
|
|
6443
|
+
.filter(([kw, info]) => info.count >= minOccurrence)
|
|
6444
|
+
.filter(([kw]) => !existing.has(kw) && !installedTokens.has(kw.replace(/^cmd:/, '')))
|
|
6445
|
+
.map(([kw, info]) => ({ keyword: kw, ...info }))
|
|
6446
|
+
.sort((a, b) => b.count - a.count);
|
|
6447
|
+
if (has('--json')) { log(JSON.stringify({ minOccurrence, lookbackDays, candidates: candidates.slice(0, 20) }, null, 2)); return; }
|
|
6448
|
+
log(`# leerness skill suggest (1.9.53)`);
|
|
6449
|
+
log(`반복 패턴 자동 감지 (최소 ${minOccurrence}회, ${lookbackDays}일 이내)`);
|
|
6450
|
+
log('');
|
|
6451
|
+
if (!candidates.length) {
|
|
6452
|
+
log(' (아직 패턴 부족 — task-log/progress-tracker에 작업이 더 누적되면 자동 감지)');
|
|
6453
|
+
return;
|
|
6454
|
+
}
|
|
6455
|
+
log(`발견된 후보: ${candidates.length}건`);
|
|
6456
|
+
log('');
|
|
6457
|
+
log('| 키워드/명령 | 출처 | 등장 횟수 | 예시 |');
|
|
6458
|
+
log('|---|---|---:|---|');
|
|
6459
|
+
for (const c of candidates.slice(0, 10)) {
|
|
6460
|
+
log(`| ${c.keyword} | ${c.source} | ${c.count} | ${(c.samples[0] || '').replace(/\|/g, '\\|').slice(0, 50)} |`);
|
|
6461
|
+
}
|
|
6462
|
+
log('');
|
|
6463
|
+
log(`💡 신규 skill로 등록 권장:`);
|
|
6464
|
+
log(` leerness skill learn <id> --capability "${candidates[0].keyword}" --note "1.9.53 auto-suggest"`);
|
|
6465
|
+
}
|
|
6466
|
+
|
|
6364
6467
|
// 1.9.45: skill match <query> — 설치된 SKILL.md description ↔ 사용자 요청 키워드 매칭 추천
|
|
6365
6468
|
// jaccard similarity (단어 집합 교집합/합집합).
|
|
6366
6469
|
function _tokenize(s) {
|
|
@@ -6529,7 +6632,9 @@ function mcpServeCmd(root) {
|
|
|
6529
6632
|
{ name: 'leerness_reuse_map', description: '워크스페이스 중복 함수/capability 자동 감지 (--all-apps + fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, allApps: { type: 'boolean' }, strictElements: { type: 'boolean' } } } },
|
|
6530
6633
|
{ name: 'leerness_whats_new', description: 'CHANGELOG 차분 자동 추출 (from → to 사이 신규 명령/플래그/파일)', inputSchema: { type: 'object', properties: { from: { type: 'string' }, to: { type: 'string' } } } },
|
|
6531
6634
|
{ name: 'leerness_usage_stats', description: 'leerness 명령별 누적 호출 통계 + drift 통계', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
6532
|
-
{ 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' } } } }
|
|
6533
6638
|
];
|
|
6534
6639
|
|
|
6535
6640
|
function send(obj) {
|
|
@@ -6569,6 +6674,8 @@ function mcpServeCmd(root) {
|
|
|
6569
6674
|
case 'leerness_whats_new': cliArgs = ['whats-new', '--path', targetPath, ...(args.from ? ['--from', args.from] : []), ...(args.to ? ['--to', args.to] : []), '--json']; break;
|
|
6570
6675
|
case 'leerness_usage_stats': cliArgs = ['usage', 'stats', targetPath, '--json']; break;
|
|
6571
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;
|
|
6572
6679
|
default:
|
|
6573
6680
|
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
|
|
6574
6681
|
}
|
|
@@ -6960,6 +7067,7 @@ async function main() {
|
|
|
6960
7067
|
if (cmd === 'skill' && args[1] === 'match') return skillMatchCmd(absRoot(arg('--path', process.cwd())), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
|
|
6961
7068
|
if (cmd === 'benchmark') return benchmarkCmd(absRoot(args[1] || arg('--path', process.cwd())));
|
|
6962
7069
|
if (cmd === 'skill' && args[1] === 'publish') return skillPublishCmd(absRoot(arg('--path', process.cwd())));
|
|
7070
|
+
if (cmd === 'skill' && args[1] === 'suggest') return skillSuggestCmd(absRoot(arg('--path', process.cwd())));
|
|
6963
7071
|
if (cmd === 'mcp' && args[1] === 'serve') return mcpServeCmd(absRoot(arg('--path', process.cwd())));
|
|
6964
7072
|
if (cmd === 'gate') return gate(args[1] || process.cwd());
|
|
6965
7073
|
if (cmd === 'verify-code') return verifyCodeCmd(args[1] || process.cwd());
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -950,6 +950,54 @@ 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
|
+
|
|
984
|
+
// 1.9.53 회귀: skill suggest 자동 학습
|
|
985
|
+
total++;
|
|
986
|
+
{
|
|
987
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sg-'));
|
|
988
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
989
|
+
// 반복 키워드 6회
|
|
990
|
+
for (let i = 0; i < 6; i++) {
|
|
991
|
+
cp.spawnSync(process.execPath, [CLI, 'task', 'add', `autosuggestkw 작업 ${i}`, '--path', tmpC], { stdio: 'ignore', timeout: 10000 });
|
|
992
|
+
}
|
|
993
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'skill', 'suggest', '--path', tmpC, '--min', '3', '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
994
|
+
let j = null;
|
|
995
|
+
try { j = JSON.parse(r.stdout); } catch {}
|
|
996
|
+
const ok = j && j.candidates && j.candidates.some(c => /autosuggestkw/.test(c.keyword) && c.count >= 3);
|
|
997
|
+
console.log(ok ? '✓ B(1.9.53) skill suggest: progress-tracker 반복 패턴 자동 감지 (Hermes-style)' : `✗ suggest 실패`);
|
|
998
|
+
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
999
|
+
}
|
|
1000
|
+
|
|
953
1001
|
// 1.9.51/52 회귀
|
|
954
1002
|
total++;
|
|
955
1003
|
{
|