leerness 1.9.258 → 1.9.260

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,63 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.260 — 2026-05-31 — 🎉 1.9.260 / UR-0020 shell-guard (터미널 셸 호환성 린터 + 실패 메모리)
4
+
5
+ **🐚 사용자 명시 (UR-0020): 터미널 명령 실패를 파악·기록하고 다음 실행 시 셸 호환성(PowerShell 5.1 && 미지원 등)을 참조.**
6
+
7
+ ### 개발 방향 분석 (사용자 요청 "알맞는 방향 고민")
8
+ leerness 는 0-dependency Node CLI 라 셸을 직접 가로챌 수 없음 → 가장 안전·유용한 방향은 **(1) 실행 전 정적 린터 + (2) 실패 메모리 + (3) 환경 버전 변동 감지**. 기존 인프라(`_collectRuntimeEnv` 1.9.241 PS 버전 감지 + `environment.json` 1.9.145 스냅샷) 재사용.
9
+
10
+ ### 구현 (`leerness shell-guard "<command>"`)
11
+ 1. **정적 셸 호환성 린터** `_shellGuardAnalyze(cmd, ctx)` (순수 함수, 6 규칙):
12
+ - **ps5-chain** (error): Windows PowerShell 5.1 `&&`/`||` 미지원 → `A; if ($?) { B }` 제안 (핵심 시나리오)
13
+ - **ps-devnull** (error): `2>/dev/null` → `2>$null`
14
+ - **ps-inline-env** (error): `VAR=x cmd` → `$env:VAR='x'; cmd`
15
+ - **ps-rm-rf** (warn): `rm -rf` → `Remove-Item -Recurse -Force`
16
+ - **cmd-semicolon** (warn): CMD `;` 구분자 아님 → `&` / `&&`
17
+ - **ps-version-unknown** (info): PS 버전 미상 + `&&` → 안전 패턴 권장
18
+ 2. **실패 메모리** `.harness/shell-failures.json` (200건 cap):
19
+ - `shell-guard --record --cmd "..." --exit N` 기록 → 다음 분석 시 동일/유사(첫 토큰) 실패 회수
20
+ 3. **환경 버전 변동 감지**: `environment.json` 스냅샷 vs 현재 node/PowerShell 버전 비교 → 변동 시 경고
21
+ 4. PS 버전·셸 자동 판별 (`_detectShellCtx`) + `--json` (error 시 exit 1) + 순수 함수 export (단위 테스트)
22
+
23
+ ### 다음 라운드 참조 (UR-0020 후속 후보)
24
+ - handoff/agent-mode 에 shell-failures 요약 자동 노출 · MCP leerness_shell_guard 도구 · selftest 케이스 추가
25
+
26
+ ### stress-v205 — **27/27 PASS · 100%**
27
+ - 1.9.260 (16): 6 규칙 분석 + record/advise 회수 + 200 cap + CLI/json/help
28
+ - 성능 (1): cold start avg 441ms
29
+ - 누적 회귀 (10): 1.9.207~259
30
+
31
+ ### 자동 release (122 main-push streak · 83 npm publish streak · R216)
32
+
33
+ 🐚 **셸 실패 예방** — Windows PowerShell 5.1 `&&` 등 셸 차이로 인한 터미널 실패를 실행 전 감지 + 과거 실패 회수.
34
+
35
+ ---
36
+
37
+ ## 1.9.259 — 2026-05-30 — MCP leerness_selftest (71 도구) + npm test 무결성 게이트
38
+
39
+ **🔌 1.9.258 selftest 를 워크플로에 통합: 외부 AI(MCP) + 배포 파이프라인(npm test) 양쪽에서 무결성 검증.**
40
+
41
+ ### 구현
42
+ 1. **MCP 71번째 도구 `leerness_selftest`** (70 → 71):
43
+ - 외부 AI(Claude Code 등)가 `tools/call` 로 leerness 무결성 확인 → `{ version, total, pass, fail, ok, results[] }`
44
+ - tools/list 71 도구 노출 + 핸들러 → `selftest --json`
45
+ - 실제 stdio JSON-RPC 라운드트립 검증: tools/list 71 + tools/call ok=true 13/13
46
+ 2. **npm test 무결성 게이트**:
47
+ - `test`: `--version && selftest && e2e` — selftest 가 e2e 보다 먼저 (fast-fail)
48
+ - 배포/CI 시 코어 함수 손상을 e2e 전에 즉시 감지 (release sync-main 자동 publish 안전망)
49
+
50
+ ### stress-v204 — **18/18 PASS · 100%**
51
+ - 1.9.259 (7): VERSION + MCP 71 정의/핸들러 + 실 서버 list/call + npm test 게이트 순서
52
+ - 성능 (1): cold start avg 371ms
53
+ - 누적 회귀 (10): 1.9.207~258 + MCP initialize 프로토콜 회귀
54
+
55
+ ### 자동 release (121 main-push streak · 82 npm publish streak · R215)
56
+
57
+ 🔌 **selftest 3중 노출 완성** — CLI(1.9.258) + MCP 도구 + npm test 게이트. 외부 AI·CI·배포 모두 무결성 자동 검증.
58
+
59
+ ---
60
+
3
61
  ## 1.9.258 — 2026-05-30 — leerness selftest 명령 (코어 함수 무결성 자가 검증)
