leerness 1.9.67 → 1.9.69
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 +52 -0
- package/README.md +4 -2
- package/bin/harness.js +84 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.69 — 2026-05-19
|
|
4
|
+
|
|
5
|
+
**handoff에 skill-suggestions.md rolling history hit 노출 (1.9.67 + 1.9.68 결합)**.
|
|
6
|
+
|
|
7
|
+
### Added — handoff history hit
|
|
8
|
+
- 매 `leerness handoff`마다 현재 task 키워드와 매칭되는 **이전 세션의 `skill match` 결과**를 함께 노출.
|
|
9
|
+
- 매칭: 현재 키워드 (≥4자, 7할 길이)의 fuzzy regex로 `skill-suggestions.md`의 query 헤더 검색.
|
|
10
|
+
- 표시: 최근 2건 + 각 블록의 top 2 매치 라인.
|
|
11
|
+
- AI 에이전트는 **이전 세션과 같은 결정을 일관되게 유지** 가능.
|
|
12
|
+
- 끄기: 같은 `--no-skill-suggest` / `LEERNESS_NO_SKILL_SUGGEST=1`.
|
|
13
|
+
|
|
14
|
+
### Internal
|
|
15
|
+
- `_loadSkillHistory(root)` + `_SKILL_HISTORY_CACHE` — mtime 기반 메모리 캐시 (1.9.65/66/67 캐시 패밀리 연속).
|
|
16
|
+
- 같은 프로세스에서 `_lidx` / `_SKILLS_LIST_CACHE` / `_USAGE_CACHE`와 함께 lifetime 공유.
|
|
17
|
+
|
|
18
|
+
### Verified
|
|
19
|
+
- stress-v15 — history hit 노출 + 비매칭 시 출력 안 함 + mtime invalidation + 누적 회귀.
|
|
20
|
+
- e2e 회귀: 219/219 PASS 유지.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1.9.68 — 2026-05-19
|
|
25
|
+
|
|
26
|
+
**`skill match` rolling history 자동 누적 + 종합 회귀**.
|
|
27
|
+
|
|
28
|
+
### Added — skill match rolling history (default ON)
|
|
29
|
+
- `leerness skill match <query>` 호출 시 결과를 `.harness/skill-suggestions.md`에 append 누적.
|
|
30
|
+
- frontmatter: `leernessRole: skill-suggestions`, `readWhen: ['skill 결정 전', '세션 시작']`.
|
|
31
|
+
- 형식:
|
|
32
|
+
```
|
|
33
|
+
## YYYY-MM-DD HH:MM:SS — query "<keyword>"
|
|
34
|
+
- Algorithm: jaccard|embedding
|
|
35
|
+
- Top N matches:
|
|
36
|
+
- [점수] skill-id — description
|
|
37
|
+
```
|
|
38
|
+
- AI 에이전트가 같은 키워드를 반복 검색하지 않고 이력 참조 가능.
|
|
39
|
+
- 끄기: `--no-save` 또는 `LEERNESS_NO_SKILL_HISTORY=1`.
|
|
40
|
+
|
|
41
|
+
### Updated
|
|
42
|
+
- `_banner` quickStart: 1.9.68 안내 라인 추가.
|
|
43
|
+
|
|
44
|
+
### Verified — 종합 회귀 + 성능 측정
|
|
45
|
+
- stress-v14 (1.9.68 + 1.9.43~67 누적 회귀 + 성능 벤치마크) — 모든 시나리오 PASS.
|
|
46
|
+
- 이전 중요 기능 12종 정상 동작 검증:
|
|
47
|
+
- MCP 13 도구 / drift check / benchmark scenario / skill suggest / lessons --auto
|
|
48
|
+
- session close --suggest default / audit --strict / install 별칭 / task export
|
|
49
|
+
- handoff 자동 skill 추천 (1.9.67) / listAllSkills 캐시 (1.9.66) / usage-stats 캐시 (1.9.65)
|
|
50
|
+
- 성능 (warm-up 적용): status / handoff / drift / audit / skill list / skill match.
|
|
51
|
+
- e2e 회귀: 219/219 PASS 유지.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
3
55
|
## 1.9.67 — 2026-05-19
|
|
4
56
|
|
|
5
57
|
**handoff 자동 skill 추천 default ON + lessons 인덱스에 task-log 통합**.
|
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.69 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.69** — **handoff에 skill-suggestions.md history hit 노출** (fuzzy 매칭, 최근 2건 + top 2 매치). AI가 이전 세션 결정을 일관 유지. mtime 기반 캐시 (1.9.65/66/67 캐시 패밀리 연속).
|
|
437
|
+
- **1.9.68** — **`skill match` 결과 → `.harness/skill-suggestions.md` rolling history 자동 누적** (AI가 다음 세션에 이전 추천 참조 가능). `--no-save` / `LEERNESS_NO_SKILL_HISTORY=1`로 끄기.
|
|
436
438
|
- **1.9.67** — **handoff 자동 skill 추천 default ON** (jaccard 매칭) + lessons 인덱스에 task-log.md 실패 라인 통합 (회수 범위 확장).
|
|
437
439
|
- **1.9.66** — **성능 최적화 2차 + MCP 13번째 도구**. `listAllSkills` 메모리 캐시 (skill list/info/match/discover/suggest 공유) + MCP `leerness_task_export` 추가 (TodoWrite 양방향 sync 외부 노출).
|
|
438
440
|
- **1.9.65** — **성능 최적화 1차** — usage-stats 메모리 캐시 + lessons 인덱스 캐시 (mtime invalidation). handoff -37% · drift -19% · audit -29% · skill list -17% · 100-task handoff -42% · status -48% (vs 1.9.64).
|
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.69';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -1844,6 +1844,26 @@ function handoff(root) {
|
|
|
1844
1844
|
log(dim(` → 전체: leerness skill match "${String(latestRow.request).slice(0, 60)}"`));
|
|
1845
1845
|
log('');
|
|
1846
1846
|
}
|
|
1847
|
+
// 1.9.69: skill-suggestions.md rolling history hit — 이전 세션 매칭 결과 노출
|
|
1848
|
+
const hist = _loadSkillHistory(root);
|
|
1849
|
+
if (hist.blocks.length) {
|
|
1850
|
+
const histRe = new RegExp(escapeRegex(keyword.slice(0, Math.max(4, Math.floor(keyword.length * 0.7)))), 'i');
|
|
1851
|
+
const hits = hist.blocks.filter(b => histRe.test(b.query)).slice(0, 2);
|
|
1852
|
+
if (hits.length) {
|
|
1853
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
1854
|
+
const blu = s => isTty ? `\x1b[34m${s}\x1b[0m` : s;
|
|
1855
|
+
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
1856
|
+
log(blu(`## 📒 이전 skill match 이력 (1.9.69) — 키워드 "${keyword}" 관련`));
|
|
1857
|
+
for (const h of hits) {
|
|
1858
|
+
// 블록에서 첫 1~2개 match 줄만 추출
|
|
1859
|
+
const matchLines = (h.block.match(/^\s*-\s*\[[\d.]+\][^\n]+/gm) || []).slice(0, 2);
|
|
1860
|
+
log(dim(` [${h.at}] query "${h.query}"`));
|
|
1861
|
+
for (const ml of matchLines) log(dim(` ${ml.trim()}`));
|
|
1862
|
+
}
|
|
1863
|
+
log(dim(` → 전체 이력: cat .harness/skill-suggestions.md`));
|
|
1864
|
+
log('');
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1847
1867
|
}
|
|
1848
1868
|
} catch {}
|
|
1849
1869
|
}
|
|
@@ -3196,9 +3216,10 @@ function _banner(opts = {}) {
|
|
|
3196
3216
|
lines.push('');
|
|
3197
3217
|
for (const ln of lines) log(ln);
|
|
3198
3218
|
if (opts.quickStart) {
|
|
3199
|
-
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.
|
|
3219
|
+
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.69+ 워크플로)')));
|
|
3200
3220
|
log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트 + 외부 AI CLI 설정'));
|
|
3201
|
-
log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 + lessons
|
|
3221
|
+
log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 + lessons + 매칭 skill + 이전 history hit (1.9.69)'));
|
|
3222
|
+
log(' ' + C.green('npx leerness skill match "<query>"') + C.dim(' # 매칭 skill + rolling history 자동 누적 (1.9.68)'));
|
|
3202
3223
|
log(' ' + C.green('npx leerness verify-claim T-0001 --run-tests') + C.dim(' # AI 거짓 완료 자동 검증'));
|
|
3203
3224
|
log(' ' + C.green('npx leerness session close .') + C.dim(' # 마감 + 다음 라운드 추천 (default)'));
|
|
3204
3225
|
log('');
|
|
@@ -6190,6 +6211,33 @@ function driftCheckCmd(root, opts = {}) {
|
|
|
6190
6211
|
if (level === '🔴 critical') process.exitCode = 1;
|
|
6191
6212
|
}
|
|
6192
6213
|
|
|
6214
|
+
// 1.9.69: skill-suggestions.md rolling history 인덱스 — mtime 기반 캐시
|
|
6215
|
+
// handoff에서 같은 키워드 과거 추천 결과를 즉시 노출 (재매칭 불필요)
|
|
6216
|
+
const _SKILL_HISTORY_CACHE = new Map();
|
|
6217
|
+
function _loadSkillHistory(root) {
|
|
6218
|
+
const p = path.join(absRoot(root), '.harness', 'skill-suggestions.md');
|
|
6219
|
+
if (!exists(p)) return { mtime: 0, blocks: [] };
|
|
6220
|
+
let mtime = 0;
|
|
6221
|
+
try { mtime = fs.statSync(p).mtimeMs; } catch {}
|
|
6222
|
+
const key = absRoot(root);
|
|
6223
|
+
const cached = _SKILL_HISTORY_CACHE.get(key);
|
|
6224
|
+
if (cached && cached.mtime === mtime) return cached;
|
|
6225
|
+
const txt = read(p);
|
|
6226
|
+
const blocks = [];
|
|
6227
|
+
for (const block of txt.split(/\n(?=## )/)) {
|
|
6228
|
+
if (!block.startsWith('## ')) continue;
|
|
6229
|
+
// 헤더에서 timestamp + query 추출
|
|
6230
|
+
const h = block.match(/^## ([\d-]+ [\d:]+) — query "([^"]+)"/m);
|
|
6231
|
+
if (!h) continue;
|
|
6232
|
+
blocks.push({ at: h[1], query: h[2], block });
|
|
6233
|
+
}
|
|
6234
|
+
// 최신순 (마지막에 append되므로 reverse)
|
|
6235
|
+
blocks.reverse();
|
|
6236
|
+
const idx = { mtime, blocks };
|
|
6237
|
+
_SKILL_HISTORY_CACHE.set(key, idx);
|
|
6238
|
+
return idx;
|
|
6239
|
+
}
|
|
6240
|
+
|
|
6193
6241
|
// 1.9.65: lessons blocks 인덱스 — evidence/decisions 파일 read + split을 1회로
|
|
6194
6242
|
// 1.9.67: task-log.md 실패 라인도 인덱스에 포함 (mtime 기반 invalidation)
|
|
6195
6243
|
// key: root → { evidenceMtime, decisionsMtime, taskLogMtime, evidence/decisions/taskLogFails: [{title, block}] }
|
|
@@ -6820,6 +6868,13 @@ async function skillMatchCmd(root, query) {
|
|
|
6820
6868
|
})).sort((a, b) => b.score - a.score);
|
|
6821
6869
|
}
|
|
6822
6870
|
const top = ranked.filter(r => r.score > 0).slice(0, 5);
|
|
6871
|
+
// 1.9.68: rolling history 자동 누적 (.harness/skill-suggestions.md) — default ON
|
|
6872
|
+
// 끄기: --no-save 또는 LEERNESS_NO_SKILL_HISTORY=1
|
|
6873
|
+
if (!has('--no-save') && process.env.LEERNESS_NO_SKILL_HISTORY !== '1') {
|
|
6874
|
+
try {
|
|
6875
|
+
_appendSkillSuggestion(root, { query, useEmbedding, top });
|
|
6876
|
+
} catch {}
|
|
6877
|
+
}
|
|
6823
6878
|
if (has('--json')) {
|
|
6824
6879
|
log(JSON.stringify({ query, total: skills.length, matched: top.length, top: top.map(({ dir, ...rest }) => rest) }, null, 2));
|
|
6825
6880
|
return;
|
|
@@ -6839,6 +6894,32 @@ async function skillMatchCmd(root, query) {
|
|
|
6839
6894
|
}
|
|
6840
6895
|
log('');
|
|
6841
6896
|
log(`💡 사용: \`cat ${rel(root, top[0].dir)}/SKILL.md\` 또는 메인 에이전트가 이 skill 본문을 참고`);
|
|
6897
|
+
log(`📒 자동 누적: .harness/skill-suggestions.md (--no-save로 끄기)`);
|
|
6898
|
+
}
|
|
6899
|
+
|
|
6900
|
+
// 1.9.68: skill match rolling history append (.harness/skill-suggestions.md)
|
|
6901
|
+
// AI가 다음 세션에 이전 추천을 참조 가능 — readWhen: '세션 시작', 'skill 결정 전'
|
|
6902
|
+
function _appendSkillSuggestion(root, { query, useEmbedding, top }) {
|
|
6903
|
+
const p = path.join(absRoot(root), '.harness', 'skill-suggestions.md');
|
|
6904
|
+
if (!exists(p)) {
|
|
6905
|
+
// 신규 파일 — frontmatter + 안내
|
|
6906
|
+
const fm = `---\nleernessRole: skill-suggestions\nreadWhen:\n - skill 결정 전\n - 세션 시작\nupdateWhen:\n - leerness skill match 호출 시 자동 누적 (1.9.68)\ndoNotStore:\n - 실제 토큰\n - 비밀번호\n - 운영 쿠키\n - 민감한 개인정보 원문\n---\n<!-- leerness:managed -->\n# Skill Suggestions (Rolling History)\n\n매 \`leerness skill match\` 호출이 여기 누적됩니다. AI 에이전트는 다음 세션에 같은 키워드를 다시 검색하지 말고 이력을 먼저 참조하세요.\n\n`;
|
|
6907
|
+
mkdirp(path.dirname(p));
|
|
6908
|
+
writeUtf8(p, fm);
|
|
6909
|
+
}
|
|
6910
|
+
const algo = useEmbedding ? 'embedding' : 'jaccard';
|
|
6911
|
+
const ts = new Date().toISOString();
|
|
6912
|
+
let block = `\n## ${ts.slice(0, 19).replace('T', ' ')} — query "${(query || '').slice(0, 80)}"\n`;
|
|
6913
|
+
block += `- Algorithm: ${algo}\n`;
|
|
6914
|
+
if (!top.length) {
|
|
6915
|
+
block += `- Matched: 0 — 다른 키워드 또는 \`leerness skill discover\` 권장\n`;
|
|
6916
|
+
} else {
|
|
6917
|
+
block += `- Top ${top.length} matches:\n`;
|
|
6918
|
+
for (const r of top) {
|
|
6919
|
+
block += ` - [${r.score.toFixed(2)}] ${r.id} — ${(r.description || '').slice(0, 80)}\n`;
|
|
6920
|
+
}
|
|
6921
|
+
}
|
|
6922
|
+
append(p, block);
|
|
6842
6923
|
}
|
|
6843
6924
|
|
|
6844
6925
|
// 1.9.43: skill export-all — 모든 자체 skill을 agentskills.io 표준 SKILL.md로 일괄 export
|