leerness 1.9.84 → 1.9.86

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,52 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.86 — 2026-05-20
4
+
5
+ **MCP server 18번째 도구 `leerness_health` 추가** (1.9.85 health 외부 AI 노출).
6
+
7
+ ### Added — MCP 18번째 도구
8
+ - `leerness_health` — 1.9.85 종합 헬스 체크를 외부 AI에 노출.
9
+ - inputSchema: `{ path: string, strict: boolean }`
10
+ - 응답: `health --json` 결과 (drift + security + skills + usage + tasks + issues)
11
+ - 외부 AI가 워크스페이스 상태 한 번에 인지.
12
+ - MCP server 도구 카운트: **17 → 18**.
13
+
14
+ ### Use Case
15
+ - Claude Code / Cursor가 사용자 워크스페이스에서 작업 시작 시 → `leerness_health` 호출 → drift/보안/skill/MCP 상태 즉시 파악 → 적절한 행동 결정.
16
+ - "이 워크스페이스 보안 안전한가?" "어떤 skill 있나?" "MCP 호출 패턴은?" 한 호출로 답변.
17
+
18
+ ### Verified
19
+ - stress-v32 — MCP 18 도구 + health 호출 + JSON 응답 + 누적 회귀.
20
+ - e2e 219/219 PASS 유지.
21
+
22
+ ---
23
+
24
+ ## 1.9.85 — 2026-05-20
25
+
26
+ **`leerness health` 새 명령** — 종합 헬스 체크 (drift + 보안 + skill + MCP + 누적 통계).
27
+
28
+ ### Added — `leerness health [<path>]`
29
+ - 종합 진단 한 번에:
30
+ - `drift`: level + score + fired 개수
31
+ - `보안`: .env 존재 / .gitignore에 .env 포함 / .env.example 누락 키 / .gitignore 시크릿 패턴 누락
32
+ - `skills`: 설치 수 / skill query 누적 (rolling history)
33
+ - `usage`: 명령 호출 총수 + 종류 / MCP 호출 총수 + 종류 / since
34
+ - `tasks`: progress-tracker 총수 + 상태별 카운트
35
+ - **`issues`** 배열에 발견된 모든 문제 자동 집계.
36
+ - `--json`: 구조화된 JSON 출력 (CI 친화).
37
+ - `--strict`: issue ≥ 1 시 exit 1.
38
+
39
+ ### Use Case
40
+ - 사용자: `leerness health .` 한 줄로 워크스페이스 전체 상태 즉시 확인.
41
+ - CI 통합: `leerness health . --strict` 로 보안/drift 문제 자동 감지.
42
+ - 1.9.78 drift + 1.9.75/76/80 보안 + 1.9.70 MCP 통계 + 1.9.79 skill suggest의 모든 신호를 한 곳에 집계.
43
+
44
+ ### Verified
45
+ - stress-v31 — health 출력 / --json / --strict / 누적 회귀.
46
+ - e2e 219/219 PASS 유지.
47
+
48
+ ---
49
+
3
50
  ## 1.9.84 — 2026-05-20
4
51
 
5
52
  **MCP server 17번째 도구 `leerness_skill_list` 추가** (외부 AI에 skill 목록 조회 노출).
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.84-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.86-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.84 AI Agent Reliability Harness ║
15
+ ║ v1.9.86 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.86** — **MCP server 18번째 도구 `leerness_health`** — 1.9.85 health 종합 헬스 체크를 외부 AI에 노출. drift + 보안 + skill + MCP + tasks 한 호출.
437
+ - **1.9.85** — **`leerness health` 새 명령** — drift + 보안 + skill + MCP + 누적 통계 종합 헬스 체크. `--json` / `--strict` 옵션.
436
438
  - **1.9.84** — **MCP server 17번째 도구 `leerness_skill_list`** — 외부 AI에 skill 카탈로그 (id/이름/출처/능력/사용 횟수) 조회 노출. `skill list --json` 옵션도 함께 추가.
437
439
  - **1.9.83** — **MCP server 16번째 도구 `leerness_skill_match`** — 1.9.45/50/68 skill match를 외부 AI에 노출. rolling history 자동 누적.
438
440
  - **1.9.82** — **`drift check --auto-fix` 보안 회복 통합** — 1.9.78 보안 신호 발견 시 `audit --fix` 자동 실행 → `.gitignore` + `.env.example` 동기화 후 재검사. 보안 + 세션 마감 한 번에 자동 회복.
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.84';
9
+ const VERSION = '1.9.86';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -3398,16 +3398,17 @@ function _banner(opts = {}) {
3398
3398
  lines.push('');
3399
3399
  for (const ln of lines) log(ln);
3400
3400
  if (opts.quickStart) {
3401
- log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.84+ 워크플로)')));
3401
+ log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.86+ 워크플로)')));
3402
3402
  log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트 + 외부 AI CLI 설정'));