4
62
 
5
63
  **🩺 사용자/CI 가 설치된 leerness 바이너리의 건강 상태를 1초 내 검증하는 신규 명령.**
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.258-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v203-21%2F21-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-70-brightgreen)]() [![cli](https://img.shields.io/badge/CLI_commands-58-brightgreen)]() [![json-fields](https://img.shields.io/badge/JSON_통합-11_필드-blueviolet)]() [![rounds](https://img.shields.io/badge/autonomous_R-214-blueviolet)]() [![main-push](https://img.shields.io/badge/🎉_main--push_streak-120_rounds-success)]() [![npm-streak](https://img.shields.io/badge/🎉_npm--publish-81_streak-success)]() [![encoding](https://img.shields.io/badge/🌐_인코딩보호-Windows%2BPOSIX-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-368ms-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.260-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v205-27%2F27-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-71-brightgreen)]() [![cli](https://img.shields.io/badge/CLI_commands-59-brightgreen)]() [![json-fields](https://img.shields.io/badge/JSON_통합-11_필드-blueviolet)]() [![rounds](https://img.shields.io/badge/autonomous_R-216-blueviolet)]() [![main-push](https://img.shields.io/badge/🎉_main--push_streak-122_rounds-success)]() [![npm-streak](https://img.shields.io/badge/🎉_npm--publish-83_streak-success)]() [![encoding](https://img.shields.io/badge/🌐_인코딩보호-Windows%2BPOSIX-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-368ms-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,9 +12,9 @@
12
12
  ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
13
13
  ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
14
14
  ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
15
- ║ v1.9.258 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.260 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- 🩺 leerness selftest 코어 함수 무결성 자가 검증 (R214)
17
+ 🐚 shell-guardPowerShell 5.1 && 실패 예방 (UR-0020)
18
18
  ╚══════════════════════════════════════════════════════════════╝
19
19
  ```
20
20
 
package/bin/harness.js CHANGED
@@ -7,7 +7,7 @@ const cp = require('child_process');
7
7
  const os = require('os'); // 1.9.178: _publishToNpm 에서 os.tmpdir() 사용 (전역 import)
8
8
  const readline = require('readline');
9
9
 
10
- const VERSION = '1.9.258';
10
+ const VERSION = '1.9.260';
11
11
 
12
12
  // 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
13
13
  // leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
@@ -2941,6 +2941,150 @@ function selfTestCmd(opts = {}) {
2941
2941
  }
2942
2942
  }
2943
2943
 
2944
+ // 1.9.260: shell-guard — 터미널 명령 셸 호환성 린터 + 실패 메모리 (사용자 명시 UR-0020)
2945
+ // 배경: leerness 적용 프로젝트에서 터미널 명령이 셸 차이(PowerShell 5.1 && 미지원 등)로 실패.
2946
+ // leerness 는 셸을 직접 가로챌 수 없으므로(0-dep Node CLI) → 실행 전 정적 분석 + 실패 기록/회수로 대응.
2947
+ // ctx: { shell: 'powershell'|'cmd'|'bash'|'sh', psVersion: '5'|'7'|null }
2948
+ // 반환: { shell, psVersion, issues: [{ rule, severity, detail, suggestion }] } — 순수 함수 (단위 테스트 가능)
2949
+ function _shellGuardAnalyze(cmd, ctx) {
2950
+ const c = String(cmd || '');
2951
+ const shell = (ctx && ctx.shell) || 'unknown';
2952
+ const psVer = ctx && ctx.psVersion != null ? parseInt(ctx.psVersion, 10) : null;
2953
+ const issues = [];
2954
+ const isWinPowerShell = shell === 'powershell' && psVer != null && psVer < 6; // 5.1 = Windows PowerShell
2955
+ // 규칙 1: PowerShell 5.1 에서 && / || 체이닝 미지원 (pwsh 7+ 부터 지원)
2956
+ if (isWinPowerShell && /\s&&\s|\s\|\|\s/.test(c)) {
2957
+ issues.push({ rule: 'ps5-chain', severity: 'error', detail: 'Windows PowerShell 5.1 은 && / || 연산자를 지원하지 않습니다 (PowerShell 7+ 부터 지원).', suggestion: 'A; if ($?) { B } (조건부) 또는 A; B (무조건) 로 분리. 또는 pwsh 7 설치.' });
2958
+ }
2959
+ // 규칙 2: PowerShell 에서 2>/dev/null → 2>$null
2960
+ if (shell === 'powershell' && /2>\s*\/dev\/null/.test(c)) {
2961
+ issues.push({ rule: 'ps-devnull', severity: 'error', detail: 'PowerShell 은 /dev/null 경로가 없습니다.', suggestion: '2>$null 사용 (PowerShell 리다이렉트).' });
2962
+ }
2963
+ // 규칙 3: PowerShell 에서 inline env (VAR=val cmd) 미지원
2964
+ if (shell === 'powershell' && /^[A-Z_][A-Z0-9_]*=[^\s]+\s+\S/.test(c.trim())) {
2965
+ issues.push({ rule: 'ps-inline-env', severity: 'error', detail: 'PowerShell 은 VAR=val cmd 형식의 inline 환경변수를 지원하지 않습니다.', suggestion: "$env:VAR='val'; cmd 로 분리." });
2966
+ }
2967
+ // 규칙 4: PowerShell 에서 Unix 전용 명령 (rm -rf / ls -la 등) — 별칭은 되나 플래그 오류 가능
2968
+ if (shell === 'powershell' && /\brm\s+-rf\b/.test(c)) {
2969
+ issues.push({ rule: 'ps-rm-rf', severity: 'warn', detail: 'PowerShell 에서 rm -rf 는 -rf 플래그 파싱 오류 가능 (rm 은 Remove-Item 별칭).', suggestion: 'Remove-Item -Recurse -Force <path> 사용.' });
2970
+ }
2971
+ // 규칙 5: CMD 에서 ; 는 명령 구분자가 아님 (한 줄로 실행됨)
2972
+ if (shell === 'cmd' && /;/.test(c) && !/&&|\|\|/.test(c)) {
2973
+ issues.push({ rule: 'cmd-semicolon', severity: 'warn', detail: 'CMD 는 ; 를 명령 구분자로 처리하지 않습니다 (인자로 전달됨).', suggestion: 'A && B (조건부) 또는 A & B (무조건) 사용.' });
2974
+ }
2975
+ // 규칙 6: PowerShell 에서 && 가 있으나 버전 미상 — 정보성
2976
+ if (shell === 'powershell' && psVer == null && /\s&&\s/.test(c)) {
2977
+ issues.push({ rule: 'ps-version-unknown', severity: 'info', detail: 'PowerShell 버전 미상 — 5.1 이면 && 미지원, 7+ 이면 지원.', suggestion: '$PSVersionTable.PSVersion 확인. 안전하게 A; if ($?) { B } 권장.' });
2978
+ }
2979
+ return { shell, psVersion: psVer, issues };
2980
+ }
2981
+ function _shellFailuresPath(root) { return path.join(absRoot(root), '.harness', 'shell-failures.json'); }
2982
+ function _loadShellFailures(root) {
2983
+ try { const f = _shellFailuresPath(root); if (!exists(f)) return { failures: [] }; const j = JSON.parse(read(f)); return j && Array.isArray(j.failures) ? j : { failures: [] }; } catch { return { failures: [] }; }
2984
+ }
2985
+ function _recordShellFailure(root, entry) {
2986
+ const data = _loadShellFailures(root);
2987
+ data.failures.push(entry);
2988
+ if (data.failures.length > 200) data.failures = data.failures.slice(-200); // cap
2989
+ data.updatedAt = new Date().toISOString();
2990
+ try { writeUtf8(_shellFailuresPath(root), JSON.stringify(data, null, 2) + '\n'); } catch {}
2991
+ return data;
2992
+ }
2993
+ function _detectShellCtx() {
2994
+ // 현재 환경에서 셸 종류 + PowerShell 버전 추정 (_collectRuntimeEnv 재사용)
2995
+ let env; try { env = _collectRuntimeEnv(); } catch { env = null; }
2996
+ let shell = 'unknown';
2997
+ const sh = (env && env.terminal && env.terminal.shell) || process.env.SHELL || process.env.ComSpec || '';
2998
+ if (/powershell|pwsh/i.test(sh)) shell = 'powershell';
2999
+ else if (/cmd\.exe/i.test(sh)) shell = 'cmd';
3000
+ else if (/bash/i.test(sh)) shell = 'bash';
3001
+ else if (/\bsh\b|zsh/i.test(sh)) shell = 'sh';
3002
+ else if (process.platform === 'win32') shell = (env && env.terminal && env.terminal.powershell) ? 'powershell' : 'cmd';
3003
+ const psVersion = env && env.terminal && env.terminal.powershell ? env.terminal.powershell : null;
3004
+ return { shell, psVersion, raw: sh };
3005
+ }
3006
+ // 환경 버전 변동 감지 (environment.json 스냅샷 vs 현재 — 사용자 명시: 과거↔현재 버전 업데이트)
3007
+ function _shellEnvDrift(root) {
3008
+ try {
3009
+ const snapPath = path.join(absRoot(root), '.harness', 'environment.json');
3010
+ if (!exists(snapPath)) return null;
3011
+ const snap = JSON.parse(read(snapPath));
3012
+ const cur = _collectRuntimeEnv();
3013
+ const changes = [];
3014
+ const snapNode = snap.node && snap.node.version;
3015
+ if (snapNode && cur.node.version && snapNode !== cur.node.version) changes.push({ what: 'node', from: snapNode, to: cur.node.version });
3016
+ const snapPs = snap.terminal && snap.terminal.powershell;
3017
+ if (snapPs && cur.terminal.powershell && String(snapPs) !== String(cur.terminal.powershell)) changes.push({ what: 'powershell', from: snapPs, to: cur.terminal.powershell });
3018
+ return { changes, capturedAt: snap.capturedAt || null };
3019
+ } catch { return null; }
3020
+ }
3021
+ function shellGuardCmd(root, cmd, opts = {}) {
3022
+ const isTty = process.stdout && process.stdout.isTTY;
3023
+ const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
3024
+ const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
3025
+ const yl = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
3026
+ const rd = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
3027
+ const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3028
+ const ctx = _detectShellCtx();
3029
+ // record 모드: 실패한 명령 기록
3030
+ if (opts.record) {
3031
+ if (!cmd) { fail('shell-guard --record 에는 --cmd "<명령>" 필요'); return; }
3032
+ const analysis = _shellGuardAnalyze(cmd, ctx);
3033
+ const entry = { cmd, exitCode: opts.exit != null ? opts.exit : null, shell: ctx.shell, psVersion: ctx.psVersion, issues: analysis.issues.map(i => i.rule), ts: new Date().toISOString() };
3034
+ _recordShellFailure(root, entry);
3035
+ if (opts.json) { log(JSON.stringify({ recorded: true, entry }, null, 2)); return; }
3036
+ log(gr(`✓ shell 실패 기록됨: ${cmd.slice(0, 60)}${cmd.length > 60 ? '…' : ''} (shell=${ctx.shell}, exit=${entry.exitCode})`));
3037
+ if (analysis.issues.length) log(dm(` 감지된 패턴: ${analysis.issues.map(i => i.rule).join(', ')}`));
3038
+ return;
3039
+ }
3040
+ // 기본/advise 모드: 명령 분석 + 과거 실패 회수 + 버전 변동
3041
+ if (!cmd) {
3042
+ // 명령 없으면 환경 + 과거 실패 요약
3043
+ const data = _loadShellFailures(root);
3044
+ const drift = _shellEnvDrift(root);
3045
+ if (opts.json) { log(JSON.stringify({ shell: ctx.shell, psVersion: ctx.psVersion, failureCount: data.failures.length, recent: data.failures.slice(-5), envDrift: drift }, null, 2)); return; }
3046
+ log(cy(`# leerness shell-guard (1.9.260, UR-0020) — 셸 호환성 가드`));
3047
+ log('');
3048
+ log(` 현재 셸: ${gr(ctx.shell)}${ctx.psVersion ? ` (PowerShell ${ctx.psVersion}${parseInt(ctx.psVersion, 10) < 6 ? ' = Windows PowerShell, && 미지원' : ''})` : ''}`);
3049
+ log(` 기록된 실패: ${data.failures.length}건`);
3050
+ if (drift && drift.changes.length) {
3051
+ log(yl(` ⚠ 환경 버전 변동 감지 (과거 스냅샷 대비):`));
3052
+ drift.changes.forEach(ch => log(` ${ch.what}: ${ch.from} → ${ch.to}`));
3053
+ log(dm(` → 과거 실패 기록 재검토 권장 (버전 변경으로 동작 달라질 수 있음)`));
3054
+ }
3055
+ if (data.failures.length) {
3056
+ log(dm(` 최근 실패 (최대 3):`));
3057
+ data.failures.slice(-3).reverse().forEach(f => log(dm(` • ${(f.cmd || '').slice(0, 50)} (exit=${f.exitCode}, ${f.shell})`)));
3058
+ }
3059
+ log('');
3060
+ log(dm(` 명령 분석: leerness shell-guard "<command>" · 실패 기록: shell-guard --record --cmd "..." --exit N`));
3061
+ return;
3062
+ }
3063
+ const analysis = _shellGuardAnalyze(cmd, ctx);
3064
+ // 과거 동일/유사 실패 회수 (정확 일치 + 첫 토큰 일치)
3065
+ const data = _loadShellFailures(root);
3066
+ const firstTok = cmd.trim().split(/\s+/)[0];
3067
+ const pastSame = data.failures.filter(f => f.cmd === cmd).length;
3068
+ const pastSimilar = data.failures.filter(f => f.cmd && f.cmd.trim().split(/\s+/)[0] === firstTok).length;
3069
+ if (opts.json) { log(JSON.stringify({ ...analysis, pastSame, pastSimilar, ok: analysis.issues.filter(i => i.severity === 'error').length === 0 }, null, 2)); if (analysis.issues.some(i => i.severity === 'error')) process.exitCode = 1; return; }
3070
+ log(cy(`# leerness shell-guard (1.9.260, UR-0020)`));
3071
+ log(` 명령: ${dm(cmd.slice(0, 80))}${cmd.length > 80 ? '…' : ''}`);
3072
+ log(` 셸: ${ctx.shell}${ctx.psVersion ? ` (PS ${ctx.psVersion})` : ''}`);
3073
+ log('');
3074
+ if (analysis.issues.length === 0) {
3075
+ log(gr(` ✓ 셸 호환성 문제 없음`));
3076
+ } else {
3077
+ analysis.issues.forEach(i => {
3078
+ const mark = i.severity === 'error' ? rd('✗') : (i.severity === 'warn' ? yl('⚠') : dm('ℹ'));
3079
+ log(` ${mark} [${i.rule}] ${i.detail}`);
3080
+ log(dm(` → ${i.suggestion}`));
3081
+ });
3082
+ }
3083
+ if (pastSame > 0) log(yl(` ⚠ 이 명령은 과거 ${pastSame}회 실패 기록 있음`));
3084
+ else if (pastSimilar > 0) log(dm(` ℹ "${firstTok}" 시작 명령 과거 ${pastSimilar}회 실패 기록`));
3085
+ if (analysis.issues.some(i => i.severity === 'error')) process.exitCode = 1;
3086
+ }
3087
+
2944
3088
  // 1.9.245: API skill cache — 공식 문서 + 관련 링크 자동 정리 (사용자 명시 UR-0015)
