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 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
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.65-green)]() [![tests](https://img.shields.io/badge/e2e-219%2F219-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
5
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.66-green)]() [![tests](https://img.shields.io/badge/e2e-219%2F219-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,7 +12,7 @@
12
12
  ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
13
13
  ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
14
14
  ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
15
- ║ v1.9.65 AI Agent Reliability Harness ║
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.65';
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.65",
3
+ "version": "1.9.66",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",