3403
3403
  log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 + lessons + 매칭 skill + 이전 history hit (1.9.69)'));
3404
3404
  log(' ' + C.green('npx leerness skill match "<query>"') + C.dim(' # 매칭 skill + rolling history 자동 누적 (1.9.68)'));
3405
3405
  log(' ' + C.green('npx leerness verify-claim T-0001 --run-tests') + C.dim(' # AI 거짓 완료 자동 검증'));
3406
3406
  log(' ' + C.green('npx leerness env check .') + C.dim(' # .env ↔ .env.example 동기화 검사 (1.9.71)'));
3407
+ log(' ' + C.green('npx leerness health .') + C.dim(' # 종합 헬스 체크 — drift + 보안 + skill + MCP (1.9.85)'));
3407
3408
  log(' ' + C.green('npx leerness session close .') + C.dim(' # 마감 + 다음 라운드 추천 (default)'));
3408
3409
  log('');
3409
3410
  log(C.bold(C.cyan(' 🤖 메인 에이전트 (Claude/Cursor/Copilot)용')));
3410
- log(' ' + C.green('npx leerness mcp serve') + C.dim(' # MCP 서버 — 17 도구 (skill_list 포함, 1.9.84)'));
3411
+ log(' ' + C.green('npx leerness mcp serve') + C.dim(' # MCP 서버 — 18 도구 (health 포함, 1.9.86)'));
3411
3412
  log(' ' + C.green('npx leerness agents bench "<task>"') + C.dim(' # 3 CLI 동시 비교'));
3412
3413
  log('');
3413
3414
  }
