leerness 1.9.259 → 1.9.261

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,64 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.261 — 2026-05-31 — UR-0020 2단계: MCP leerness_shell_guard (72 도구) + selftest 케이스
4
+
5
+ **🐚🔌 1.9.260 shell-guard 를 외부 AI(MCP) + 무결성 검증(selftest)에 통합.**
6
+
7
+ ### 구현
8
+ 1. **MCP 72번째 도구 `leerness_shell_guard`** (71 → 72):
9
+ - 외부 AI(Claude Code 등)가 명령 실행 **전에** `tools/call` 로 셸 호환성 점검
10
+ - 예: `npm run build && npm test` on PowerShell 5.1 → ps5-chain error + `;` 제안
11
+ - 응답: `{ shell, psVersion, issues[], pastSame, pastSimilar, ok }` · 인자 `{ command (required), path? }`
12
+ - 실 stdio JSON-RPC 검증: tools/list 72 + tools/call shell_guard 응답 OK
13
+ 2. **selftest 케이스 +2** (13 → 15):
14
+ - `_shellGuardAnalyze`: PS5.1 `&&` → ps5-chain error · bash `&&` → 문제 없음
15
+ - 코어 무결성 검증에 셸 린터 포함 (CLI + MCP + npm test 게이트 모두 자동 검증)
16
+
17
+ ### stress-v206 — **18/18 PASS · 100%**
18
+ - 1.9.261 (7): VERSION + MCP 72 정의/핸들러 + 실 서버 list/call + selftest 15 케이스
19
+ - 성능 (1): cold start avg 440ms
20
+ - 누적 회귀 (10): 1.9.207~260 + MCP initialize 프로토콜
21
+
22
+ ### 자동 release (123 main-push streak · 84 npm publish streak · R217)
23
+
24
+ 🐚🔌 **shell-guard 3중 노출** — CLI(1.9.260) + MCP 도구(1.9.261) + selftest 케이스. 외부 AI 가 명령 실행 전 셸 실패를 사전 차단.
25
+
26
+ ---
27
+
28
+ ## 1.9.260 — 2026-05-31 — 🎉 1.9.260 / UR-0020 shell-guard (터미널 셸 호환성 린터 + 실패 메모리)
29
+
30
+ **🐚 사용자 명시 (UR-0020): 터미널 명령 실패를 파악·기록하고 다음 실행 시 셸 호환성(PowerShell 5.1 && 미지원 등)을 참조.**
31
+
32
+ ### 개발 방향 분석 (사용자 요청 "알맞는 방향 고민")
33
+ leerness 는 0-dependency Node CLI 라 셸을 직접 가로챌 수 없음 → 가장 안전·유용한 방향은 **(1) 실행 전 정적 린터 + (2) 실패 메모리 + (3) 환경 버전 변동 감지**. 기존 인프라(`_collectRuntimeEnv` 1.9.241 PS 버전 감지 + `environment.json` 1.9.145 스냅샷) 재사용.
34
+
35
+ ### 구현 (`leerness shell-guard "<command>"`)
36
+ 1. **정적 셸 호환성 린터** `_shellGuardAnalyze(cmd, ctx)` (순수 함수, 6 규칙):
37
+ - **ps5-chain** (error): Windows PowerShell 5.1 `&&`/`||` 미지원 → `A; if ($?) { B }` 제안 (핵심 시나리오)
38
+ - **ps-devnull** (error): `2>/dev/null` → `2>$null`
39
+ - **ps-inline-env** (error): `VAR=x cmd` → `$env:VAR='x'; cmd`
40
+ - **ps-rm-rf** (warn): `rm -rf` → `Remove-Item -Recurse -Force`
41
+ - **cmd-semicolon** (warn): CMD `;` 구분자 아님 → `&` / `&&`
42
+ - **ps-version-unknown** (info): PS 버전 미상 + `&&` → 안전 패턴 권장
43
+ 2. **실패 메모리** `.harness/shell-failures.json` (200건 cap):
44
+ - `shell-guard --record --cmd "..." --exit N` 기록 → 다음 분석 시 동일/유사(첫 토큰) 실패 회수
45
+ 3. **환경 버전 변동 감지**: `environment.json` 스냅샷 vs 현재 node/PowerShell 버전 비교 → 변동 시 경고
46
+ 4. PS 버전·셸 자동 판별 (`_detectShellCtx`) + `--json` (error 시 exit 1) + 순수 함수 export (단위 테스트)
47
+
48
+ ### 다음 라운드 참조 (UR-0020 후속 후보)
49
+ - handoff/agent-mode 에 shell-failures 요약 자동 노출 · MCP leerness_shell_guard 도구 · selftest 케이스 추가
50
+
51
+ ### stress-v205 — **27/27 PASS · 100%**
52
+ - 1.9.260 (16): 6 규칙 분석 + record/advise 회수 + 200 cap + CLI/json/help
53
+ - 성능 (1): cold start avg 441ms
54
+ - 누적 회귀 (10): 1.9.207~259
55
+
56
+ ### 자동 release (122 main-push streak · 83 npm publish streak · R216)
57
+
58
+ 🐚 **셸 실패 예방** — Windows PowerShell 5.1 `&&` 등 셸 차이로 인한 터미널 실패를 실행 전 감지 + 과거 실패 회수.
59
+
60
+ ---
61
+
3
62
  ## 1.9.259 — 2026-05-30 — MCP leerness_selftest (71 도구) + npm test 무결성 게이트
