leerness 1.9.52 → 1.9.53
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 +31 -0
- package/README.md +3 -2
- package/bin/harness.js +79 -1
- package/package.json +1 -1
- package/scripts/e2e.js +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.53 — 2026-05-19
|
|
4
|
+
|
|
5
|
+
**`leerness skill suggest` — Hermes-style 자동 학습 (사용 패턴 → skill 후보 자동 제안)**.
|
|
6
|
+
|
|
7
|
+
### 배경
|
|
8
|
+
1.9.2부터 `skill learn` / `skill use` / `skill optimize` / `skill consolidate` / `lessons` / `rule add` 등 자체 학습 인프라가 있었으나, **모두 명시 호출 필요**. Hermes처럼 *사용 중* 자동으로 새 skill을 만들지 못함.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`leerness skill suggest [--min N] [--days N] [--json]`** — Hermes-style 자동 학습의 leerness 버전:
|
|
12
|
+
- **task-log.md** — `` `leerness X` `` 명령 인용 패턴 감지
|
|
13
|
+
- **progress-tracker.md** — request/nextAction 컬럼의 4자+ 키워드
|
|
14
|
+
- **usage-stats.json** — 명령별 누적 카운트
|
|
15
|
+
- 임계 (`--min`, 기본 3회) 이상 + **기존 skill에 없는** 키워드만 후보로
|
|
16
|
+
- `--days N` lookback (기본 30일)
|
|
17
|
+
- 출처 (`task-log` / `progress` / `usage`) 자동 분류
|
|
18
|
+
- 실 워크스페이스 검증: 본 프로젝트에서 6 후보 자동 감지 (leerness 22회, publish 14회, github 5회 등)
|
|
19
|
+
|
|
20
|
+
### Hermes vs leerness 학습 비교 (1.9.53 후)
|
|
21
|
+
| 영역 | Hermes | leerness |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| 새 skill 자동 생성 | ✅ LLM 기반 | ⚠ 후보 제안만 (수동 등록 권장) |
|
|
24
|
+
| **반복 패턴 감지** | ✅ | ✅ **1.9.53 신규** |
|
|
25
|
+
| 사용 카운트 추적 | ✅ | ✅ 1.9.38 |
|
|
26
|
+
| 중복 자동 통합 | ✅ | ✅ 1.9.2 `skill consolidate` |
|
|
27
|
+
| 외부 docs 학습 | ✅ | ✅ 1.9.2 `skill learn --doc` |
|
|
28
|
+
|
|
29
|
+
### 검증 (필수 stress-v5)
|
|
30
|
+
- O1-O5 (skill suggest 시나리오) 5/5 PASS
|
|
31
|
+
- P1-P5 (1.9.43~52 누적 회귀) 5/5 PASS
|
|
32
|
+
- **stress-v5: 10/10 PASS** + e2e: **206/206 PASS**
|
|
33
|
+
|
|
3
34
|
## 1.9.52 — 2026-05-19
|
|
4
35
|
|
|
5
36
|
**`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.53 AI Agent Reliability Harness ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · prevent drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
|
@@ -433,6 +433,7 @@ npm test # = node ./scripts/e2e.js
|
|
|
433
433
|
|
|
434
434
|
## 변경 이력 (최근)
|
|
435
435
|
|
|
436
|
+
- **1.9.53** — `leerness skill suggest` — task-log / progress-tracker / usage-stats에서 반복 패턴 **자동 감지 → 새 skill 후보 제안** (Hermes-style 자동 학습의 leerness 버전).
|
|
436
437
|
- **1.9.52** — `skill discover` 카탈로그 형식 다양성 — JSON manifest / RSS·Atom / Markdown / llms.txt URL 4 형식 자동 감지 (`_parseSkillCatalog`).
|
|
437
438
|
- **1.9.51** — `benchmark --scenario <id|all>` — leerness 고유 가치 시나리오 4종 (거짓 완료 / 사양 불일치 / drift / BOM) **command 한 번에 정량 증명**.
|
|
438
439
|
- **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.53';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -6361,6 +6361,83 @@ function benchmarkCmd(root) {
|
|
|
6361
6361
|
log('💡 시뮬레이션은 정성적 추정 — 실 측정은 별도 환경 필요 (사용자 환경)');
|
|
6362
6362
|
}
|
|
6363
6363
|
|
|
6364
|
+
// 1.9.53: leerness skill suggest — task-log + usage-stats에서 반복 패턴 감지 → 새 skill 후보 제안
|
|
6365
|
+
// Hermes-style 자동 학습의 leerness 버전. 명시적 `skill learn` 호출 없이도 패턴 추출.
|
|
6366
|
+
function skillSuggestCmd(root) {
|
|
6367
|
+
root = absRoot(root || process.cwd());
|
|
6368
|
+
const minOccurrence = parseInt(arg('--min', '3'), 10);
|
|
6369
|
+
const lookbackDays = parseInt(arg('--days', '30'), 10);
|
|
6370
|
+
const cutoff = Date.now() - lookbackDays * 86400000;
|
|
6371
|
+
const seen = {}; // keyword → { count, samples, files }
|
|
6372
|
+
// 1) task-log.md 라인 분석
|
|
6373
|
+
const taskLog = taskLogPath(root);
|
|
6374
|
+
if (exists(taskLog)) {
|
|
6375
|
+
const body = read(taskLog);
|
|
6376
|
+
// 날짜 헤더 ## YYYY-MM-DD 안의 라인들
|
|
6377
|
+
const blocks = body.split(/^## \d{4}-\d{2}-\d{2}/m);
|
|
6378
|
+
for (const block of blocks) {
|
|
6379
|
+
// 명령 인용 `leerness X` 또는 키워드 (3+ chars)
|
|
6380
|
+
for (const m of block.matchAll(/`leerness\s+([a-z][\w-]+(?:\s+[a-z][\w-]+)?)`/g)) {
|
|
6381
|
+
const cmd = m[1].trim();
|
|
6382
|
+
seen[cmd] = seen[cmd] || { count: 0, samples: [], source: 'task-log' };
|
|
6383
|
+
seen[cmd].count++;
|
|
6384
|
+
if (seen[cmd].samples.length < 3) seen[cmd].samples.push(block.slice(0, 80).replace(/\n/g, ' '));
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
// 2) progress-tracker request 컬럼 분석
|
|
6389
|
+
const rows = readProgressRows(root);
|
|
6390
|
+
for (const row of rows) {
|
|
6391
|
+
const text = (row.request || '') + ' ' + (row.nextAction || '');
|
|
6392
|
+
// 도메인 키워드 (한글 + 영어 단어, 3자 이상)
|
|
6393
|
+
for (const m of text.toLowerCase().matchAll(/[\w가-힣]{4,}/g)) {
|
|
6394
|
+
const kw = m[0];
|
|
6395
|
+
if (/^\d+$/.test(kw)) continue;
|
|
6396
|
+
if (['이런', '저런', '하다', '하고', '있는', '하지', '에서'].includes(kw)) continue;
|
|
6397
|
+
seen[kw] = seen[kw] || { count: 0, samples: [], source: 'progress' };
|
|
6398
|
+
seen[kw].count++;
|
|
6399
|
+
if (seen[kw].samples.length < 3) seen[kw].samples.push((row.request || '').slice(0, 60));
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
6402
|
+
// 3) usage-stats의 명령 카운트
|
|
6403
|
+
try {
|
|
6404
|
+
const stats = _readUsageStats(root);
|
|
6405
|
+
for (const [cmd, n] of Object.entries(stats.commands || {})) {
|
|
6406
|
+
if (n >= minOccurrence) {
|
|
6407
|
+
seen[`cmd:${cmd}`] = seen[`cmd:${cmd}`] || { count: 0, samples: [], source: 'usage' };
|
|
6408
|
+
seen[`cmd:${cmd}`].count = n;
|
|
6409
|
+
}
|
|
6410
|
+
}
|
|
6411
|
+
} catch {}
|
|
6412
|
+
// 4) 임계 이상 + 기존 skill에 없는 키워드만 필터
|
|
6413
|
+
const existing = new Set(Object.keys(listAllSkills(root)));
|
|
6414
|
+
const installed = _readInstalledSkills(root);
|
|
6415
|
+
const installedTokens = new Set(installed.flatMap(s => [..._tokenize(s.name + ' ' + s.description)]));
|
|
6416
|
+
const candidates = Object.entries(seen)
|
|
6417
|
+
.filter(([kw, info]) => info.count >= minOccurrence)
|
|
6418
|
+
.filter(([kw]) => !existing.has(kw) && !installedTokens.has(kw.replace(/^cmd:/, '')))
|
|
6419
|
+
.map(([kw, info]) => ({ keyword: kw, ...info }))
|
|
6420
|
+
.sort((a, b) => b.count - a.count);
|
|
6421
|
+
if (has('--json')) { log(JSON.stringify({ minOccurrence, lookbackDays, candidates: candidates.slice(0, 20) }, null, 2)); return; }
|
|
6422
|
+
log(`# leerness skill suggest (1.9.53)`);
|
|
6423
|
+
log(`반복 패턴 자동 감지 (최소 ${minOccurrence}회, ${lookbackDays}일 이내)`);
|
|
6424
|
+
log('');
|
|
6425
|
+
if (!candidates.length) {
|
|
6426
|
+
log(' (아직 패턴 부족 — task-log/progress-tracker에 작업이 더 누적되면 자동 감지)');
|
|
6427
|
+
return;
|
|
6428
|
+
}
|
|
6429
|
+
log(`발견된 후보: ${candidates.length}건`);
|
|
6430
|
+
log('');
|
|
6431
|
+
log('| 키워드/명령 | 출처 | 등장 횟수 | 예시 |');
|
|
6432
|
+
log('|---|---|---:|---|');
|
|
6433
|
+
for (const c of candidates.slice(0, 10)) {
|
|
6434
|
+
log(`| ${c.keyword} | ${c.source} | ${c.count} | ${(c.samples[0] || '').replace(/\|/g, '\\|').slice(0, 50)} |`);
|
|
6435
|
+
}
|
|
6436
|
+
log('');
|
|
6437
|
+
log(`💡 신규 skill로 등록 권장:`);
|
|
6438
|
+
log(` leerness skill learn <id> --capability "${candidates[0].keyword}" --note "1.9.53 auto-suggest"`);
|
|
6439
|
+
}
|
|
6440
|
+
|
|
6364
6441
|
// 1.9.45: skill match <query> — 설치된 SKILL.md description ↔ 사용자 요청 키워드 매칭 추천
|
|
6365
6442
|
// jaccard similarity (단어 집합 교집합/합집합).
|
|
6366
6443
|
function _tokenize(s) {
|
|
@@ -6960,6 +7037,7 @@ async function main() {
|
|
|
6960
7037
|
if (cmd === 'skill' && args[1] === 'match') return skillMatchCmd(absRoot(arg('--path', process.cwd())), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
|
|
6961
7038
|
if (cmd === 'benchmark') return benchmarkCmd(absRoot(args[1] || arg('--path', process.cwd())));
|
|
6962
7039
|
if (cmd === 'skill' && args[1] === 'publish') return skillPublishCmd(absRoot(arg('--path', process.cwd())));
|
|
7040
|
+
if (cmd === 'skill' && args[1] === 'suggest') return skillSuggestCmd(absRoot(arg('--path', process.cwd())));
|
|
6963
7041
|
if (cmd === 'mcp' && args[1] === 'serve') return mcpServeCmd(absRoot(arg('--path', process.cwd())));
|
|
6964
7042
|
if (cmd === 'gate') return gate(args[1] || process.cwd());
|
|
6965
7043
|
if (cmd === 'verify-code') return verifyCodeCmd(args[1] || process.cwd());
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -950,6 +950,23 @@ total++;
|
|
|
950
950
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
|
|
951
951
|
}
|
|
952
952
|
|
|
953
|
+
// 1.9.53 회귀: skill suggest 자동 학습
|
|
954
|
+
total++;
|
|
955
|
+
{
|
|
956
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sg-'));
|
|
957
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
958
|
+
// 반복 키워드 6회
|
|
959
|
+
for (let i = 0; i < 6; i++) {
|
|
960
|
+
cp.spawnSync(process.execPath, [CLI, 'task', 'add', `autosuggestkw 작업 ${i}`, '--path', tmpC], { stdio: 'ignore', timeout: 10000 });
|
|
961
|
+
}
|
|
962
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'skill', 'suggest', '--path', tmpC, '--min', '3', '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
963
|
+
let j = null;
|
|
964
|
+
try { j = JSON.parse(r.stdout); } catch {}
|
|
965
|
+
const ok = j && j.candidates && j.candidates.some(c => /autosuggestkw/.test(c.keyword) && c.count >= 3);
|
|
966
|
+
console.log(ok ? '✓ B(1.9.53) skill suggest: progress-tracker 반복 패턴 자동 감지 (Hermes-style)' : `✗ suggest 실패`);
|
|
967
|
+
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
968
|
+
}
|
|
969
|
+
|
|
953
970
|
// 1.9.51/52 회귀
|
|
954
971
|
total++;
|
|
955
972
|
{
|