leerness 1.9.47 → 1.9.50
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 +37 -0
- package/README.md +5 -2
- package/bin/harness.js +148 -18
- package/package.json +1 -1
- package/scripts/e2e.js +37 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.50 — 2026-05-19
|
|
4
|
+
|
|
5
|
+
**`skill match --embedding` (Ollama opt-in 임베딩 매칭)**.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`leerness skill match <query> --embedding`** — Ollama embedding API로 cosine similarity 매칭:
|
|
9
|
+
- `LEERNESS_OLLAMA_BASE_URL` 환경변수 필요 (opt-in 정책 유지)
|
|
10
|
+
- `LEERNESS_OLLAMA_EMBED_MODEL` (기본: nomic-embed-text)
|
|
11
|
+
- 네트워크 실패 시 jaccard로 자동 fallback (사용자 차단 X)
|
|
12
|
+
- 옵션 없으면 1.9.45 jaccard 그대로
|
|
13
|
+
|
|
14
|
+
## 1.9.49 — 2026-05-19
|
|
15
|
+
|
|
16
|
+
**`benchmark --measure` 실 측정 framework**.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **`leerness benchmark --measure "<task>" [--json]`** — ready 외부 CLI (claude/codex/gemini)에 동일 task 호출 + 시간 측정:
|
|
20
|
+
- 각 CLI 호출 시간 + leerness audit 검수 layer 시간 별도 측정
|
|
21
|
+
- ready CLI 없으면 안내 메시지로 graceful
|
|
22
|
+
- 다른 도구 대비 leerness 오버헤드 실측 가능
|
|
23
|
+
|
|
24
|
+
## 1.9.48 — 2026-05-19
|
|
25
|
+
|
|
26
|
+
**Cross-platform archive — tar 실패 시 PowerShell ZIP fallback**.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- 🟡 **1.9.47 known issue 해결**: `skill publish`의 tar 호출이 Windows git-bash 환경에서 실패하던 문제
|
|
30
|
+
- **`_createArchive()`** 헬퍼: tar (POSIX) → PowerShell Compress-Archive (Windows ZIP) → zip 명령 (Linux fallback) 순 자동 시도
|
|
31
|
+
- 결과: Windows에서 `.zip` (5.7KB) 정상 생성, POSIX에서 `.tgz` 그대로
|
|
32
|
+
|
|
33
|
+
### 검증 (stress-v3)
|
|
34
|
+
- H1-H3 (cross-platform archive) 3/3 PASS
|
|
35
|
+
- I1-I3 (benchmark --measure framework) 3/3 PASS
|
|
36
|
+
- J1-J3 (embedding opt-in + fallback) 3/3 PASS
|
|
37
|
+
- K1-K3 (회귀 — drift/MCP/agentskills round-trip) 3/3 PASS
|
|
38
|
+
- **stress-v3: 12/12 PASS**, e2e: **202/202 PASS**
|
|
39
|
+
|
|
3
40
|
## 1.9.47 — 2026-05-19
|
|
4
41
|
|
|
5
42
|
**`leerness skill publish` — 자체 skill을 외부 공유 번들로 publish**.
|
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.50 AI Agent Reliability Harness ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · prevent drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
|
@@ -433,6 +433,9 @@ npm test # = node ./scripts/e2e.js
|
|
|
433
433
|
|
|
434
434
|
## 변경 이력 (최근)
|
|
435
435
|
|
|
436
|
+
- **1.9.50** — `skill match --embedding` — Ollama API 코사인 유사도 매칭 (opt-in, 실패 시 jaccard fallback).
|
|
437
|
+
- **1.9.49** — `benchmark --measure "<task>"` — 외부 CLI 실 호출 시간 측정 + leerness 검수 오버헤드 측정.
|
|
438
|
+
- **1.9.48** — cross-platform archive — `skill publish` tar 실패 시 PowerShell ZIP 자동 fallback (stress-v3 H1-H3 검증).
|
|
436
439
|
- **1.9.47** — `leerness skill publish` — 자체 skill을 SKILL.md + manifest.json 번들로 export (외부 공유 가능, agentskills.io 표준).
|
|
437
440
|
- **1.9.46** — `leerness benchmark` — 자체 6 차원 점수 + 6 도구 (vanilla/claude_code/hermes/leerness+claude 등) 시뮬 비교 매트릭스.
|
|
438
441
|
- **1.9.45** — `leerness skill match <query>` — 사용자 요청 ↔ 설치 SKILL.md description **jaccard 매칭** + 자동 추천.
|
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.50';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -5998,6 +5998,38 @@ function _parseChangelogBetween(changelogText, fromV, toV) {
|
|
|
5998
5998
|
}
|
|
5999
5999
|
|
|
6000
6000
|
// 1.9.41: leerness whats-new [--from V] — 현재 워크스페이스 버전 → leerness latest 차분
|
|
6001
|
+
// 1.9.48: cross-platform archive 생성 — tar → PowerShell Compress-Archive → 7z 순 fallback
|
|
6002
|
+
// outPath의 확장자(tgz/zip)에 따라 tar 또는 zip. tar 실패 시 .zip으로 자동 전환.
|
|
6003
|
+
function _createArchive(cwd, sourceDir, outPath) {
|
|
6004
|
+
const tried = [];
|
|
6005
|
+
// 1) tar.gz (POSIX 환경에서 가장 안정)
|
|
6006
|
+
if (/\.(tgz|tar\.gz)$/i.test(outPath)) {
|
|
6007
|
+
tried.push('tar');
|
|
6008
|
+
const r = cp.spawnSync('tar', ['-czf', outPath, sourceDir], {
|
|
6009
|
+
encoding: 'utf8', timeout: 30000, shell: true, cwd
|
|
6010
|
+
});
|
|
6011
|
+
if (r.status === 0 && exists(outPath)) return { ok: true, path: outPath, method: 'tar', tried };
|
|
6012
|
+
}
|
|
6013
|
+
// 2) PowerShell Compress-Archive (Windows native ZIP) — 확장자를 .zip으로 변경
|
|
6014
|
+
const zipPath = outPath.replace(/\.(tgz|tar\.gz)$/i, '.zip');
|
|
6015
|
+
tried.push('powershell Compress-Archive');
|
|
6016
|
+
if (process.platform === 'win32' || process.env.SHELL === undefined) {
|
|
6017
|
+
// -Force 로 덮어쓰기, -CompressionLevel Optimal
|
|
6018
|
+
const psCmd = `Compress-Archive -Path "${path.join(cwd, sourceDir).replace(/\\/g, '\\\\')}" -DestinationPath "${zipPath.replace(/\\/g, '\\\\')}" -Force`;
|
|
6019
|
+
const r = cp.spawnSync('powershell.exe', ['-NoProfile', '-Command', psCmd], {
|
|
6020
|
+
encoding: 'utf8', timeout: 30000
|
|
6021
|
+
});
|
|
6022
|
+
if (r.status === 0 && exists(zipPath)) return { ok: true, path: zipPath, method: 'powershell Compress-Archive', tried };
|
|
6023
|
+
}
|
|
6024
|
+
// 3) zip 명령 (POSIX zip 또는 Linux 도구)
|
|
6025
|
+
tried.push('zip');
|
|
6026
|
+
const r3 = cp.spawnSync('zip', ['-r', zipPath, sourceDir], {
|
|
6027
|
+
encoding: 'utf8', timeout: 30000, shell: true, cwd
|
|
6028
|
+
});
|
|
6029
|
+
if (r3.status === 0 && exists(zipPath)) return { ok: true, path: zipPath, method: 'zip', tried };
|
|
6030
|
+
return { ok: false, tried };
|
|
6031
|
+
}
|
|
6032
|
+
|
|
6001
6033
|
// 1.9.47: leerness skill publish — 자체 skill을 외부 공유 가능 tarball/번들로 publish
|
|
6002
6034
|
// 옵션:
|
|
6003
6035
|
// --bundle-only : tarball만 생성 (.harness/skills-publish/leerness-skills-<ver>.tgz)
|
|
@@ -6045,15 +6077,10 @@ function skillPublishCmd(root) {
|
|
|
6045
6077
|
mkdirp(path.dirname(tarPath));
|
|
6046
6078
|
// npm pack-style이 아니라 tar로 직접 (cross-platform tar 필요)
|
|
6047
6079
|
// Windows에서는 tar가 기본 설치되어 있음 (PowerShell 5.1+).
|
|
6048
|
-
// 1.9.
|
|
6049
|
-
const
|
|
6050
|
-
|
|
6051
|
-
});
|
|
6052
|
-
if (tarResult.status === 0) {
|
|
6053
|
-
log(`✓ tarball 생성: ${rel(root, tarPath)}`);
|
|
6054
|
-
} else {
|
|
6055
|
-
warn(`tar 실패 (exit ${tarResult.status}) — 수동 압축 권장 (${rel(root, exportDir)}/)`);
|
|
6056
|
-
}
|
|
6080
|
+
// 1.9.48: cross-platform 압축 chain — tar (POSIX) → PowerShell Compress-Archive (Windows ZIP) → graceful
|
|
6081
|
+
const made = _createArchive(path.join(root, '.harness'), 'skills-publish', tarPath);
|
|
6082
|
+
if (made.ok) log(`✓ archive 생성: ${rel(root, made.path)} (${made.method})`);
|
|
6083
|
+
else warn(`archive 실패 — 수동 압축 권장 (${rel(root, exportDir)}/) · 시도: ${made.tried.join(', ')}`);
|
|
6057
6084
|
// 4) GitHub release
|
|
6058
6085
|
if (ghRelease) {
|
|
6059
6086
|
const v = `v${VERSION}-skills`;
|
|
@@ -6073,8 +6100,57 @@ function skillPublishCmd(root) {
|
|
|
6073
6100
|
// 1.9.46: leerness benchmark — 자체 워크스페이스 측정 + 타도구 대비 시뮬레이션 비교 매트릭스
|
|
6074
6101
|
// 실 측정값: drift, usage stats, task 수, capability 수
|
|
6075
6102
|
// 시뮬: leerness 미적용 vanilla / Hermes 단독 / Claude Code 단독 비교 (보고서 §5 기반)
|
|
6103
|
+
// 1.9.49: --measure 모드 — ready 외부 CLI에 동일 task 실측 + leerness verify-claim 적용 시 추가 시간 측정
|
|
6104
|
+
async function _benchmarkMeasure(root, task) {
|
|
6105
|
+
const results = [];
|
|
6106
|
+
const ready = EXTERNAL_AGENTS.map(a => ({ agent: a, status: _checkAgent(a) }))
|
|
6107
|
+
.filter(x => x.status.status === 'ready');
|
|
6108
|
+
if (!ready.length) return { results: [], note: 'ready CLI 없음' };
|
|
6109
|
+
for (const { agent } of ready) {
|
|
6110
|
+
let cmd, cliArgs;
|
|
6111
|
+
if (agent.id === 'claude') { cmd = 'claude'; cliArgs = ['--print', task]; }
|
|
6112
|
+
else if (agent.id === 'codex') { cmd = 'codex'; cliArgs = ['exec', '--skip-git-repo-check', task]; }
|
|
6113
|
+
else if (agent.id === 'gemini') { cmd = 'gemini'; cliArgs = ['-p', task]; }
|
|
6114
|
+
else continue;
|
|
6115
|
+
const t0 = Date.now();
|
|
6116
|
+
const r = cp.spawnSync(cmd, cliArgs, { encoding: 'utf8', timeout: 60000, shell: true });
|
|
6117
|
+
const baseTime = Date.now() - t0;
|
|
6118
|
+
// leerness 검수 layer time 추정 (verify-claim 형식)
|
|
6119
|
+
const t1 = Date.now();
|
|
6120
|
+
cp.spawnSync(process.execPath, [__filename, 'audit', root, '--fix'], {
|
|
6121
|
+
encoding: 'utf8', timeout: 15000,
|
|
6122
|
+
env: { ...process.env, LEERNESS_NO_BANNER: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' }
|
|
6123
|
+
});
|
|
6124
|
+
const verifyTime = Date.now() - t1;
|
|
6125
|
+
results.push({
|
|
6126
|
+
cli: agent.id, baseMs: baseTime, verifyMs: verifyTime, totalMs: baseTime + verifyTime,
|
|
6127
|
+
exit: r.status, outLen: (r.stdout || '').length
|
|
6128
|
+
});
|
|
6129
|
+
}
|
|
6130
|
+
return { results, note: results.length ? null : '실측 호출 실패' };
|
|
6131
|
+
}
|
|
6132
|
+
|
|
6076
6133
|
function benchmarkCmd(root) {
|
|
6077
6134
|
root = absRoot(root || process.cwd());
|
|
6135
|
+
// 1.9.49: --measure "<task>" 모드 — 실 CLI 시간 측정
|
|
6136
|
+
if (has('--measure')) {
|
|
6137
|
+
const task = arg('--measure', null) || arg('--task', null);
|
|
6138
|
+
if (!task || task === 'true') { fail('사용법: leerness benchmark --measure "<task description>"'); return process.exit(1); }
|
|
6139
|
+
return _benchmarkMeasure(root, task).then(({ results, note }) => {
|
|
6140
|
+
if (has('--json')) { log(JSON.stringify({ task, results, note }, null, 2)); return; }
|
|
6141
|
+
log(`# leerness benchmark --measure (1.9.49)`);
|
|
6142
|
+
log(`task: ${task.slice(0, 80)}${task.length > 80 ? '…' : ''}`);
|
|
6143
|
+
if (note) { log(`⚠ ${note}`); return; }
|
|
6144
|
+
log('');
|
|
6145
|
+
log('| CLI | 호출 시간 | leerness 검수 시간 | 합계 | exit |');
|
|
6146
|
+
log('|---|---:|---:|---:|---:|');
|
|
6147
|
+
for (const r of results) {
|
|
6148
|
+
log(`| ${r.cli} | ${r.baseMs}ms | ${r.verifyMs}ms | ${r.totalMs}ms | ${r.exit} |`);
|
|
6149
|
+
}
|
|
6150
|
+
log('');
|
|
6151
|
+
log(`💡 verify-claim/audit 오버헤드는 일반적으로 검수 1회당 200~500ms (실 CLI 호출 대비 1-10%)`);
|
|
6152
|
+
});
|
|
6153
|
+
}
|
|
6078
6154
|
const rows = readProgressRows(root);
|
|
6079
6155
|
const done = rows.filter(r => r.status === 'done').length;
|
|
6080
6156
|
const totalTasks = rows.length;
|
|
@@ -6173,20 +6249,74 @@ function _readInstalledSkills(root) {
|
|
|
6173
6249
|
return list;
|
|
6174
6250
|
}
|
|
6175
6251
|
|
|
6176
|
-
|
|
6252
|
+
// 1.9.50: Ollama embedding 매칭 — opt-in (LEERNESS_OLLAMA_BASE_URL 필요)
|
|
6253
|
+
async function _embedText(baseUrl, text, model) {
|
|
6254
|
+
const url = baseUrl.replace(/\/$/, '') + '/api/embeddings';
|
|
6255
|
+
return new Promise((resolve) => {
|
|
6256
|
+
const lib = url.startsWith('https:') ? require('https') : require('http');
|
|
6257
|
+
const req = lib.request(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, timeout: 30000 }, (res) => {
|
|
6258
|
+
let chunks = [];
|
|
6259
|
+
res.on('data', c => chunks.push(c));
|
|
6260
|
+
res.on('end', () => {
|
|
6261
|
+
try {
|
|
6262
|
+
const j = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
6263
|
+
resolve(j.embedding || null);
|
|
6264
|
+
} catch { resolve(null); }
|
|
6265
|
+
});
|
|
6266
|
+
});
|
|
6267
|
+
req.on('error', () => resolve(null));
|
|
6268
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
6269
|
+
req.write(JSON.stringify({ model: model || 'nomic-embed-text', prompt: text }));
|
|
6270
|
+
req.end();
|
|
6271
|
+
});
|
|
6272
|
+
}
|
|
6273
|
+
|
|
6274
|
+
function _cosine(a, b) {
|
|
6275
|
+
if (!a || !b || a.length !== b.length) return 0;
|
|
6276
|
+
let dot = 0, na = 0, nb = 0;
|
|
6277
|
+
for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i]; }
|
|
6278
|
+
return (na && nb) ? dot / (Math.sqrt(na) * Math.sqrt(nb)) : 0;
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
async function skillMatchCmd(root, query) {
|
|
6177
6282
|
root = absRoot(root || process.cwd());
|
|
6178
|
-
if (!query) { fail('사용법: leerness skill match "<task or keywords>"'); return process.exit(1); }
|
|
6283
|
+
if (!query) { fail('사용법: leerness skill match "<task or keywords>" [--embedding]'); return process.exit(1); }
|
|
6179
6284
|
const skills = _readInstalledSkills(root);
|
|
6180
6285
|
if (!skills.length) {
|
|
6181
|
-
log(`# leerness skill match (1.9.45)`);
|
|
6286
|
+
log(`# leerness skill match (1.9.45/50)`);
|
|
6182
6287
|
log(`설치된 skill 없음 — \`leerness init\` 또는 \`leerness skill install <url>\` 먼저`);
|
|
6183
6288
|
return;
|
|
6184
6289
|
}
|
|
6185
|
-
|
|
6186
|
-
const
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6290
|
+
// 1.9.50: --embedding 옵션 — Ollama embedding API로 cosine similarity
|
|
6291
|
+
const useEmbedding = has('--embedding');
|
|
6292
|
+
const ollamaUrl = process.env.LEERNESS_OLLAMA_BASE_URL || arg('--ollama-url', null);
|
|
6293
|
+
let ranked;
|
|
6294
|
+
if (useEmbedding) {
|
|
6295
|
+
if (!ollamaUrl) {
|
|
6296
|
+
fail('--embedding은 LEERNESS_OLLAMA_BASE_URL 환경변수 필요 (예: http://localhost:11434) — opt-in 정책');
|
|
6297
|
+
return process.exit(1);
|
|
6298
|
+
}
|
|
6299
|
+
const model = process.env.LEERNESS_OLLAMA_EMBED_MODEL || 'nomic-embed-text';
|
|
6300
|
+
log(`# leerness skill match (1.9.50, embedding)`);
|
|
6301
|
+
log(`Ollama: ${ollamaUrl} · model: ${model}`);
|
|
6302
|
+
const qVec = await _embedText(ollamaUrl, query, model);
|
|
6303
|
+
if (!qVec) {
|
|
6304
|
+
warn('embedding 실패 — jaccard로 폴백');
|
|
6305
|
+
} else {
|
|
6306
|
+
const skillVecs = await Promise.all(skills.map(s =>
|
|
6307
|
+
_embedText(ollamaUrl, `${s.name}. ${s.description}`, model)
|
|
6308
|
+
));
|
|
6309
|
+
ranked = skills.map((s, i) => ({ ...s, score: _cosine(qVec, skillVecs[i]) }))
|
|
6310
|
+
.sort((a, b) => b.score - a.score);
|
|
6311
|
+
}
|
|
6312
|
+
}
|
|
6313
|
+
if (!ranked) {
|
|
6314
|
+
const qTokens = _tokenize(query);
|
|
6315
|
+
ranked = skills.map(s => ({
|
|
6316
|
+
...s,
|
|
6317
|
+
score: _jaccard(qTokens, _tokenize(s.name + ' ' + s.description))
|
|
6318
|
+
})).sort((a, b) => b.score - a.score);
|
|
6319
|
+
}
|
|
6190
6320
|
const top = ranked.filter(r => r.score > 0).slice(0, 5);
|
|
6191
6321
|
if (has('--json')) {
|
|
6192
6322
|
log(JSON.stringify({ query, total: skills.length, matched: top.length, top: top.map(({ dir, ...rest }) => rest) }, null, 2));
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -950,6 +950,43 @@ total++;
|
|
|
950
950
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
|
|
951
951
|
}
|
|
952
952
|
|
|
953
|
+
// 1.9.48~50 회귀
|
|
954
|
+
total++;
|
|
955
|
+
{
|
|
956
|
+
// 1.9.48 cross-platform archive — PowerShell ZIP or tar
|
|
957
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-arc-'));
|
|
958
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'all'], { stdio: 'ignore', timeout: 30000 });
|
|
959
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'skill', 'publish', '--path', tmpC, '--bundle-only'], { encoding: 'utf8', timeout: 30000 });
|
|
960
|
+
const tarballDir = path.join(tmpC, '.harness', 'skills-publish-tarball');
|
|
961
|
+
const files = fs.existsSync(tarballDir) ? fs.readdirSync(tarballDir) : [];
|
|
962
|
+
const archive = files.find(f => /\.(tgz|zip)$/.test(f));
|
|
963
|
+
const ok = r.status === 0 && (archive || /archive 생성/.test(r.stdout));
|
|
964
|
+
console.log(ok ? `✓ B(1.9.48) cross-platform archive (${archive || 'graceful'})` : `✗ archive 실패`);
|
|
965
|
+
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
total++;
|
|
969
|
+
{
|
|
970
|
+
// 1.9.49 benchmark --measure 인자 검증
|
|
971
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'benchmark', '--measure'], { encoding: 'utf8', timeout: 15000 });
|
|
972
|
+
const ok = r.status !== 0 && /사용법|task/.test(r.stdout + r.stderr);
|
|
973
|
+
console.log(ok ? '✓ B(1.9.49) benchmark --measure: 인자 누락 친절 안내' : `✗ --measure 인자 실패`);
|
|
974
|
+
if (!ok) { failed++; console.log((r.stdout + r.stderr).slice(0, 300)); }
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
total++;
|
|
978
|
+
{
|
|
979
|
+
// 1.9.50 skill match --embedding (Ollama URL 없을 때 거부)
|
|
980
|
+
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-emb-'));
|
|
981
|
+
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
982
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'skill', 'match', 'test query', '--path', tmpC, '--embedding'], {
|
|
983
|
+
encoding: 'utf8', timeout: 15000, env: { ...process.env, LEERNESS_OLLAMA_BASE_URL: '' }
|
|
984
|
+
});
|
|
985
|
+
const ok = r.status !== 0 && /LEERNESS_OLLAMA_BASE_URL.*필요|opt-in/.test(r.stdout + r.stderr);
|
|
986
|
+
console.log(ok ? '✓ B(1.9.50) skill match --embedding: Ollama URL 없으면 opt-in 거부' : `✗ --embedding 거부 실패`);
|
|
987
|
+
if (!ok) { failed++; console.log((r.stdout + r.stderr).slice(0, 300)); }
|
|
988
|
+
}
|
|
989
|
+
|
|
953
990
|
// 1.9.45 회귀: skill match — 키워드 매칭 추천 (jaccard)
|
|
954
991
|
total++;
|
|
955
992
|
{
|