4
63
 
5
64
  **🔌 1.9.258 selftest 를 워크플로에 통합: 외부 AI(MCP) + 배포 파이프라인(npm test) 양쪽에서 무결성 검증.**
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.259-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v204-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-71-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-215-blueviolet)]() [![main-push](https://img.shields.io/badge/🎉_main--push_streak-121_rounds-success)]() [![npm-streak](https://img.shields.io/badge/🎉_npm--publish-82_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.261-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v206-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-72-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-217-blueviolet)]() [![main-push](https://img.shields.io/badge/🎉_main--push_streak-123_rounds-success)]() [![npm-streak](https://img.shields.io/badge/🎉_npm--publish-84_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.259 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.261 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- ║ 🔌 MCP 71 도구 + selftest 게이트 (CLI+MCP+npm test · R215)
17
+ ║ 🔌 MCP 72 shell-guard 외부 AI 노출 (UR-0020 · R217)
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.259';
10
+ const VERSION = '1.9.261';
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 처리).
@@ -2903,6 +2903,8 @@ function _selfTestCases() {
2903
2903
  { name: '_dirInPath: 정규화 (trailing slash)', run: () => { const sep = process.platform === 'win32' ? ';' : ':'; const old = process.env.PATH; process.env.PATH = '/usr/bin' + sep + '/opt/x/bin'; const r = _dirInPath('/opt/x/bin/') === true && _dirInPath('/no/such') === false; process.env.PATH = old; return r; } },
2904
2904
  { name: '_winPathPsScript: User scope + setx 미사용', run: () => { const ps = _winPathPsScript('C:\\npm'); return /SetEnvironmentVariable\('PATH', \$nu, 'User'\)/.test(ps) && !/setx/i.test(ps); } },
2905
2905
  { name: '_unixPathBlock: 멱등 마커 + export', run: () => { const b = _unixPathBlock('/x/bin'); return b.includes('managed') && /export PATH="\$PATH:\/x\/bin"/.test(b); } },
2906
+ { name: '_shellGuardAnalyze: PS5.1 && → ps5-chain error', run: () => { const r = _shellGuardAnalyze('a && b', { shell: 'powershell', psVersion: '5' }); return r.issues.some(i => i.rule === 'ps5-chain' && i.severity === 'error'); } },
2907
+ { name: '_shellGuardAnalyze: bash && → 문제 없음', run: () => _shellGuardAnalyze('a && b', { shell: 'bash' }).issues.length === 0 },
2906
2908
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
2907
2909
  ];
2908
2910
  }
@@ -2941,6 +2943,150 @@ function selfTestCmd(opts = {}) {
2941
2943
  }
2942
2944
  }
2943
2945
 