2945
3089
  // .harness/api-skills/<id>.md 에 frontmatter + 본문 저장. handoff 자동 매칭.
2946
3090
  // Node built-in https (의존성 0), depth=1 same-domain crawl, max 10 links.
@@ -15893,7 +16037,8 @@ function mcpServeCmd(root) {
15893
16037
  { name: 'leerness_py_check', description: '1.9.239 (사용자 명시 UR-0013) — Python 파일 분석. 의존성 0 regex fallback. .md 외 .py 도 leerness 인지. 응답: { totalFiles, totalLOC, totalImports, totalFuncs, totalClasses, totalTodos, biggest: [{ file, loc, funcs, classes }] }. 외부 AI가 "이 프로젝트 Python 표면이 얼마나 되나"를 회수. 인자: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
15894
16038
  { name: 'leerness_agent_mode', description: '1.9.239 (사용자 명시 UR-0013) — 자율 모드 전용 통합 명령. start: handoff + drift --auto-fix + session-resume --auto-fix (진입). tick: pulse 한 줄 (매 라운드). stop: session close --auto-apply-delivered --auto-cleanup-branches (마감). 외부 AI 가 자율 라운드 진입/매 라운드/마감을 단일 호출로 수행. 인자: { path?, sub (required: "start"|"tick"|"stop"|"help") }', inputSchema: { type: 'object', properties: { path: { type: 'string' }, sub: { type: 'string', enum: ['start', 'tick', 'stop', 'help'] } }, required: ['sub'] } },
15895
16039
  { name: 'leerness_env_info', description: '1.9.241 (사용자 명시 UR-0014) — 환경 종합 정보 회수. OS / 언어 (LANG, 코드페이지) / 한국어 Windows / 하드웨어 / 터미널 (TTY, PowerShell 버전) / 도구 버전 (git, npm, python). 외부 AI 가 환경 호환성을 미리 인지 → 인코딩 오류 예방. 응답: { os, node, locale, hardware, terminal, tools }. 인자: { path?, encodingCheck? }. encodingCheck: true 시 셸 스크립트 (.ps1/.bat/.cmd/.sh) BOM 없는 비-ASCII 위험 감지', inputSchema: { type: 'object', properties: { path: { type: 'string' }, encodingCheck: { type: 'boolean' } } } },
15896
- { name: 'leerness_api_skill', description: '1.9.245 (사용자 명시 UR-0015) — API 문서·관련링크 자동 캐시. 공식 API 문서 URL을 fetch 하고 1단계 same-domain 관련 링크까지 정리 → .harness/api-skills/<id>.md 저장. 후속 같은 API 관련 작업 시 자동 참조. 응답: list (skills 배열) / show (전체 본문) / match (task 매칭 결과). 인자: { path?, sub ("list"|"show"|"match"|"add"|"drop"), url? (add), id? (show/drop), query? (match), direction? (add: 구현 방향 텍스트) }. 외부 AI가 "이 프로젝트 어떤 API 문서가 정리되어 있나?" / "내 작업과 매칭되는 API skill 있나?" 회수.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, sub: { type: 'string', enum: ['list', 'show', 'match', 'add', 'drop'] }, url: { type: 'string' }, id: { type: 'string' }, query: { type: 'string' }, direction: { type: 'string' } }, required: ['sub'] } }
16040
+ { name: 'leerness_api_skill', description: '1.9.245 (사용자 명시 UR-0015) — API 문서·관련링크 자동 캐시. 공식 API 문서 URL을 fetch 하고 1단계 same-domain 관련 링크까지 정리 → .harness/api-skills/<id>.md 저장. 후속 같은 API 관련 작업 시 자동 참조. 응답: list (skills 배열) / show (전체 본문) / match (task 매칭 결과). 인자: { path?, sub ("list"|"show"|"match"|"add"|"drop"), url? (add), id? (show/drop), query? (match), direction? (add: 구현 방향 텍스트) }. 외부 AI가 "이 프로젝트 어떤 API 문서가 정리되어 있나?" / "내 작업과 매칭되는 API skill 있나?" 회수.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, sub: { type: 'string', enum: ['list', 'show', 'match', 'add', 'drop'] }, url: { type: 'string' }, id: { type: 'string' }, query: { type: 'string' }, direction: { type: 'string' } }, required: ['sub'] } },
16041
+ { name: 'leerness_selftest', description: '1.9.258/259 — 설치된 leerness 바이너리의 코어 함수(보안 _isSecretKey / 버전 compareVer / 인코딩 _classifyCJK 등) 무결성 자가 검증. 응답: { version, total, pass, fail, ok, results[] }. 외부 AI/CI 가 "이 leerness 설치가 정상인가?(npx 캐시 손상·부분 설치 감지)" 를 1초 내 확인. 인자: 없음.', inputSchema: { type: 'object', properties: {} } }
15897
16042
  ];
15898
16043
 
15899
16044
  function send(obj) {
@@ -16096,6 +16241,10 @@ function mcpServeCmd(root) {
16096
16241
  if (args.apply === true) cliArgs.push('--apply');
16097
16242
  if (typeof args.keep === 'number') cliArgs.push('--keep', String(args.keep));
16098
16243
  break;
16244
+ case 'leerness_selftest':
16245
+ // 1.9.259 (1.9.258): 코어 함수 무결성 자가 검증
16246
+ cliArgs = ['selftest', '--json'];
16247
+ break;
16099
16248
  default:
16100
16249
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
16101
16250
  }
@@ -19752,7 +19901,7 @@ function whichCmd() {
19752
19901
  }
19753
19902
 
19754
19903
  function help() {
19755
- log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/agy/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 활성 N개 일괄 dispatch (--execute: 실 spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry — 사용자 정의 CLI provider 동적 추가 (OpenRouter/Bedrock 흡수)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi 모드 alias (또는 --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness which [--json] # 1.9.164 진단: 현재 실행 경로/버전 + npm 캐시 + PATH 후보 (구버전 충돌 해결)\n leerness selftest [--json] # 1.9.258 코어 함수 무결성 자가 검증 (설치 손상/부분설치 감지, CI 친화 exit 1)\n leerness path-setup [--apply] [--json] # 1.9.254 leerness CLI PATH 자동 등록 (npm global bin 미등록 시)\n leerness web check|screenshot|extract <url> [--out file.png] [--selector "css"] # 1.9.165 playwright bridge (opt-in: npm i -g playwright + permissions.browser)\n leerness pc check|click|type|screenshot [--x N --y N] [--text "s"] [--out f.png] # 1.9.166 robotjs/nut-tree bridge (opt-in: npm i -g robotjs + permissions.mouse/keyboard, ⚠ full 모드 권장)\n leerness lsp check|symbols|references <file/name> [--in dir] [--json] # 1.9.167 LSP 어댑터 MVP (typescript opt-in + regex fallback, 코드 인텔리전스)\n leerness review-request "<request>" [--json] # 1.9.176 사용자 요청 사전 검토 (충돌/재사용/효율/권장 단계 — 사용자 명시)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
19904
+ log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/agy/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 활성 N개 일괄 dispatch (--execute: 실 spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry — 사용자 정의 CLI provider 동적 추가 (OpenRouter/Bedrock 흡수)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi 모드 alias (또는 --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness which [--json] # 1.9.164 진단: 현재 실행 경로/버전 + npm 캐시 + PATH 후보 (구버전 충돌 해결)\n leerness selftest [--json] # 1.9.258 코어 함수 무결성 자가 검증 (설치 손상/부분설치 감지, CI 친화 exit 1)\n leerness shell-guard "<command>" [--json] # 1.9.260 터미널 명령 셸 호환성 린터 (PowerShell 5.1 && 미지원 등 실행 전 감지, UR-0020)\n leerness shell-guard --record --cmd "..." --exit N # 1.9.260 실패한 터미널 명령 기록 → 다음 분석 시 회수\n leerness path-setup [--apply] [--json] # 1.9.254 leerness CLI PATH 자동 등록 (npm global bin 미등록 시)\n leerness web check|screenshot|extract <url> [--out file.png] [--selector "css"] # 1.9.165 playwright bridge (opt-in: npm i -g playwright + permissions.browser)\n leerness pc check|click|type|screenshot [--x N --y N] [--text "s"] [--out f.png] # 1.9.166 robotjs/nut-tree bridge (opt-in: npm i -g robotjs + permissions.mouse/keyboard, ⚠ full 모드 권장)\n leerness lsp check|symbols|references <file/name> [--in dir] [--json] # 1.9.167 LSP 어댑터 MVP (typescript opt-in + regex fallback, 코드 인텔리전스)\n leerness review-request "<request>" [--json] # 1.9.176 사용자 요청 사전 검토 (충돌/재사용/효율/권장 단계 — 사용자 명시)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
19756
19905
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
19757
19906
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
19758
19907
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -19957,6 +20106,12 @@ async function main() {
19957
20106
  if (cmd === 'path-setup' || cmd === 'path') return pathSetupCmd(arg('--path', process.cwd()), { apply: has('--apply'), json: has('--json') });
19958
20107
  // 1.9.258: leerness selftest — 설치된 바이너리 코어 함수 무결성 자가 검증 (CI/사용자 진단)
19959
20108
  if (cmd === 'selftest' || cmd === 'self-test') return selfTestCmd({ json: has('--json') });
20109
+ // 1.9.260: leerness shell-guard "<cmd>" — 터미널 명령 셸 호환성 린터 + 실패 메모리 (사용자 명시 UR-0020)
20110
+ if (cmd === 'shell-guard' || cmd === 'shellguard') {
20111
+ const cmdArg = arg('--cmd', args[1] && !args[1].startsWith('-') ? args[1] : '');
20112
+ const exitRaw = arg('--exit', null);
20113
+ return shellGuardCmd(arg('--path', process.cwd()), cmdArg, { record: has('--record'), exit: exitRaw != null ? parseInt(exitRaw, 10) : null, json: has('--json') });
20114
+ }
19960
20115
  // 1.9.245: API skill cache — 공식 문서·관련링크 자동 정리 (사용자 명시 UR-0015)
19961
20116
  if (cmd === 'api-skill') return apiSkillCmd(arg('--path', process.cwd()), args[1] || 'help');
19962
20117
  // 1.9.208: leerness constraints <list|check|add> — 플랫폼/API 제약 사전 체크 (사용자 명시)
@@ -20118,5 +20273,7 @@ module.exports = {
20118
20273
  // 1.9.257: CJK 인코딩 분류 (UR-0014 계열) 순수 함수 — 단위 테스트
20119
20274
  _classifyCJK, _riskLabel,
20120
20275
  // 1.9.258: selftest — 코어 함수 무결성 검증
20121
- _selfTestCases, selfTestCmd
20276
+ _selfTestCases, selfTestCmd,
20277
+ // 1.9.260: shell-guard — 셸 호환성 린터 (UR-0020) 순수 분석 함수 — 단위 테스트
20278
+ _shellGuardAnalyze, _detectShellCtx, shellGuardCmd
20122
20279
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.258",
3
+ "version": "1.9.260",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
@@ -45,7 +45,7 @@
45
45
  "LICENSE"
46
46
  ],
47
47
  "scripts": {
48
- "test": "node ./bin/harness.js --version && node ./scripts/e2e.js",
48
+ "test": "node ./bin/harness.js --version && node ./bin/harness.js selftest && node ./scripts/e2e.js",
49
49
  "test:smoke": "node ./scripts/e2e.js",
50
50
  "prepack": "node ./bin/harness.js --version"
51
51
  },