@@ -7351,7 +7352,8 @@ function mcpServeCmd(root) {
7351
7352
  { name: 'leerness_env_check', description: '1.9.71/73 — .env vs .env.example 동기화 검사 (보안: 키만, 값 미노출). exit 1 if 누락 키 있음', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
7352
7353
  { name: 'leerness_brainstorm', description: '1.9.16/72/77 — 누적 컨텍스트(decisions+skills+tasks+rules+evidence+lessons+skillHistory+taskLogFails) 자원 회수. 외부 AI가 새 작업 시작 전 호출', inputSchema: { type: 'object', properties: { topic: { type: 'string' }, path: { type: 'string' }, allApps: { type: 'boolean' } }, required: ['topic'] } },
7353
7354
  { name: 'leerness_skill_match', description: '1.9.45/50/83 — 사용자 task 키워드에 매칭되는 설치된 skill 추천 (jaccard 또는 embedding). 1.9.68 rolling history 자동 누적', inputSchema: { type: 'object', properties: { query: { type: 'string' }, path: { type: 'string' }, useEmbedding: { type: 'boolean' } }, required: ['query'] } },
7354
- { name: 'leerness_skill_list', description: '1.9.84 — 워크스페이스에 설치된 skill 목록 + 사용 횟수 + 출처 (catalog/user). 외부 AI가 사용 가능한 skill 조회', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } }
7355
+ { name: 'leerness_skill_list', description: '1.9.84 — 워크스페이스에 설치된 skill 목록 + 사용 횟수 + 출처 (catalog/user). 외부 AI가 사용 가능한 skill 조회', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
7356
+ { name: 'leerness_health', description: '1.9.85/86 — 종합 헬스 체크 (drift + 보안 + skills + MCP + tasks + issues 배열). 외부 AI가 워크스페이스 상태 한 번에 확인', inputSchema: { type: 'object', properties: { path: { type: 'string' }, strict: { type: 'boolean' } } } }
7355
7357
  ];
7356
7358
 
7357
7359
  function send(obj) {
@@ -7400,6 +7402,7 @@ function mcpServeCmd(root) {
7400
7402
  case 'leerness_brainstorm': cliArgs = ['brainstorm', args.topic || '', '--path', targetPath, '--json', ...(args.allApps ? ['--all-apps'] : [])]; break;
7401
7403
  case 'leerness_skill_match': cliArgs = ['skill', 'match', args.query || '', '--path', targetPath, '--json', ...(args.useEmbedding ? ['--embedding'] : [])]; break;
7402
7404
  case 'leerness_skill_list': cliArgs = ['skill', 'list', targetPath, '--json']; break;
7405
+ case 'leerness_health': cliArgs = ['health', targetPath, '--json', ...(args.strict ? ['--strict'] : [])]; break;
7403
7406
  default:
7404
7407
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
7405
7408
  }
@@ -7581,6 +7584,118 @@ function envSyncCmd(root) {
7581
7584
  for (const k of d.inEnvOnly) log(` + ${k}=`);
7582
7585
  }
7583
7586
 
7587
+ // 1.9.85: leerness health — 종합 헬스 체크 (drift + 보안 + skill + MCP + 누적)
7588
+ function healthCmd(root) {
7589
+ root = absRoot(root || process.cwd());
7590
+ const out = { root, generatedAt: new Date().toISOString(), checks: {} };
7591
+ // 1) drift level
7592
+ try {
7593
+ const r = cp.spawnSync(process.execPath, [__filename, 'drift', 'check', root, '--json'],
7594
+ { encoding: 'utf8', timeout: 15000, env: { ...process.env, LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '0' } });
7595
+ const j = JSON.parse(r.stdout.trim());
7596
+ out.checks.drift = { level: j.level, score: j.score, firedCount: (j.fired || []).length };
7597
+ } catch { out.checks.drift = { error: 'drift check 실패' }; }
7598
+ // 2) 보안 상태 (env + .gitignore)
7599
+ try {
7600
+ const envPath = path.join(root, '.env');
7601
+ if (exists(envPath)) {
7602
+ const d = envDiff(root);
7603
+ const giText = exists(path.join(root, '.gitignore')) ? read(path.join(root, '.gitignore')) : '';
7604
+ const giLines = giText.split('\n').map(l => l.trim());
7605
+ const envInGi = giLines.includes('.env') || giLines.includes('/.env');
7606
+ const SECRET_PATTERNS = ['.env', '.env.local', '.env.production', '.env.*.local', '*.pem', 'credentials.json'];
7607
+ const missingSecrets = SECRET_PATTERNS.filter(p => !giLines.some(l => l === p || l === '/' + p));
7608
+ out.checks.security = {
7609
+ hasDotEnv: true,
7610
+ envInGitignore: envInGi,
7611
+ envExampleMissing: d.inEnvOnly,
7612
+ gitignoreMissingSecrets: missingSecrets,
7613
+ critical: !envInGi
7614
+ };
7615
+ } else {
7616
+ out.checks.security = { hasDotEnv: false, ok: true };
7617
+ }
7618
+ } catch { out.checks.security = { error: '보안 점검 실패' }; }
7619
+ // 3) skill 수 + skill query 누적
7620
+ try {
7621
+ const all = listAllSkills(root);
7622
+ const skillCount = Object.keys(all).length;
7623
+ let queryCount = 0;
7624
+ const histPath = path.join(root, '.harness', 'skill-suggestions.md');
7625
+ if (exists(histPath)) {
7626
+ queryCount = (read(histPath).match(/^## [\d-]+ [\d:]+ — query/gm) || []).length;
7627
+ }
7628
+ out.checks.skills = { installed: skillCount, queryHistoryCount: queryCount };
7629
+ } catch { out.checks.skills = { error: 'skill 점검 실패' }; }
7630
+ // 4) MCP + 명령 호출 누적
7631
+ try {
7632
+ const stats = _readUsageStats(root);
7633
+ const cmdTotal = Object.values(stats.commands || {}).reduce((s, n) => s + n, 0);
7634
+ const mcpTotal = stats.mcp?.tools ? Object.values(stats.mcp.tools).reduce((s, n) => s + n, 0) : 0;
7635
+ out.checks.usage = {
7636
+ commandTotal: cmdTotal,
7637
+ commandKinds: Object.keys(stats.commands || {}).length,
7638
+ mcpTotal,
7639
+ mcpToolKinds: stats.mcp?.tools ? Object.keys(stats.mcp.tools).length : 0,
7640
+ since: stats.since || null
7641
+ };
7642
+ } catch { out.checks.usage = { error: 'usage 점검 실패' }; }
7643
+ // 5) tasks (progress-tracker)
7644
+ try {
7645
+ const rows = readProgressRows(root);
7646
+ const byStatus = {};
7647
+ for (const r of rows) byStatus[r.status] = (byStatus[r.status] || 0) + 1;
7648
+ out.checks.tasks = { total: rows.length, byStatus };
7649
+ } catch { out.checks.tasks = { error: 'tasks 점검 실패' }; }
7650
+ // 6) issues 요약 (사용자 글로벌 룰 가시화)
7651
+ const issues = [];
7652
+ if (out.checks.drift?.level && !/healthy/.test(out.checks.drift.level)) issues.push(`drift ${out.checks.drift.level}`);
7653
+ if (out.checks.security?.critical) issues.push('🚨 .env가 .gitignore에 누락 (보안 CRITICAL)');
7654
+ if (out.checks.security?.envExampleMissing?.length) issues.push(`.env→.env.example 누락 ${out.checks.security.envExampleMissing.length}건`);
7655
+ if (out.checks.security?.gitignoreMissingSecrets?.length) issues.push(`.gitignore 시크릿 누락 ${out.checks.security.gitignoreMissingSecrets.length}건`);
7656
+ out.issues = issues;
7657
+ out.healthy = issues.length === 0;
7658
+
7659
+ // --strict: issue 있으면 exit 1
7660
+ if (has('--strict') && !out.healthy) process.exitCode = 1;
7661
+
7662
+ if (has('--json')) { log(JSON.stringify(out, null, 2)); return; }
7663
+ log(`# leerness health (1.9.85)`);
7664
+ log(`Date: ${out.generatedAt}`);
7665
+ log(`Status: ${out.healthy ? '✅ healthy' : `⚠ ${issues.length} issues`}`);
7666
+ log('');
7667
+ log(`## drift`);
7668
+ log(` level: ${out.checks.drift?.level || 'n/a'} (score ${out.checks.drift?.score || 0}, fired ${out.checks.drift?.firedCount || 0})`);
7669
+ log('');
7670
+ log(`## 보안`);
7671
+ if (out.checks.security?.hasDotEnv) {
7672
+ log(` .env 존재 · .gitignore에 .env 포함: ${out.checks.security.envInGitignore ? '✓' : '✗ CRITICAL'}`);
7673
+ log(` .env.example 누락 키: ${out.checks.security.envExampleMissing?.length || 0}건`);
7674
+ log(` .gitignore 시크릿 패턴 누락: ${out.checks.security.gitignoreMissingSecrets?.length || 0}건`);
7675
+ } else {
7676
+ log(` .env 없음 (검증 불필요)`);
7677
+ }
7678
+ log('');
7679
+ log(`## skills`);
7680
+ log(` 설치: ${out.checks.skills?.installed || 0}개 · skill query 누적: ${out.checks.skills?.queryHistoryCount || 0}회`);
7681
+ log('');
7682
+ log(`## usage`);
7683
+ log(` 명령 호출: ${out.checks.usage?.commandTotal || 0}회 / ${out.checks.usage?.commandKinds || 0}종`);
7684
+ log(` MCP 호출: ${out.checks.usage?.mcpTotal || 0}회 / ${out.checks.usage?.mcpToolKinds || 0}종 도구`);
7685
+ log(` since: ${out.checks.usage?.since || 'unknown'}`);
7686
+ log('');
7687
+ log(`## tasks`);
7688
+ const tb = out.checks.tasks?.byStatus || {};
7689
+ log(` 총 ${out.checks.tasks?.total || 0}건: ${Object.entries(tb).map(([s, n]) => `${s}=${n}`).join(', ') || '없음'}`);
7690
+ if (issues.length) {
7691
+ log('');
7692
+ log(`## ⚠ Issues (${issues.length})`);
7693
+ for (const i of issues) log(` - ${i}`);
7694
+ log('');
7695
+ log(`💡 자동 회복: leerness drift check --auto-fix · leerness audit --fix`);
7696
+ }
7697
+ }
7698
+
7584
7699
  function usageStatsCmd(root) {
7585
7700
  root = absRoot(root || process.cwd());
7586
7701
  const stats = _readUsageStats(root);
@@ -7912,6 +8027,8 @@ async function main() {
7912
8027
  // 1.9.71: leerness env check / sync — .env vs .env.example 자동 동기화
7913
8028
  if (cmd === 'env' && args[1] === 'check') return envCheckCmd(args[2] || arg('--path', process.cwd()));
7914
8029
  if (cmd === 'env' && args[1] === 'sync') return envSyncCmd(args[2] || arg('--path', process.cwd()));
8030
+ // 1.9.85: leerness health — 종합 헬스 체크
8031
+ if (cmd === 'health') return healthCmd(args[1] || arg('--path', process.cwd()));
7915
8032
  if (cmd === 'whats-new') return whatsNewCmd(args[1] || arg('--path', process.cwd()));
7916
8033
  if (cmd === 'reuse' && args[1] === 'autodetect') return reuseAutodetectCmd(args[2] || arg('--path', process.cwd()));
7917
8034
  if (cmd === 'setup-agents' || cmd === 'setup' && args[1] === 'agents') return await setupAgentsCmd(args[1] && args[1] !== 'agents' ? args[1] : (args[2] || process.cwd()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.84",
3
+ "version": "1.9.86",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",