2946
+ // 1.9.260: shell-guard — 터미널 명령 셸 호환성 린터 + 실패 메모리 (사용자 명시 UR-0020)
2947
+ // 배경: leerness 적용 프로젝트에서 터미널 명령이 셸 차이(PowerShell 5.1 && 미지원 등)로 실패.
2948
+ // leerness 는 셸을 직접 가로챌 수 없으므로(0-dep Node CLI) → 실행 전 정적 분석 + 실패 기록/회수로 대응.
2949
+ // ctx: { shell: 'powershell'|'cmd'|'bash'|'sh', psVersion: '5'|'7'|null }
2950
+ // 반환: { shell, psVersion, issues: [{ rule, severity, detail, suggestion }] } — 순수 함수 (단위 테스트 가능)
2951
+ function _shellGuardAnalyze(cmd, ctx) {
2952
+ const c = String(cmd || '');
2953
+ const shell = (ctx && ctx.shell) || 'unknown';
2954
+ const psVer = ctx && ctx.psVersion != null ? parseInt(ctx.psVersion, 10) : null;
2955
+ const issues = [];
2956
+ const isWinPowerShell = shell === 'powershell' && psVer != null && psVer < 6; // 5.1 = Windows PowerShell
2957
+ // 규칙 1: PowerShell 5.1 에서 && / || 체이닝 미지원 (pwsh 7+ 부터 지원)
2958
+ if (isWinPowerShell && /\s&&\s|\s\|\|\s/.test(c)) {
2959
+ issues.push({ rule: 'ps5-chain', severity: 'error', detail: 'Windows PowerShell 5.1 은 && / || 연산자를 지원하지 않습니다 (PowerShell 7+ 부터 지원).', suggestion: 'A; if ($?) { B } (조건부) 또는 A; B (무조건) 로 분리. 또는 pwsh 7 설치.' });
2960
+ }
2961
+ // 규칙 2: PowerShell 에서 2>/dev/null → 2>$null
2962
+ if (shell === 'powershell' && /2>\s*\/dev\/null/.test(c)) {
2963
+ issues.push({ rule: 'ps-devnull', severity: 'error', detail: 'PowerShell 은 /dev/null 경로가 없습니다.', suggestion: '2>$null 사용 (PowerShell 리다이렉트).' });
2964
+ }
2965
+ // 규칙 3: PowerShell 에서 inline env (VAR=val cmd) 미지원
2966
+ if (shell === 'powershell' && /^[A-Z_][A-Z0-9_]*=[^\s]+\s+\S/.test(c.trim())) {
2967
+ issues.push({ rule: 'ps-inline-env', severity: 'error', detail: 'PowerShell 은 VAR=val cmd 형식의 inline 환경변수를 지원하지 않습니다.', suggestion: "$env:VAR='val'; cmd 로 분리." });
2968
+ }
2969
+ // 규칙 4: PowerShell 에서 Unix 전용 명령 (rm -rf / ls -la 등) — 별칭은 되나 플래그 오류 가능
2970
+ if (shell === 'powershell' && /\brm\s+-rf\b/.test(c)) {
2971
+ issues.push({ rule: 'ps-rm-rf', severity: 'warn', detail: 'PowerShell 에서 rm -rf 는 -rf 플래그 파싱 오류 가능 (rm 은 Remove-Item 별칭).', suggestion: 'Remove-Item -Recurse -Force <path> 사용.' });
2972
+ }
2973
+ // 규칙 5: CMD 에서 ; 는 명령 구분자가 아님 (한 줄로 실행됨)
2974
+ if (shell === 'cmd' && /;/.test(c) && !/&&|\|\|/.test(c)) {
2975
+ issues.push({ rule: 'cmd-semicolon', severity: 'warn', detail: 'CMD 는 ; 를 명령 구분자로 처리하지 않습니다 (인자로 전달됨).', suggestion: 'A && B (조건부) 또는 A & B (무조건) 사용.' });
2976
+ }
2977
+ // 규칙 6: PowerShell 에서 && 가 있으나 버전 미상 — 정보성
2978
+ if (shell === 'powershell' && psVer == null && /\s&&\s/.test(c)) {
2979
+ issues.push({ rule: 'ps-version-unknown', severity: 'info', detail: 'PowerShell 버전 미상 — 5.1 이면 && 미지원, 7+ 이면 지원.', suggestion: '$PSVersionTable.PSVersion 확인. 안전하게 A; if ($?) { B } 권장.' });
2980
+ }
2981
+ return { shell, psVersion: psVer, issues };
2982
+ }
2983
+ function _shellFailuresPath(root) { return path.join(absRoot(root), '.harness', 'shell-failures.json'); }
2984
+ function _loadShellFailures(root) {
2985
+ 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: [] }; }
2986
+ }
2987
+ function _recordShellFailure(root, entry) {
2988
+ const data = _loadShellFailures(root);
2989
+ data.failures.push(entry);
2990
+ if (data.failures.length > 200) data.failures = data.failures.slice(-200); // cap
2991
+ data.updatedAt = new Date().toISOString();
2992
+ try { writeUtf8(_shellFailuresPath(root), JSON.stringify(data, null, 2) + '\n'); } catch {}
2993
+ return data;
2994
+ }
2995
+ function _detectShellCtx() {
2996
+ // 현재 환경에서 셸 종류 + PowerShell 버전 추정 (_collectRuntimeEnv 재사용)
2997
+ let env; try { env = _collectRuntimeEnv(); } catch { env = null; }
2998
+ let shell = 'unknown';
2999
+ const sh = (env && env.terminal && env.terminal.shell) || process.env.SHELL || process.env.ComSpec || '';
3000
+ if (/powershell|pwsh/i.test(sh)) shell = 'powershell';
3001
+ else if (/cmd\.exe/i.test(sh)) shell = 'cmd';
3002
+ else if (/bash/i.test(sh)) shell = 'bash';
3003
+ else if (/\bsh\b|zsh/i.test(sh)) shell = 'sh';
3004
+ else if (process.platform === 'win32') shell = (env && env.terminal && env.terminal.powershell) ? 'powershell' : 'cmd';
3005
+ const psVersion = env && env.terminal && env.terminal.powershell ? env.terminal.powershell : null;
3006
+ return { shell, psVersion, raw: sh };
3007
+ }
3008
+ // 환경 버전 변동 감지 (environment.json 스냅샷 vs 현재 — 사용자 명시: 과거↔현재 버전 업데이트)
3009
+ function _shellEnvDrift(root) {
3010
+ try {
3011
+ const snapPath = path.join(absRoot(root), '.harness', 'environment.json');
3012
+ if (!exists(snapPath)) return null;
3013
+ const snap = JSON.parse(read(snapPath));
3014
+ const cur = _collectRuntimeEnv();
3015
+ const changes = [];
3016
+ const snapNode = snap.node && snap.node.version;
3017
+ if (snapNode && cur.node.version && snapNode !== cur.node.version) changes.push({ what: 'node', from: snapNode, to: cur.node.version });
3018
+ const snapPs = snap.terminal && snap.terminal.powershell;
3019
+ if (snapPs && cur.terminal.powershell && String(snapPs) !== String(cur.terminal.powershell)) changes.push({ what: 'powershell', from: snapPs, to: cur.terminal.powershell });
3020
+ return { changes, capturedAt: snap.capturedAt || null };
3021
+ } catch { return null; }
3022
+ }
3023
+ function shellGuardCmd(root, cmd, opts = {}) {
3024
+ const isTty = process.stdout && process.stdout.isTTY;
3025
+ const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
3026
+ const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
3027
+ const yl = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
3028
+ const rd = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
3029
+ const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3030
+ const ctx = _detectShellCtx();
3031
+ // record 모드: 실패한 명령 기록
3032
+ if (opts.record) {
3033
+ if (!cmd) { fail('shell-guard --record 에는 --cmd "<명령>" 필요'); return; }
3034
+ const analysis = _shellGuardAnalyze(cmd, ctx);
3035
+ 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() };
3036
+ _recordShellFailure(root, entry);
3037
+ if (opts.json) { log(JSON.stringify({ recorded: true, entry }, null, 2)); return; }
3038
+ log(gr(`✓ shell 실패 기록됨: ${cmd.slice(0, 60)}${cmd.length > 60 ? '…' : ''} (shell=${ctx.shell}, exit=${entry.exitCode})`));
3039
+ if (analysis.issues.length) log(dm(` 감지된 패턴: ${analysis.issues.map(i => i.rule).join(', ')}`));
3040
+ return;
3041
+ }
3042
+ // 기본/advise 모드: 명령 분석 + 과거 실패 회수 + 버전 변동
3043
+ if (!cmd) {
3044
+ // 명령 없으면 환경 + 과거 실패 요약
3045
+ const data = _loadShellFailures(root);
3046
+ const drift = _shellEnvDrift(root);
3047
+ 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; }
3048
+ log(cy(`# leerness shell-guard (1.9.260, UR-0020) — 셸 호환성 가드`));
3049
+ log('');
3050
+ log(` 현재 셸: ${gr(ctx.shell)}${ctx.psVersion ? ` (PowerShell ${ctx.psVersion}${parseInt(ctx.psVersion, 10) < 6 ? ' = Windows PowerShell, && 미지원' : ''})` : ''}`);
3051
+ log(` 기록된 실패: ${data.failures.length}건`);
3052
+ if (drift && drift.changes.length) {
3053
+ log(yl(` ⚠ 환경 버전 변동 감지 (과거 스냅샷 대비):`));
3054
+ drift.changes.forEach(ch => log(` ${ch.what}: ${ch.from} → ${ch.to}`));
3055
+ log(dm(` → 과거 실패 기록 재검토 권장 (버전 변경으로 동작 달라질 수 있음)`));
3056
+ }
3057
+ if (data.failures.length) {
3058
+ log(dm(` 최근 실패 (최대 3):`));
3059
+ data.failures.slice(-3).reverse().forEach(f => log(dm(` • ${(f.cmd || '').slice(0, 50)} (exit=${f.exitCode}, ${f.shell})`)));
3060
+ }
3061
+ log('');
3062
+ log(dm(` 명령 분석: leerness shell-guard "<command>" · 실패 기록: shell-guard --record --cmd "..." --exit N`));
3063
+ return;
3064
+ }
3065
+ const analysis = _shellGuardAnalyze(cmd, ctx);
3066
+ // 과거 동일/유사 실패 회수 (정확 일치 + 첫 토큰 일치)
3067
+ const data = _loadShellFailures(root);
3068
+ const firstTok = cmd.trim().split(/\s+/)[0];
3069
+ const pastSame = data.failures.filter(f => f.cmd === cmd).length;
3070
+ const pastSimilar = data.failures.filter(f => f.cmd && f.cmd.trim().split(/\s+/)[0] === firstTok).length;
3071
+ 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; }
3072
+ log(cy(`# leerness shell-guard (1.9.260, UR-0020)`));
3073
+ log(` 명령: ${dm(cmd.slice(0, 80))}${cmd.length > 80 ? '…' : ''}`);
3074
+ log(` 셸: ${ctx.shell}${ctx.psVersion ? ` (PS ${ctx.psVersion})` : ''}`);
3075
+ log('');
3076
+ if (analysis.issues.length === 0) {
3077
+ log(gr(` ✓ 셸 호환성 문제 없음`));
3078
+ } else {
3079
+ analysis.issues.forEach(i => {
3080
+ const mark = i.severity === 'error' ? rd('✗') : (i.severity === 'warn' ? yl('⚠') : dm('ℹ'));
3081
+ log(` ${mark} [${i.rule}] ${i.detail}`);
3082
+ log(dm(` → ${i.suggestion}`));
3083
+ });
3084
+ }
3085
+ if (pastSame > 0) log(yl(` ⚠ 이 명령은 과거 ${pastSame}회 실패 기록 있음`));
3086
+ else if (pastSimilar > 0) log(dm(` ℹ "${firstTok}" 시작 명령 과거 ${pastSimilar}회 실패 기록`));
3087
+ if (analysis.issues.some(i => i.severity === 'error')) process.exitCode = 1;
3088
+ }
3089
+
2944
3090
  // 1.9.245: API skill cache — 공식 문서 + 관련 링크 자동 정리 (사용자 명시 UR-0015)
