leerness 1.9.65 → 1.9.66
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 +18 -0
- package/README.md +3 -2
- package/bin/harness.js +31 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.66 — 2026-05-19
|
|
4
|
+
|
|
5
|
+
**성능 최적화 2차 + MCP 13번째 도구**.
|
|
6
|
+
|
|
7
|
+
### Performance
|
|
8
|
+
- **`listAllSkills` 메모리 캐시 (`_SKILLS_LIST_CACHE`)** — userSkillsDir mtime 기반 캐시. `skill list/info/match/discover/suggest` 가 같은 인덱스 공유.
|
|
9
|
+
- `saveUserSkill`/`skillRemove`에서 캐시 invalidate — skill 추가/제거 즉시 반영.
|
|
10
|
+
|
|
11
|
+
### MCP server — 13번째 도구
|
|
12
|
+
- **`leerness_task_export`** — 1.9.60 TodoWrite 호환 JSON을 외부 에이전트(Claude Code, Cursor 등)에 노출. `to: <path>` 또는 stdout JSON 모두 지원.
|
|
13
|
+
- MCP server 도구 카운트: 12 → **13**.
|
|
14
|
+
|
|
15
|
+
### Verified
|
|
16
|
+
- stress-v12 (1.9.66 검증) — listAllSkills 캐시 정합성 + MCP 13 도구 + warm-up 1회 시나리오 보강.
|
|
17
|
+
- e2e 회귀: 219/219 PASS 유지.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
3
21
|
## 1.9.65 — 2026-05-19
|
|
4
22
|
|
|
5
23
|
**성능 최적화 1차 — usage-stats 메모리 캐시 + lessons 인덱스 캐시**.
|
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.66 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.66** — **성능 최적화 2차 + MCP 13번째 도구**. `listAllSkills` 메모리 캐시 (skill list/info/match/discover/suggest 공유) + MCP `leerness_task_export` 추가 (TodoWrite 양방향 sync 외부 노출).
|
|
436
437
|
- **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).
|
|
437
438
|
- **1.9.64** — `leerness install <SKILL.md>` 별칭 (skill install 단축) · **성능 벤치마크 1차 실측** (status/handoff/drift/audit/skill list 평균 1.2~1.5초).
|
|
438
439
|
- **1.9.63** — `leerness audit --strict [--threshold N]` — CI 친화 옵션 (warnings → failures 승격).
|
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.66';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -715,13 +715,34 @@ function loadUserSkill(root, id) {
|
|
|
715
715
|
function saveUserSkill(root, id, data) {
|
|
716
716
|
const dir = path.join(userSkillsDir(root), id); mkdirp(dir);
|
|
717
717
|
writeUtf8(path.join(dir, 'skill.json'), JSON.stringify(data, null, 2) + '\n');
|
|
718
|
+
// 1.9.66: 캐시 invalidate (skill 추가/변경 즉시 반영)
|
|
719
|
+
try { _SKILLS_LIST_CACHE.delete(absRoot(root)); } catch {}
|
|
718
720
|
// README mirror
|
|
719
721
|
const usage = data.usage || { count: 0 };
|
|
720
722
|
const readme = `# ${data.displayNameKo || id}\n\n## Capabilities\n${(data.capabilities || []).map(c => '- ' + c).join('\n') || '-'}\n\n## Sources\n${(data.sources || []).map(s => `- ${s.url || s}`).join('\n') || '-'}\n\n## Patterns (성공 명령/접근)\n${(data.patterns || []).map(p => `- \`${p.command}\` — ${p.note || ''}`).join('\n') || '-'}\n\n## Optimization history\n${(data.optimizations || []).map(o => `- ${o.at}: ${o.note || ''}${o.before||o.after?` (${o.before||'?'} → ${o.after||'?'})`:''}`).join('\n') || '-'}\n\n## Usage\n${usage.count || 0}회 사용 / 마지막: ${usage.lastUsed || '-'}\n${usage.lastNote ? '\n마지막 노트: ' + usage.lastNote : ''}\n`;
|
|
721
723
|
writeUtf8(path.join(dir, 'README.md'), readme);
|
|
722
724
|
}
|
|
723
725
|
|
|
726
|
+
// 1.9.66: listAllSkills 메모리 캐시 — skill list/info/match/discover/suggest 가 공유
|
|
727
|
+
// key: root → { mtime(skillsDir), out }
|
|
728
|
+
const _SKILLS_LIST_CACHE = new Map();
|
|
724
729
|
function listAllSkills(root) {
|
|
730
|
+
// 캐시 hit 확인: userSkillsDir mtime 동일 시 재구성 skip
|
|
731
|
+
if (root) {
|
|
732
|
+
try {
|
|
733
|
+
const dir = userSkillsDir(root);
|
|
734
|
+
const dirMtime = exists(dir) ? fs.statSync(dir).mtimeMs : 0;
|
|
735
|
+
const key = absRoot(root);
|
|
736
|
+
const cached = _SKILLS_LIST_CACHE.get(key);
|
|
737
|
+
if (cached && cached.dirMtime === dirMtime) return cached.out;
|
|
738
|
+
const out = _buildAllSkills(root);
|
|
739
|
+
_SKILLS_LIST_CACHE.set(key, { dirMtime, out });
|
|
740
|
+
return out;
|
|
741
|
+
} catch { return _buildAllSkills(root); }
|
|
742
|
+
}
|
|
743
|
+
return _buildAllSkills(root);
|
|
744
|
+
}
|
|
745
|
+
function _buildAllSkills(root) {
|
|
725
746
|
const out = {};
|
|
726
747
|
// 1.9.10: skillCatalog의 _source('skillpack' 또는 'builtin')를 보존
|
|
727
748
|
for (const [k, v] of Object.entries(skillCatalog)) out[k] = { ...v, _source: v._source || 'builtin' };
|
|
@@ -739,6 +760,10 @@ function listAllSkills(root) {
|
|
|
739
760
|
}
|
|
740
761
|
return out;
|
|
741
762
|
}
|
|
763
|
+
// 1.9.66: skill 추가/제거 시 캐시 invalidate (외부 helper)
|
|
764
|
+
function _invalidateSkillsCache(root) {
|
|
765
|
+
try { _SKILLS_LIST_CACHE.delete(absRoot(root)); } catch {}
|
|
766
|
+
}
|
|
742
767
|
|
|
743
768
|
function skillList(root) {
|
|
744
769
|
const all = listAllSkills(root);
|
|
@@ -826,6 +851,8 @@ function skillRemove(root, id) {
|
|
|
826
851
|
if (!id) return fail('id required');
|
|
827
852
|
const dir = path.join(userSkillsDir(root), id);
|
|
828
853
|
if (!exists(dir)) return fail(`skill folder not found: ${id}`);
|
|
854
|
+
// 1.9.66: 캐시 invalidate
|
|
855
|
+
try { _SKILLS_LIST_CACHE.delete(absRoot(root)); } catch {}
|
|
829
856
|
if (skillCatalog[id]) {
|
|
830
857
|
// catalog 스킬은 로컬 메타만 제거 (카탈로그는 패키지 내장이라 영구 제거 불가)
|
|
831
858
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
@@ -6815,7 +6842,8 @@ function mcpServeCmd(root) {
|
|
|
6815
6842
|
{ name: 'leerness_usage_stats', description: 'leerness 명령별 누적 호출 통계 + drift 통계', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
6816
6843
|
{ name: 'leerness_session_close', description: '세션 마감 — handoff/current-state/task-log 자동 갱신', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
6817
6844
|
{ name: 'leerness_skill_suggest', description: '1.9.53 — 사용 패턴 자동 분석 → 새 skill 후보 제안 (Hermes-style 자동 학습)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, min: { type: 'number' }, days: { type: 'number' } } } },
|
|
6818
|
-
{ 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' } } } }
|
|
6845
|
+
{ 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' } } } },
|
|
6846
|
+
{ name: 'leerness_task_export', description: '1.9.60/66 — leerness task → Claude Code TodoWrite 호환 JSON (외부 AI 양방향 sync)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, to: { type: 'string' } } } }
|
|
6819
6847
|
];
|
|
6820
6848
|
|
|
6821
6849
|
function send(obj) {
|
|
@@ -6857,6 +6885,7 @@ function mcpServeCmd(root) {
|
|
|
6857
6885
|
case 'leerness_session_close': cliArgs = ['session', 'close', targetPath]; break;
|
|
6858
6886
|
case 'leerness_skill_suggest': cliArgs = ['skill', 'suggest', '--path', targetPath, '--json', ...(args.min ? ['--min', String(args.min)] : []), ...(args.days ? ['--days', String(args.days)] : [])]; break;
|
|
6859
6887
|
case 'leerness_lessons': cliArgs = ['lessons', '--path', targetPath, ...(args.auto ? ['--auto'] : []), ...(args.query ? ['--query', args.query] : []), ...(args.limit ? ['--limit', String(args.limit)] : [])]; break;
|
|
6888
|
+
case 'leerness_task_export': cliArgs = ['task', 'export', '--path', targetPath, ...(args.to ? ['--to', args.to] : ['--json'])]; break;
|
|
6860
6889
|
default:
|
|
6861
6890
|
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
|
|
6862
6891
|
}
|