leerness 1.9.285 → 1.9.286

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,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.286 — 2026-06-03 — UR-0024: 스킬 설치 영향 경량 상관추적 + HuggingFace 안내 (백로그 완전 소진)
4
+
5
+ **📊 "설치 스킬이 코딩 성능/정확도에 도움?" — 무거운 벤치 없이 기존 데이터로 정직한 상관 advisory (GPT-5.5 / 사용자 요청). 마지막 추적 백로그.**
6
+
7
+ ### 배경 + 설계 판단
8
+ 사용자 요청(UR-0024): 스킬 소스(HuggingFace) + 설치 영향 측정. **권고대로**: (1) 설치는 이미 존재(skill install/discover) → HF 는 모델/데이터셋 중심이라 agent-skill 가치 낮음 → **discover --github 안내만**(저우선), (2) 영향 측정은 **경량 상관추적**(인과 아님, 표본 부족 시 판단 보류)으로 honest 구현.
9
+
10
+ ### 구현
11
+ 1. **`leerness skill impact [--json]`** — 설치 스킬 × 사용 빈도(skill-suggestions 이력) + `review-evidence.md` 검증 통과율 상관. **정직한 advisory**: 표본<5 시 "판단 보류", 그 외 "상관추적(인과 아님)".
12
+ 2. **`_parseEvidenceStats(text)` 순수 함수** — review-evidence 의 Exit code / PASS·FAIL 키워드로 통과율 집계.
13
+ 3. **HuggingFace 안내** — `skill discover` 도움말에 "HF 는 모델/데이터셋 중심 → agent-skill 은 GitHub 미러 repo 를 --github 로 지정 권장".
14
+ 4. **offline-first 유지** — 실제 외부 스킬 탐색은 호스트 AI, leerness 는 네트워크 자동 호출 안 함.
15
+ 5. selftest 32→33 + e2e 230→231.
16
+
17
+ ### 디버그 (테스트 인프라)
18
+ - `skill impact` 가 id 없는 skill 엔트리에서 `.replace` crash → `Object.entries` + 가드로 수정.
19
+ - e2e exit 127 크래시 추적 → **누적 temp 디렉토리 6409개**(세션 내 e2e 반복 실행 잔재)로 인한 OS spawn 자원 고갈 확인. 정리 후 정상(코드/CI 무관 — CI 는 fresh runner).
20
+
21
+ ### 검증
22
+ - **selftest 33/33 PASS** · **E2E 231/231 PASS** (회귀 0).
23
+
24
+ ### 🎉 전체 백로그 완전 소진
25
+ GPT-5.5 1차 리뷰(UR-0025~0027) + 2차 리뷰(설치부담/모듈화/e2e) + 범용 하네스 전략(UR-0030~0035) + 사용자 요청(UR-0022~0024) — **전부 구현·배포 완료**.
26
+
3
27
  ## 1.9.285 — 2026-06-03 — UR-0023: 외부 OSS "빌드 vs 재사용" 결정 게이트 (오프라인)
4
28
 
5
29
  **🔍 기능 계획 시 "이미 검증된 OSS 가 있는데 새로 만드는가?" 를 묻는 오프라인 구조적 게이트 (GPT-5.5 / 사용자 요청). 네트워크 크롤러 아님 — offline-first 유지.**
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
4
4
  > **A CLI harness that stops AI coding agents from faking completion, duplicating work, forgetting context, and colliding.**
5
5
 
