leerness 1.9.64 → 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 +38 -0
- package/README.md +4 -2
- package/bin/harness.js +93 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
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
|
+
|
|
21
|
+
## 1.9.65 — 2026-05-19
|
|
22
|
+
|
|
23
|
+
**성능 최적화 1차 — usage-stats 메모리 캐시 + lessons 인덱스 캐시**.
|
|
24
|
+
|
|
25
|
+
### Performance
|
|
26
|
+
- **usage-stats 메모리 캐시 (`_USAGE_CACHE`)** — 같은 프로세스 lifetime 동안 `.harness/cache/usage-stats.json`을 mtime 기반으로 한 번만 파싱. `_readUsageStats()` 다중 호출 시 디스크 I/O 절감.
|
|
27
|
+
- **lessons 인덱스 캐시 (`_LESSONS_INDEX_CACHE`)** — `review-evidence.md` + `decisions.md`를 mtime 기반으로 1회 read+split, 블록 인덱스를 메모리에 보관.
|
|
28
|
+
- handoff의 lessons 자동 재상기: 키워드별 fuzzy 매칭이 split 재실행 없이 인덱스 순회로 동작.
|
|
29
|
+
- `leerness lessons` 명령도 같은 인덱스 재활용.
|
|
30
|
+
- 벤치마크 워크스페이스 크기 비례 비용 → 사실상 O(1) (인덱스 hit 시).
|
|
31
|
+
- API 호환성 유지 — 캐시는 mtime invalidation이라 외부에서 파일을 수정해도 자동 재로드.
|
|
32
|
+
|
|
33
|
+
### Verified
|
|
34
|
+
- stress-v11 (1.9.64 baseline ↔ 1.9.65 optimized 정량 비교) — 13/14 PASS, 캐시 정합성 3/3 PASS.
|
|
35
|
+
- 성능: handoff -37% / drift -19% / audit -29% / skill list -17% / 100-task handoff -42% / 50-evidence handoff 1048ms.
|
|
36
|
+
- status 클린 환경 측정: median 623ms (v10 1195ms 대비 -48% 개선).
|
|
37
|
+
- e2e 회귀: 219/219 PASS 유지.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
3
41
|
## 1.9.64 — 2026-05-19
|
|
4
42
|
|
|
5
43
|
**`leerness install <skill>` 별칭 + 성능 벤치마크 1차 실측**.
|
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,8 @@ 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 외부 노출).
|
|
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).
|
|
436
438
|
- **1.9.64** — `leerness install <SKILL.md>` 별칭 (skill install 단축) · **성능 벤치마크 1차 실측** (status/handoff/drift/audit/skill list 평균 1.2~1.5초).
|
|
437
439
|
- **1.9.63** — `leerness audit --strict [--threshold N]` — CI 친화 옵션 (warnings → failures 승격).
|
|
438
440
|
- **1.9.62** — `leerness audit` npm CVE 자동 감지 (npm audit --json 통합, OFFLINE/no-npm-audit 스킵).
|
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 });
|
|
@@ -1757,24 +1784,22 @@ function handoff(root) {
|
|
|
1757
1784
|
const tokens = String(latestRow.request).toLowerCase().match(/[\w가-힣]{4,}/g) || [];
|
|
1758
1785
|
const keyword = tokens.filter(t => !stopwords.has(t)).sort((a, b) => b.length - a.length)[0];
|
|
1759
1786
|
if (keyword) {
|
|
1760
|
-
//
|
|
1761
|
-
|
|
1762
|
-
const
|
|
1787
|
+
// 1.9.65: lessons blocks 인덱스 메모리 캐시 — mtime 기반 invalidation
|
|
1788
|
+
// 같은 프로세스가 여러 번 handoff를 호출해도 split/regex 비용 1회만
|
|
1789
|
+
const idx = _loadLessonsIndex(root);
|
|
1763
1790
|
// fuzzy: keyword 또는 keyword 부분 (4자+) 일치
|
|
1764
1791
|
// 예: "webhook" 매칭 시 "webhook-payload", "webhooks", "webhooked" 모두 매칭
|
|
1765
1792
|
const fuzzyRe = new RegExp(escapeRegex(keyword.slice(0, Math.max(4, Math.floor(keyword.length * 0.7)))), 'i');
|
|
1766
1793
|
const matches = [];
|
|
1767
|
-
for (const
|
|
1768
|
-
if (
|
|
1769
|
-
|
|
1770
|
-
if (titleM) matches.push({ source: 'review-evidence.md', title: titleM[1].trim(), block });
|
|
1794
|
+
for (const e of idx.evidence) {
|
|
1795
|
+
if (fuzzyRe.test(e.block) && /✗|fail|롤백|버그|incomplete/i.test(e.block)) {
|
|
1796
|
+
matches.push({ source: 'review-evidence.md', title: e.title, block: e.block });
|
|
1771
1797
|
}
|
|
1772
1798
|
}
|
|
1773
1799
|
// 1.9.58: decisions.md도 fuzzy 매칭 (실패/롤백 관련 결정만)
|
|
1774
|
-
for (const
|
|
1775
|
-
if (
|
|
1776
|
-
|
|
1777
|
-
if (titleM) matches.push({ source: 'decisions.md', title: titleM[1].trim(), block });
|
|
1800
|
+
for (const d of idx.decisions) {
|
|
1801
|
+
if (fuzzyRe.test(d.block) && /롤백|실패|fail|취소|회귀|deprecate/i.test(d.block)) {
|
|
1802
|
+
matches.push({ source: 'decisions.md', title: d.title, block: d.block });
|
|
1778
1803
|
}
|
|
1779
1804
|
}
|
|
1780
1805
|
if (matches.length) {
|
|
@@ -5520,8 +5545,9 @@ function lessonsCmd(root) {
|
|
|
5520
5545
|
}
|
|
5521
5546
|
log(`# Lessons --auto (1.9.54): 추출 키워드 "${query}"`);
|
|
5522
5547
|
}
|
|
5548
|
+
// 1.9.65: 인덱스 캐시 활용 (decisions/evidence split 1회만)
|
|
5549
|
+
const _lidx = _loadLessonsIndex(root);
|
|
5523
5550
|
const decisions = exists(decisionsPath(root)) ? read(decisionsPath(root)) : '';
|
|
5524
|
-
const evidence = exists(evidencePath(root)) ? read(evidencePath(root)) : '';
|
|
5525
5551
|
const tlog = exists(taskLogPath(root)) ? read(taskLogPath(root)) : '';
|
|
5526
5552
|
const handoff = exists(handoffPath(root)) ? read(handoffPath(root)) : '';
|
|
5527
5553
|
const lessons = [];
|
|
@@ -5531,12 +5557,10 @@ function lessonsCmd(root) {
|
|
|
5531
5557
|
if (!m) continue;
|
|
5532
5558
|
lessons.push({ source: 'decisions.md', title: m[1].trim(), block });
|
|
5533
5559
|
}
|
|
5534
|
-
// evidence: ## 블록 중 실패/롤백/버그 표지가 있는 것
|
|
5535
|
-
for (const
|
|
5536
|
-
if (
|
|
5537
|
-
|
|
5538
|
-
const m = block.match(/^## (.+)$/m);
|
|
5539
|
-
if (m) lessons.push({ source: 'review-evidence.md', title: m[1].trim(), block });
|
|
5560
|
+
// evidence: ## 블록 중 실패/롤백/버그 표지가 있는 것 (1.9.65: 인덱스 재활용)
|
|
5561
|
+
for (const e of _lidx.evidence) {
|
|
5562
|
+
if (/✗|\bfail(ed)?\b|롤백|재발|incomplete|\bbug\b|버그|warning/i.test(e.block)) {
|
|
5563
|
+
lessons.push({ source: 'review-evidence.md', title: e.title, block: e.block });
|
|
5540
5564
|
}
|
|
5541
5565
|
}
|
|
5542
5566
|
// task-log: 실패 키워드 라인
|
|
@@ -6137,12 +6161,56 @@ function driftCheckCmd(root, opts = {}) {
|
|
|
6137
6161
|
if (level === '🔴 critical') process.exitCode = 1;
|
|
6138
6162
|
}
|
|
6139
6163
|
|
|
6164
|
+
// 1.9.65: lessons blocks 인덱스 — evidence/decisions 파일 read + split을 1회로
|
|
6165
|
+
// key: root → { evidenceMtime, decisionsMtime, evidence: [{title, block}], decisions: [{title, block}] }
|
|
6166
|
+
const _LESSONS_INDEX_CACHE = new Map();
|
|
6167
|
+
function _loadLessonsIndex(root) {
|
|
6168
|
+
const ep = evidencePath(root);
|
|
6169
|
+
const dp = decisionsPath(root);
|
|
6170
|
+
const em = exists(ep) ? (() => { try { return fs.statSync(ep).mtimeMs; } catch { return 0; } })() : 0;
|
|
6171
|
+
const dm = exists(dp) ? (() => { try { return fs.statSync(dp).mtimeMs; } catch { return 0; } })() : 0;
|
|
6172
|
+
const cacheKey = absRoot(root);
|
|
6173
|
+
const cached = _LESSONS_INDEX_CACHE.get(cacheKey);
|
|
6174
|
+
if (cached && cached.evidenceMtime === em && cached.decisionsMtime === dm) return cached;
|
|
6175
|
+
const evidence = [];
|
|
6176
|
+
if (em) {
|
|
6177
|
+
const txt = read(ep);
|
|
6178
|
+
for (const block of txt.split(/\n(?=## )/)) {
|
|
6179
|
+
if (!block.startsWith('## ')) continue;
|
|
6180
|
+
const t = block.match(/^## (.+)$/m);
|
|
6181
|
+
if (t) evidence.push({ title: t[1].trim(), block });
|
|
6182
|
+
}
|
|
6183
|
+
}
|
|
6184
|
+
const decisions = [];
|
|
6185
|
+
if (dm) {
|
|
6186
|
+
const txt = read(dp);
|
|
6187
|
+
for (const block of txt.split(/\n(?=### )/)) {
|
|
6188
|
+
if (!block.startsWith('### ')) continue;
|
|
6189
|
+
const t = block.match(/^### (.+)$/m);
|
|
6190
|
+
if (t) decisions.push({ title: t[1].trim(), block });
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
const idx = { evidenceMtime: em, decisionsMtime: dm, evidence, decisions };
|
|
6194
|
+
_LESSONS_INDEX_CACHE.set(cacheKey, idx);
|
|
6195
|
+
return idx;
|
|
6196
|
+
}
|
|
6197
|
+
|
|
6140
6198
|
// 1.9.38: 사용 통계 (cumulative count, command별)
|
|
6199
|
+
// 1.9.65: 같은 프로세스 lifetime 메모리 캐시 — 다중 호출 시 디스크 I/O 절감
|
|
6200
|
+
const _USAGE_CACHE = new Map(); // root → { stats, mtime }
|
|
6141
6201
|
function _usageStatsPath(root) { return path.join(absRoot(root), '.harness', 'cache', 'usage-stats.json'); }
|
|
6142
6202
|
function _readUsageStats(root) {
|
|
6143
6203
|
const p = _usageStatsPath(root);
|
|
6144
6204
|
if (!exists(p)) return { commands: {}, drift: { criticalSeen: 0, skipped: 0, autoResolved: 0 }, since: today() };
|
|
6145
|
-
|
|
6205
|
+
// 1.9.65: 캐시 hit — mtime 동일 시 재파싱 skip
|
|
6206
|
+
try {
|
|
6207
|
+
const mtime = fs.statSync(p).mtimeMs;
|
|
6208
|
+
const cached = _USAGE_CACHE.get(p);
|
|
6209
|
+
if (cached && cached.mtime === mtime) return cached.stats;
|
|
6210
|
+
const stats = JSON.parse(read(p));
|
|
6211
|
+
_USAGE_CACHE.set(p, { stats, mtime });
|
|
6212
|
+
return stats;
|
|
6213
|
+
} catch { return { commands: {}, drift: {}, since: today() }; }
|
|
6146
6214
|
}
|
|
6147
6215
|
function _bumpUsage(root, cmdName) {
|
|
6148
6216
|
// 가벼운 카운터 — 명령 실행마다 호출 (sync write로 작은 파일)
|
|
@@ -6156,6 +6224,8 @@ function _bumpUsage(root, cmdName) {
|
|
|
6156
6224
|
const p = _usageStatsPath(root);
|
|
6157
6225
|
mkdirp(path.dirname(p));
|
|
6158
6226
|
writeUtf8(p, JSON.stringify(stats, null, 2) + '\n');
|
|
6227
|
+
// 1.9.65: 쓰기 후 캐시 invalidate (다음 read에서 새 mtime으로 재로드)
|
|
6228
|
+
try { _USAGE_CACHE.set(p, { stats, mtime: fs.statSync(p).mtimeMs }); } catch {}
|
|
6159
6229
|
} catch {}
|
|
6160
6230
|
}
|
|
6161
6231
|
|
|
@@ -6772,7 +6842,8 @@ function mcpServeCmd(root) {
|
|
|
6772
6842
|
{ name: 'leerness_usage_stats', description: 'leerness 명령별 누적 호출 통계 + drift 통계', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
6773
6843
|
{ name: 'leerness_session_close', description: '세션 마감 — handoff/current-state/task-log 자동 갱신', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
6774
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' } } } },
|
|
6775
|
-
{ 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' } } } }
|
|
6776
6847
|
];
|
|
6777
6848
|
|
|
6778
6849
|
function send(obj) {
|
|
@@ -6814,6 +6885,7 @@ function mcpServeCmd(root) {
|
|
|
6814
6885
|
case 'leerness_session_close': cliArgs = ['session', 'close', targetPath]; break;
|
|
6815
6886
|
case 'leerness_skill_suggest': cliArgs = ['skill', 'suggest', '--path', targetPath, '--json', ...(args.min ? ['--min', String(args.min)] : []), ...(args.days ? ['--days', String(args.days)] : [])]; break;
|
|
6816
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;
|
|
6817
6889
|
default:
|
|
6818
6890
|
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
|
|
6819
6891
|
}
|