2945
3091
  // .harness/api-skills/<id>.md 에 frontmatter + 본문 저장. handoff 자동 매칭.
2946
3092
  // Node built-in https (의존성 0), depth=1 same-domain crawl, max 10 links.
@@ -15894,7 +16040,8 @@ function mcpServeCmd(root) {
15894
16040
  { 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
16041
  { 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
16042
  { 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'] } },
15897
- { 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: {} } }
16043
+ { 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: {} } },
16044
+ { name: 'leerness_shell_guard', description: '1.9.260/261 (사용자 명시 UR-0020) — 터미널 명령 셸 호환성 린터. 외부 AI 가 명령을 실행하기 전에 셸 호환성 문제를 사전 점검. 예: Windows PowerShell 5.1 은 && / || 미지원 → A; if ($?) { B } 제안. 6 규칙(ps5-chain/ps-devnull/ps-inline-env/ps-rm-rf/cmd-semicolon/ps-version-unknown) + 과거 실패 회수(.harness/shell-failures.json). 응답: { shell, psVersion, issues[{rule,severity,detail,suggestion}], pastSame, pastSimilar, ok }. 인자: { command (required), path? }. 현재 셸/PS 버전 자동 감지.', inputSchema: { type: 'object', properties: { command: { type: 'string' }, path: { type: 'string' } }, required: ['command'] } }
15898
16045
  ];
15899
16046
 
15900
16047
  function send(obj) {
@@ -16101,6 +16248,10 @@ function mcpServeCmd(root) {
16101
16248
  // 1.9.259 (1.9.258): 코어 함수 무결성 자가 검증
16102
16249
  cliArgs = ['selftest', '--json'];
16103
16250
  break;
16251
+ case 'leerness_shell_guard':
16252
+ // 1.9.261 (1.9.260, UR-0020): 터미널 명령 셸 호환성 린터
16253
+ cliArgs = ['shell-guard', String(args.command || ''), '--path', targetPath, '--json'];
16254
+ break;
16104
16255
  default:
16105
16256
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
16106
16257
  }
@@ -19757,7 +19908,7 @@ function whichCmd() {
19757
19908
  }
19758
19909
 
19759
19910
  function help() {
19760
- 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
19911
+ 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
19761
19912
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
19762
19913
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
19763
19914
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -19962,6 +20113,12 @@ async function main() {
19962
20113
  if (cmd === 'path-setup' || cmd === 'path') return pathSetupCmd(arg('--path', process.cwd()), { apply: has('--apply'), json: has('--json') });
19963
20114
  // 1.9.258: leerness selftest — 설치된 바이너리 코어 함수 무결성 자가 검증 (CI/사용자 진단)
19964
20115
  if (cmd === 'selftest' || cmd === 'self-test') return selfTestCmd({ json: has('--json') });
20116
+ // 1.9.260: leerness shell-guard "<cmd>" — 터미널 명령 셸 호환성 린터 + 실패 메모리 (사용자 명시 UR-0020)
20117
+ if (cmd === 'shell-guard' || cmd === 'shellguard') {
20118
+ const cmdArg = arg('--cmd', args[1] && !args[1].startsWith('-') ? args[1] : '');
20119
+ const exitRaw = arg('--exit', null);
20120
+ return shellGuardCmd(arg('--path', process.cwd()), cmdArg, { record: has('--record'), exit: exitRaw != null ? parseInt(exitRaw, 10) : null, json: has('--json') });
20121
+ }
19965
20122
  // 1.9.245: API skill cache — 공식 문서·관련링크 자동 정리 (사용자 명시 UR-0015)
19966
20123
  if (cmd === 'api-skill') return apiSkillCmd(arg('--path', process.cwd()), args[1] || 'help');
19967
20124
  // 1.9.208: leerness constraints <list|check|add> — 플랫폼/API 제약 사전 체크 (사용자 명시)
@@ -20123,5 +20280,7 @@ module.exports = {
20123
20280
  // 1.9.257: CJK 인코딩 분류 (UR-0014 계열) 순수 함수 — 단위 테스트
20124
20281
  _classifyCJK, _riskLabel,
20125
20282
  // 1.9.258: selftest — 코어 함수 무결성 검증
20126
- _selfTestCases, selfTestCmd
20283
+ _selfTestCases, selfTestCmd,
20284
+ // 1.9.260: shell-guard — 셸 호환성 린터 (UR-0020) 순수 분석 함수 — 단위 테스트
20285
+ _shellGuardAnalyze, _detectShellCtx, shellGuardCmd
20127
20286
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.259",
3
+ "version": "1.9.261",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",