6
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.285-green)]() [![tests](https://img.shields.io/badge/e2e-230%2F230-success)]() [![selftest](https://img.shields.io/badge/selftest-24%2F24-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-80-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-10-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.286-green)]() [![tests](https://img.shields.io/badge/e2e-231%2F231-success)]() [![selftest](https://img.shields.io/badge/selftest-24%2F24-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-80-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-10-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
7
7
 
8
8
  ```
9
9
  ╔══════════════════════════════════════════════════════════════╗
@@ -471,7 +471,7 @@ MIT — © leerness contributors
471
471
  <!-- leerness:project-readme:start -->
472
472
  ## Leerness Project Harness
473
473
 
474
- 이 프로젝트는 Leerness v1.9.285 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
474
+ 이 프로젝트는 Leerness v1.9.286 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
475
475
 
476
476
  ### Core Commands
477
477
 
@@ -513,7 +513,7 @@ leerness memory restore decision <date|title>
513
513
 
514
514
  ### MCP server (외부 AI 통합)
515
515
 
516
- Leerness v1.9.285는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:
516
+ Leerness v1.9.286는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:
517
517
 
518
518
  ```jsonc
519
519
  // 카테고리별
@@ -534,7 +534,7 @@ Leerness v1.9.285는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
534
534
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
535
535
  1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
536
536
 
537
- 현재 누적: **70 라운드 (1.9.40 → 1.9.285)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
537
+ 현재 누적: **70 라운드 (1.9.40 → 1.9.286)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
538
538
 
539
539
  ### 성능 가이드 (1.9.140 측정)
540
540
 
@@ -572,6 +572,6 @@ leerness release pack --close --auto-main-push
572
572
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
573
573
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
574
574
 
575
- Last synced by Leerness v1.9.285: 2026-06-03
575
+ Last synced by Leerness v1.9.286: 2026-06-03
576
576
  <!-- leerness:project-readme:end -->
577
577
 
package/bin/harness.js CHANGED
@@ -10,7 +10,7 @@ const readline = require('readline');
10
10
  const { _isSecretKey, compareVer, parseHarnessVersion, _classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp,
11
11
  PERMISSION_TIERS, _tierRank, _requiredTier, _policyAllows, _resolveNpmTag, _mcpJsonContent, _newRunRecord } = require('../lib/pure-utils');
12
12
 
13
- const VERSION = '1.9.285';
13
+ const VERSION = '1.9.286';
14
14
 
15
15
  // 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
16
16
  // leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
@@ -1692,8 +1692,10 @@ async function skillDiscoverCmd(root) {
1692
1692
  ' leerness skill discover --preset anthropic # anthropics/skills',
1693
1693
  ' leerness skill discover --all-presets # 모든 preset 동시 탐색',
1694
1694
  ' leerness skill discover --github owner/repo # 직접 GitHub repo 지정',
1695
+ ' leerness skill discover --github <hf-owner>/<repo> # HuggingFace 미러/agent-skills repo 도 동일 방식 (1.9.286)',
1695
1696
  '',
1696
1697
  '예: leerness skill discover --source https://agentskills.io/llms.txt',
1698
+ '※ HuggingFace 는 모델/데이터셋 중심 — agent-skill 은 GitHub 미러 repo 를 --github 로 지정 권장 (1.9.286 UR-0024).',
1697
1699
  '',
1698
1700
  '(정책: leerness는 사용자 동의 없이 외부 URL을 fetch하지 않음 — 1.9.42 opt-in)'
1699
1701
  ].join('\n'));
@@ -2961,6 +2963,7 @@ function _selfTestCases() {
2961
2963
  { name: '_pickModel: top/code/fast 등급 선택 (1.9.270)', run: () => { return _pickModel('codex', 'code') === 'gpt-5-codex' && _pickModel('claude', 'top') === 'claude-opus-4-7' && /haiku/.test(_pickModel('claude', 'fast')); } },
2962
2964
  { name: 'CAPABILITY_SURFACE: 6 영역 + risk/optOut + 주의명령 (1.9.272)', run: () => { const ks = Object.keys(CAPABILITY_SURFACE); return ks.length === 6 && ks.includes('automationBridges') && Object.values(CAPABILITY_SURFACE).every(v => ['low','medium','high'].includes(v.risk) && v.optOut && v.desc) && POWERFUL_COMMANDS.length >= 5; } },
2963
2965
  { name: '_resolveNpmTag: latest 기본 + next 허용 + 잘못된 형식 폴백 (1.9.275)', run: () => _resolveNpmTag(null, {}) === 'latest' && _resolveNpmTag('next', {}) === 'next' && _resolveNpmTag(null, { LEERNESS_NPM_TAG: 'next' }) === 'next' && _resolveNpmTag('bad tag!', {}) === 'latest' && _resolveNpmTag('Beta', {}) === 'beta' },
2966
+ { name: '_parseEvidenceStats: review-evidence pass/fail 집계 (1.9.286)', run: () => { const s = _parseEvidenceStats('## 2026-06-03\nCommand: npm test\nExit: 0\n\n## 2026-06-03\nCommand: build\nExit: 1\n\n## 2026-06-03\nverify PASS 통과\n'); return s.entries === 3 && s.pass === 2 && s.fail === 1 && s.rate === 67; } },
2964
2967
  { name: '_reuseDetect: 키워드→OSS 카테고리 + 체크리스트 (1.9.285)', run: () => { const a = _reuseDetect('JWT 인증 구현'); const b = _reuseDetect('날짜 포맷 date'); const c = _reuseDetect('전혀무관한xyzzy'); return a.some(x => x.key === 'auth') && b.some(x => x.key === 'date') && c.length === 0 && REUSE_CHECKLIST.length >= 5 && REUSE_CATEGORIES.length >= 12; } },
2965
2968
  { name: 'AGENTS.md: 정적 vs 동적 leerness 역할 경계 (1.9.282)', run: () => { const a = coreFiles('.', 'ko', [])['AGENTS.md']; return /정적 vs 동적/.test(a) && /대체하지 않고 \*\*보완\*\*|대체하지 않고 보완/.test(a) && /leerness state/.test(a) && /\.leerness\//.test(a); } },
2966
2969
  { name: '권한 등급: _requiredTier + _policyAllows 순서 (1.9.281)', run: () => { return _requiredTier('release publish') === 'publish' && _requiredTier('agents multi --execute') === 'shell-write' && _requiredTier('handoff') === 'read-only' && _requiredTier('init') === 'project-write' && _policyAllows('project-write', 'safe-write') === true && _policyAllows('project-write', 'publish') === false && _tierRank('read-only') < _tierRank('publish'); } },
@@ -3628,6 +3631,7 @@ function commandsCmd(root) {
3628
3631
  { cmd: 'adapter <tool>|list [--dry-run]', desc: '도구별 지침/.mcp.json 선택 생성 (claude/cursor/codex/goose/...) — 1.9.280' },
3629
3632
  { cmd: 'policy show|set|check', desc: '권한 등급 (read-only…publish) — opt-in enforced (위험 명령 차단) — 1.9.281' },
3630
3633
  { cmd: 'reuse-check "<기능>"', desc: '외부 OSS 빌드 vs 재사용 결정 게이트 (오프라인 카테고리+체크리스트) — 1.9.285' },
3634
+ { cmd: 'skill impact', desc: '스킬 설치 영향 경량 상관추적 (사용 빈도 ↔ 검증 통과율, advisory) — 1.9.286' },
3631
3635
  { cmd: 'release channel [--json]', desc: '릴리스 채널 정책 (latest 안정 / next 실험 / 버전 고정) — 1.9.275' },
3632
3636
  { cmd: 'slash-commands [agent] [--json --record --detect --refresh]', desc: 'CLI 에이전트 슬래시 명령 레지스트리 + --help probe 자동 갱신 (1.9.265~267, UR-0021)' },
3633
3637
  { cmd: 'review-request "<request>"', desc: '사용자 요청 사전 검토 (1.9.176)' },
@@ -18381,6 +18385,56 @@ function reuseCheckCmd(root, feature, opts = {}) {
18381
18385
  return;
18382
18386
  }
18383
18387
 
18388
+ // ===== 1.9.286 (UR-0024, GPT-5.5): 스킬 설치 영향 경량 상관추적 =====
18389
+ // "설치한 스킬이 코딩 성능/정확도에 긍정 영향?" — 무거운 벤치 harness 가 아니라, 기존 데이터
18390
+ // (skill 사용 빈도 + review-evidence 검증 통과율)를 상관(인과 아님)으로 advisory 제시. offline.
18391
+ // review-evidence.md 파싱 (순수) — 검증 항목 수 + pass/fail 추정 (Exit 0 / 'PASS' vs 'fail'/Exit !=0).
18392
+ function _parseEvidenceStats(text) {
18393
+ const t = String(text || '');
18394
+ const blocks = t.split(/\n(?=## )/).filter(b => /Command:|Exit:|verify|test/i.test(b));
18395
+ let pass = 0, fail = 0;
18396
+ for (const b of blocks) {
18397
+ const exitM = b.match(/Exit:\s*(-?\d+)/i);
18398
+ if (exitM) { (parseInt(exitM[1], 10) === 0 ? pass++ : fail++); continue; }
18399
+ if (/\bPASS\b|통과|성공|✓/i.test(b)) pass++;
18400
+ else if (/\bFAIL\b|실패|오류|error|✗/i.test(b)) fail++;
18401
+ }
18402
+ const entries = blocks.length;
18403
+ return { entries, pass, fail, rate: (pass + fail) ? Math.round(pass / (pass + fail) * 100) : null };
18404
+ }
18405
+ function skillImpactCmd(root) {
18406
+ root = absRoot(root || process.cwd());
18407
+ const json = has('--json');
18408
+ // 설치 스킬 + 사용 빈도 (skill-suggestions.md rolling history 의 id 언급 카운트)
18409
+ let skills = [];
18410
+ try { skills = Object.entries(listAllSkills(root)).map(([k, s]) => ({ id: (s && (s.id || s.name)) || k, name: (s && (s.displayNameKo || s.name)) || k })).filter(s => s.id); } catch {}
18411
+ const histPath = path.join(absRoot(root), '.harness', 'skill-suggestions.md');
18412
+ const hist = exists(histPath) ? read(histPath) : '';
18413
+ const uses = {};
18414
+ for (const s of skills) { const re = new RegExp(String(s.id).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'); uses[s.id] = (hist.match(re) || []).length; }
18415
+ const ranked = skills.map(s => ({ ...s, uses: uses[s.id] || 0 })).sort((a, b) => b.uses - a.uses);
18416
+ const ev = _parseEvidenceStats(exists(evidencePath(root)) ? read(evidencePath(root)) : '');
18417
+ const sampleSize = ev.entries;
18418
+ const advisory = sampleSize < 5
18419
+ ? '표본 부족 (검증 기록 < 5) — 판단 보류. 더 많은 verify-code 누적 후 재확인.'
18420
+ : '상관추적(인과 아님): 스킬 사용 빈도와 검증 통과율은 참고 신호일 뿐, 인과관계를 보장하지 않음.';
18421
+ if (json) { log(JSON.stringify({ skills: ranked, usedCount: ranked.filter(s => s.uses > 0).length, evidence: ev, sampleSize, advisory, network: false }, null, 2)); return; }
18422
+ const isTty = process.stdout && process.stdout.isTTY;
18423
+ const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s; const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
18424
+ log(cy(`# leerness skill impact (1.9.286, UR-0024) — 스킬 영향 경량 상관추적`));
18425
+ log(`설치 스킬: ${skills.length} · 사용 이력 있는 스킬: ${ranked.filter(s => s.uses > 0).length}`);
18426
+ log(`검증 기록(review-evidence): ${ev.entries}건 · 통과율: ${ev.rate === null ? 'N/A' : ev.rate + '%'} (pass ${ev.pass} / fail ${ev.fail})`);
18427
+ if (ranked.filter(s => s.uses > 0).length) {
18428
+ log('');
18429
+ log(`## 사용 빈도 top (skill-suggestions 이력)`);
18430
+ for (const s of ranked.filter(x => x.uses > 0).slice(0, 8)) log(` ${String(s.uses).padStart(3)}× ${s.id} ${dm(s.name)}`);
18431
+ }
18432
+ log('');
18433
+ log(dm(` ⚠ ${advisory}`));
18434
+ log(dm(` ※ HuggingFace/외부 스킬 소스 탐색은 호스트 AI 가 수행 (leerness 는 네트워크 자동 호출 안 함).`));
18435
+ return;
18436
+ }
18437
+
18384
18438
  // 1.9.149+1.9.153: REPL 모드 — leerness 자율 AI 에이전트 (multi-provider 세션)
18385
18439
  async function _agentRepl(root, opts) {
18386
18440
  // 1.9.153: .env 자동 로드 (REPL 진입 직전) — install 직후 LEERNESS_ENABLE_* 즉시 반영
@@ -21241,6 +21295,8 @@ async function main() {
21241
21295
  if (cmd === 'skill' && args[1] === 'consolidate') return skillConsolidate(absRoot(arg('--path', process.cwd())));
21242
21296
  if (cmd === 'skill' && args[1] === 'install') return await skillInstallCmd(absRoot(arg('--path', process.cwd())), args[2]);
21243
21297
  if (cmd === 'skill' && args[1] === 'discover') return await skillDiscoverCmd(absRoot(arg('--path', process.cwd())));
21298
+ // 1.9.286 (UR-0024): leerness skill impact — 스킬 설치 영향 경량 상관추적
21299
+ if (cmd === 'skill' && args[1] === 'impact') return skillImpactCmd(args[2] || arg('--path', process.cwd()));
21244
21300
  if (cmd === 'skill' && args[1] === 'export') return skillExportCmd(absRoot(arg('--path', process.cwd())), args[2]);
21245
21301
  if (cmd === 'skill' && args[1] === 'export-all') return skillExportAllCmd(absRoot(arg('--path', process.cwd())));
21246
21302
  if (cmd === 'skill' && args[1] === 'match') return skillMatchCmd(absRoot(arg('--path', process.cwd())), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
@@ -21508,5 +21564,7 @@ module.exports = {
21508
21564
  // 1.9.281: 권한 등급 (UR-0034) — 단위 테스트
21509
21565
  PERMISSION_TIERS, _tierRank, _requiredTier, _policyAllows, _loadPolicy, _savePolicy, _policyEnforce, policyCmd,
21510
21566
  // 1.9.285: 외부 OSS 재사용 게이트 (UR-0023) — 단위 테스트
21511
- REUSE_CATEGORIES, REUSE_CHECKLIST, _reuseDetect, reuseCheckCmd
21567
+ REUSE_CATEGORIES, REUSE_CHECKLIST, _reuseDetect, reuseCheckCmd,
21568
+ // 1.9.286: 스킬 영향 상관추적 (UR-0024) — 단위 테스트
21569
+ _parseEvidenceStats, skillImpactCmd
21512
21570
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.285",
3
+ "version": "1.9.286",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -2986,5 +2986,26 @@ total++;
2986
2986
  if (!ok) failed++;
2987
2987
  }
2988
2988
 
2989
+ // 1.9.286 회귀 (UR-0024): skill impact — 영향 경량 상관추적 + 정직한 advisory
2990
+ total++;
2991
+ {
2992
+ const iDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-impact-'));
2993
+ cp.spawnSync(process.execPath, [CLI, 'init', iDir, '--minimal', '--no-env', '--yes'], { encoding: 'utf8', timeout: 30000, env: { ...process.env, LEERNESS_OFFLINE: '1' } });
2994
+ // 표본 부족 (검증 0건) → 판단 보류 advisory
2995
+ let lowOk = false;
2996
+ try { const j = JSON.parse(cp.spawnSync(process.execPath, [CLI, 'skill', 'impact', iDir, '--json'], { encoding: 'utf8', timeout: 15000 }).stdout); lowOk = Array.isArray(j.skills) && j.sampleSize === 0 && /표본 부족/.test(j.advisory) && j.network === false; } catch {}
2997
+ // review-evidence 에 pass/fail 6건 시드 → 통과율 계산
2998
+ const evFile = path.join(iDir, '.harness', 'review-evidence.md');
2999
+ let seed = '# Review Evidence\n';
3000
+ for (let k = 0; k < 5; k++) seed += `\n## 2026-06-03 0${k}:00\nCommand: npm test\nExit: 0\n`;
3001
+ seed += `\n## 2026-06-03 06:00\nCommand: build\nExit: 1\n`;
3002
+ fs.appendFileSync(evFile, seed);
3003
+ let rateOk = false;
3004
+ try { const j = JSON.parse(cp.spawnSync(process.execPath, [CLI, 'skill', 'impact', iDir, '--json'], { encoding: 'utf8', timeout: 15000 }).stdout); rateOk = j.sampleSize >= 6 && j.evidence.pass === 5 && j.evidence.fail === 1 && j.evidence.rate === 83 && /상관추적/.test(j.advisory); } catch {}
3005
+ const ok = lowOk && rateOk;
3006
+ console.log(ok ? '✓ B(1.9.286) skill impact: 표본부족 advisory + 검증 통과율 상관 + json' : `✗ skill impact 실패 (low=${lowOk} rate=${rateOk})`);
3007
+ if (!ok) failed++;
3008
+ }
3009
+
2989
3010
  console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
2990
3011
  if (failed > 0) process.exit(1);