leerness 1.9.152 → 1.9.153

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,50 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.153 — 2026-05-20
4
+
5
+ **`.env` 직접 생성/마이그레이션 + REPL 배너 leerness 고유 문구 + multi-provider REPL (사용자 명시 3종).**
6
+
7
+ 자율 모드 83 라운드.
8
+
9
+ ### Added — install 흐름에서 `.env` 직접 생성/마이그레이션 (사용자 명시)
10
+ - 기존 `.env.example` 만 작성 → **이제 `.env` 도 직접 생성** (보안 = 빈 키만)
11
+ - `mergeEnvFile()` — KEY 기준 처리:
12
+ - 기존 키 (사용자가 채운 값 포함) **절대 덮어쓰지 않음**
13
+ - 누락된 키만 빈 값으로 추가
14
+ - 주석/빈 줄은 substring 미포함 시만 append
15
+ - `.gitignore` 에 `.env` 자동 등록 (1.9.71/75 audit 검증과 통합)
16
+ - `.env.example` 은 계속 생성 (참조 템플릿)
17
+
18
+ ### Changed — REPL 배너 leerness 고유 문구 (사용자 명시)
19
+ - 기존: `Hermes / OpenClaw / OpenCode 스타일 + Sandbox`
20
+ - 변경: `검수·기억·샌드박스 통합 자율 AI 에이전트`
21
+ - agent 사용법 (non-TTY) 헤더도 동일 — leerness 자체 정체성 강화
22
+
23
+ ### Added — REPL multi-provider 세션 관리 (사용자 명시)
24
+ - 기존: Ollama 전용 채팅
25
+ - 변경: **ollama / claude / codex / gemini / copilot** 5종 세션 관리
26
+ - `_cliChat(root, provider, prompt, opts)` — 외부 CLI 호출 헬퍼
27
+ - 각 CLI 별 비-인터랙티브 호출 인자 자동 매핑
28
+ - `runCommandSafe` 경유 (env scrub + permissions + observability 자동)
29
+ - 활성 (`_checkAgent` ready) 확인 후 실행 — 비활성 시 friendly 에러
30
+ - REPL 진입 시:
31
+ - `.env` 자동 로드 (LEERNESS_ENABLE_* 즉시 반영)
32
+ - 활성 CLI 단일 → 자동 선택 / 복수 → 사용자 번호 선택 / 0개 → Ollama fallback
33
+ - `:provider <p>` 메타 명령으로 세션 중 전환 가능
34
+ - `:role <r>` 와 조합하여 planner=claude / actor=codex 같은 multi-CLI 워크플로 가능
35
+
36
+ ### Security
37
+ - `.env` 가 `.gitignore` 에 등록 (실제 시크릿 누출 방지)
38
+ - `_cliChat` 가 `runCommandSafe` 경유 → env scrub 화이트리스트만 자식 프로세스에 전파
39
+ - `.harness/runs/run-*.jsonl` 에 `kind: 'agent_repl_cli'` 로 모든 외부 CLI 호출 기록
40
+
41
+ ### Verified
42
+ - e2e 217/217 ✓
43
+ - stress-v98: 23/23 (env 생성 7종 + REPL 배너 3종 + multi-provider 6종 + 누적 회귀 7종)
44
+ - VERSION = 1.9.153 / autonomous-rounds = 83
45
+
46
+ ---
47
+
3
48
  ## 1.9.152 — 2026-05-20
4
49
 
5
50
  **`agents multi` — 활성 N개 에이전트 일괄 dispatch + handoff 헤드라인 활성 에이전트 카운트 (1.9.151 복수 선택 후속).**
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.152-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v97-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-47-blue)]() [![json](https://img.shields.io/badge/--json-20_commands-blueviolet)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-82-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-auto-success)]() [![agents-multi](https://img.shields.io/badge/agents_multi-N_parallel_dispatch-success)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-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.153-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v98-23%2F23-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-47-blue)]() [![json](https://img.shields.io/badge/--json-20_commands-blueviolet)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-83-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-auto-success)]() [![multi-provider-repl](https://img.shields.io/badge/REPL-5_providers_session-success)]() [![env](https://img.shields.io/badge/.env-auto_generate%2Bmigrate-success)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-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.152 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.153 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
17
  ╚══════════════════════════════════════════════════════════════╝
18
18
  ```
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.152';
9
+ const VERSION = '1.9.153';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -579,6 +579,31 @@ function mergeLinesFile(p, lines) {
579
579
  writeUtf8(p, next);
580
580
  }
581
581
 
582
+ // 1.9.153: env 파일 전용 key-aware merge — KEY=VALUE 줄을 키 기준 처리 (기존 값 보존, 빈 키만 추가)
583
+ // 사용자가 .env 의 LEERNESS_NPM_TOKEN=abc123 처럼 직접 편집한 값을 절대 덮어쓰지 않음.
584
+ // 주석 / 빈 줄은 substring includes 로 중복 방지 (mergeLinesFile 와 동일).
585
+ function mergeEnvFile(p, lines) {
586
+ const current = exists(p) ? read(p) : '';
587
+ const existingKeys = new Set();
588
+ for (const ln of current.split(/\r?\n/)) {
589
+ const m = ln.match(/^\s*([A-Z][A-Z0-9_]+)\s*=/);
590
+ if (m) existingKeys.add(m[1]);
591
+ }
592
+ let next = current;
593
+ for (const line of lines) {
594
+ const km = line.match(/^\s*([A-Z][A-Z0-9_]+)\s*=/);
595
+ if (km) {
596
+ if (existingKeys.has(km[1])) continue; // 기존 키 값 보존 (덮어쓰기 X)
597
+ next += (next.endsWith('\n') || !next ? '' : '\n') + line + '\n';
598
+ existingKeys.add(km[1]);
599
+ } else {
600
+ // 주석 또는 빈 줄 — substring 미포함 시만 append
601
+ if (!next.includes(line)) next += (next.endsWith('\n') || !next ? '' : '\n') + line + '\n';
602
+ }
603
+ }
604
+ writeUtf8(p, next);
605
+ }
606
+
582
607
  function writeMigrationReport(root, backup, actions, opts = {}) {
583
608
  const p = path.join(root, '.harness/migration-report.md');
584
609
  const rows = actions.map(a => `| ${a.file} | ${a.action} |`).join('\n');
@@ -820,7 +845,10 @@ async function install(root, opts = {}) {
820
845
  }
821
846
  if (!opts.dry) {
822
847
  mergeLinesFile(path.join(root, '.gitignore'), [
823
- '.harness/skill-publish.local.json','.harness/**/*.local.json','.env.local',
848
+ // 1.9.153: .env 직접 생성 + 사용자 글로벌 룰 SECRET_PATTERNS 6종 일괄 ignore (audit 통합)
849
+ // audit 가 검사하는 6 패턴: .env / .env.local / .env.production / .env.*.local / *.pem / credentials.json
850
+ '.env', '.env.local', '.env.production', '.env.*.local', '*.pem', 'credentials.json',
851
+ '.harness/skill-publish.local.json','.harness/**/*.local.json',
824
852
  '.harness/archive/','.harness/migration-report.md','.harness/cache/',
825
853
  // 1.9.147: 자동 유지보수 — 자격증명 + incident 페이로드 비공개 (보안)
826
854
  '.harness/credentials.local.json','.harness/incidents/',
@@ -836,8 +864,12 @@ async function install(root, opts = {}) {
836
864
  return new Set([a]); // back-compat: 단일 문자열
837
865
  })();
838
866
  const enable = (cli) => enabledSet.has(cli);
839
- mergeLinesFile(path.join(root, '.env.example'), [
840
- '# Leerness uses environment variable names only. Do not store real secrets here.',
867
+ // 1.9.153: .env.example 은 템플릿 (배포 가능, 실제 시크릿 값 없음)
868
+ // .env 사용 파일 사용자가 토큰 채워 넣음. 보안 정책: 토큰 값은 절대 자동 채우지 않음 (키만).
869
+ // .gitignore 에 .env 가 들어가 있어야 함 (audit 가 자동 검증). mergeLinesFile 은 기존 키 유지 + 신규 추가.
870
+ const envLines = [
871
+ '# Leerness — environment variable names only. Do not commit real secrets (this file is in .gitignore).',
872
+ `# Generated/migrated by leerness v${VERSION} at ${new Date().toISOString().slice(0, 10)}.`,
841
873
  'LEERNESS_NPM_TOKEN=','LEERNESS_GITHUB_TOKEN=',
842
874
  '# 1.9.22 — orchestrate opt-in. URL이 설정되면 leerness가 Ollama를 사용 가능. 미설정 시 LLM 호출 자동 시작 금지.',
843
875
  `LEERNESS_OLLAMA_BASE_URL=${enable('ollama') ? 'http://localhost:11434' : ''}`,
@@ -850,11 +882,22 @@ async function install(root, opts = {}) {
850
882
  `LEERNESS_ENABLE_COPILOT=${enable('copilot') ? 1 : 0}`,
851
883
  `LEERNESS_ENABLE_OLLAMA=${enable('ollama') ? 1 : 0}`,
852
884
  '# 1.9.42 — agentskills.io 공개 표준 스킬 자동 탐색 (opt-in). URL 설정 시 `leerness skill discover` 사용 가능.',
853
- '# 예: LEERNESS_SKILL_DISCOVER_URL=https://agentskills.io/llms.txt',
885
+ '# 예시 URL: https://agentskills.io/llms.txt',
854
886
  'LEERNESS_SKILL_DISCOVER_URL=',
855
887
  '# (선택) 사용자 요청 분석 시 자동 매칭 스킬 추천. 1=활성, 0/미설정=비활성.',
856
888
  'LEERNESS_SKILL_AUTO_DISCOVER=0'
857
- ]);
889
+ ];
890
+ mergeLinesFile(path.join(root, '.env.example'), envLines);
891
+ // 1.9.153: .env 직접 생성/마이그레이션 (사용자 명시 요청). 보안 = 빈 값만 — 사용자가 직접 토큰 채움.
892
+ // 기존 .env 가 있으면 mergeEnvFile 이 KEY 기준 처리:
893
+ // - 기존 키 (사용자가 채운 값 포함) 는 절대 덮어쓰지 않음
894
+ // - 누락된 키만 빈 값으로 추가
895
+ // .env 가 .gitignore 에 등록되어 있는지 audit 가 검증 (1.9.75+).
896
+ try {
897
+ mergeEnvFile(path.join(root, '.env'), envLines);
898
+ } catch (e) {
899
+ warn(`.env 생성/마이그레이션 실패 (계속 진행): ${e.message}`);
900
+ }
858
901
  // 1.9.146: agent 권한 파일 자동 생성 (사용자 명시 요청 #5)
859
902
  if (resolved.permissionMode) {
860
903
  try { _writePermissionsPreset(root, resolved.permissionMode); } catch (e) { warn('permissions 생성 실패: ' + e.message); }
@@ -10073,6 +10116,41 @@ async function _ollamaListModels() {
10073
10116
  });
10074
10117
  }
10075
10118
 
10119
+ // 1.9.153: 외부 CLI 채팅 호출 (multi-provider REPL — 사용자 명시 요청)
10120
+ // claude/codex/gemini/copilot 를 child_process 로 호출 후 stdout 캡처.
10121
+ // runCommandSafe 경유 — env scrub + permissions + observability 자동 적용.
10122
+ async function _cliChat(root, provider, prompt, opts) {
10123
+ opts = opts || {};
10124
+ const agent = EXTERNAL_AGENTS.find(a => a.id === provider);
10125
+ if (!agent) return { ok: false, error: `unknown provider: ${provider}`, provider };
10126
+ const status = _checkAgent(agent);
10127
+ if (status.status !== 'ready') {
10128
+ return { ok: false, error: `${provider} 비활성 (${status.status}) — .env 에서 ${agent.envFlag}=1 + CLI 설치 필요`, provider };
10129
+ }
10130
+ // CLI 별 비-인터랙티브 호출 인자 매핑 (read-only 모드 — REPL 안에서 파일 수정 X)
10131
+ let cmd, args;
10132
+ if (provider === 'claude') { cmd = 'claude'; args = ['--print', prompt]; }
10133
+ else if (provider === 'codex') { cmd = 'codex'; args = ['exec', '--skip-git-repo-check', prompt]; }
10134
+ else if (provider === 'gemini') { cmd = 'gemini'; args = ['-p', prompt]; }
10135
+ else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', prompt]; }
10136
+ else return { ok: false, error: `provider ${provider} 미지원`, provider };
10137
+ // runCommandSafe — env scrub + observability 자동
10138
+ const r = runCommandSafe(cmd, args, {
10139
+ cwd: process.cwd(), root,
10140
+ timeout: opts.timeout || 60000,
10141
+ allowOutsideCwd: true, // CLI 가 cwd 밖에서 실행될 수 있음
10142
+ kind: 'agent_repl_cli', label: `repl-${provider}`
10143
+ });
10144
+ if (r.status === 0) {
10145
+ return { ok: true, response: (r.stdout || '').trim(), provider, model: provider };
10146
+ }
10147
+ return {
10148
+ ok: false,
10149
+ error: `exit=${r.status} ${(r.stderr || r.stdout || '').slice(0, 200)}`,
10150
+ provider
10151
+ };
10152
+ }
10153
+
10076
10154
  // 1.9.149: observability lite — 모든 agent 호출의 traceId + duration + exit + failureCause 기록
10077
10155
  function _runsDir(root) { return path.join(absRoot(root), '.harness', 'runs'); }
10078
10156
  function _recordRun(root, entry) {
@@ -10228,8 +10306,10 @@ const _AGENT_ROLE_PROMPTS = {
10228
10306
  reviewer: '역할: reviewer. planner 의 계획 또는 actor 의 결과를 비판적으로 검토. 누락된 검증, 잠재 cascade, 오류 가능성 지적. 동의/수정 결론 명시.',
10229
10307
  actor: '역할: actor. 계획에 따라 정확한 명령/코드만 실행. evidence(파일 경로 + 테스트 결과) 함께 기록. 새 계획 생성 금지.'
10230
10308
  };
10231
- // 1.9.149: REPL 모드 — Hermes/OpenClaw/OpenCode 스타일 자율형 CLI 에이전트
10309
+ // 1.9.149+1.9.153: REPL 모드 — leerness 자율 AI 에이전트 (multi-provider 세션)
10232
10310
  async function _agentRepl(root, opts) {
10311
+ // 1.9.153: .env 자동 로드 (REPL 진입 직전) — install 직후 LEERNESS_ENABLE_* 즉시 반영
10312
+ try { _loadEnvFile(root); } catch {}
10233
10313
  const readline = require('readline');
10234
10314
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
10235
10315
  const isTty = process.stdout.isTTY;
@@ -10238,9 +10318,28 @@ async function _agentRepl(root, opts) {
10238
10318
  bold: s => `\x1b[1m${s}\x1b[0m`, green: s => `\x1b[32m${s}\x1b[0m`,
10239
10319
  yel: s => `\x1b[33m${s}\x1b[0m`, mag: s => `\x1b[35m${s}\x1b[0m`
10240
10320
  } : { cy:s=>s, dim:s=>s, bold:s=>s, green:s=>s, yel:s=>s, mag:s=>s };
10321
+ // 1.9.153: provider 자동 선택 — opts.provider 명시 안 됨 + 활성 CLI 가 있으면 사용자에게 선택지 표시
10322
+ let initialProvider = opts.provider;
10323
+ if (!initialProvider) {
10324
+ const ready = EXTERNAL_AGENTS.map(a => ({ def: a, status: _checkAgent(a) }))
10325
+ .filter(x => x.status.status === 'ready');
10326
+ if (ready.length === 1) {
10327
+ initialProvider = ready[0].def.id; // 단일 활성 → 자동 선택
10328
+ } else if (ready.length > 1 && isTty) {
10329
+ // 복수 활성 → 사용자에게 선택지 (Ollama 우선이 아닌, 활성된 CLI 중 선택)
10330
+ console.log('');
10331
+ console.log(` 사용 가능한 CLI 에이전트 ${ready.length}개:`);
10332
+ ready.forEach((x, i) => console.log(` ${i + 1}) ${x.def.id}${x.status.version ? ' (v' + x.status.version + ')' : ''}`));
10333
+ const choice = await new Promise(res => rl.question(`\n provider 선택 (Enter=1): `, res));
10334
+ const idx = parseInt(choice, 10) - 1;
10335
+ initialProvider = (idx >= 0 && idx < ready.length) ? ready[idx].def.id : ready[0].def.id;
10336
+ } else {
10337
+ initialProvider = 'ollama'; // 활성 0개 → fallback (사용 시 friendly 경고)
10338
+ }
10339
+ }
10241
10340
  // 세션 state
10242
10341
  let state = {
10243
- provider: opts.provider || 'ollama',
10342
+ provider: initialProvider,
10244
10343
  model: opts.model || process.env.LEERNESS_OLLAMA_MODEL || null,
10245
10344
  role: opts.role || 'actor',
10246
10345
  history: [], // [{role: 'user'|'assistant', content: ''}]
@@ -10258,8 +10357,8 @@ async function _agentRepl(root, opts) {
10258
10357
  // 환영 메시지 + 모델 선택
10259
10358
  log('');
10260
10359
  log(C.bold(C.cy(' ╔════════════════════════════════════════════════════╗')));
10261
- log(C.bold(C.cy(' ║ leerness agent — REPL mode (1.9.150) ║')));
10262
- log(C.bold(C.cy(' ║ Hermes / OpenClaw / OpenCode 스타일 + Sandbox ║')));
10360
+ log(C.bold(C.cy(' ║ leerness agent — REPL mode ║')));
10361
+ log(C.bold(C.cy(' ║ 검수·기억·샌드박스 통합 자율 AI 에이전트 ║')));
10263
10362
  log(C.bold(C.cy(' ╚════════════════════════════════════════════════════╝')));
10264
10363
  log('');
10265
10364
  // Ollama 모델 자동 감지 — model이 명시되지 않았으면 사용자에게 선택지 제공
@@ -10374,11 +10473,15 @@ async function _agentRepl(root, opts) {
10374
10473
  const finalPrompt = `${rolePrompt}\n\nConversation so far:\n${state.history.slice(-6).map(m => `[${m.role}] ${m.content}`).join('\n')}\n\nRespond as ${state.role}:`;
10375
10474
  const t0 = Date.now();
10376
10475
  let result;
10476
+ // 1.9.153: multi-provider REPL — ollama 외 claude/codex/gemini/copilot 도 세션 관리 (사용자 명시)
10377
10477
  if (state.provider === 'ollama') {
10378
- log(C.dim(` → ollama (${state.model}) 호출 중...`));
10478
+ log(C.dim(` → ollama${state.model ? ' (' + state.model + ')' : ''} 호출 중...`));
10379
10479
  result = await _ollamaChat(finalPrompt, state.model);
10480
+ } else if (['claude', 'codex', 'gemini', 'copilot'].includes(state.provider)) {
10481
+ log(C.dim(` → ${state.provider} CLI 호출 중...`));
10482
+ result = await _cliChat(root, state.provider, finalPrompt, { timeout: 90000 });
10380
10483
  } else {
10381
- log(C.yel(` ⚠ ${state.provider} REPL 미지원 — leerness agents dispatch 사용 권장`));
10484
+ log(C.yel(` ⚠ ${state.provider} provider 미지원 — :provider ollama|claude|codex|gemini|copilot`));
10382
10485
  rl.prompt(); return;
10383
10486
  }
10384
10487
  const dt = Date.now() - t0;
@@ -10415,17 +10518,19 @@ async function agentCmd(root, taskArg) {
10415
10518
  return;
10416
10519
  }
10417
10520
  // non-TTY: 사용법만 출력
10418
- log('# leerness agent (1.9.146/148/149/150) Hermes/OpenClaw 스타일 CLI 에이전트 + Sandbox');
10521
+ log('# leerness agent — 검수·기억·샌드박스 통합 자율 AI 에이전트');
10419
10522
  log('');
10420
10523
  log('사용법:');
10421
- log(' leerness agent # 1.9.149 REPL 모드 (모델 선택 + 채팅)');
10524
+ log(' leerness agent # REPL 모드 (provider 자동 선택 + 채팅)');
10422
10525
  log(' leerness agent "<task>" # 1회 위임 (actor 역할 기본)');
10423
10526
  log(' leerness agent "<task>" --role planner # 계획만 (1.9.148)');
10424
10527
  log(' leerness agent "<task>" --role reviewer # 비판적 검토 (1.9.148)');
10425
- log(' leerness agent --interactive --model qwen2.5-coder # 명시적 REPL + model 선택');
10528
+ log(' leerness agent --provider claude # provider 명시 (ollama/claude/codex/gemini/copilot)');
10529
+ log(' leerness agent --interactive --model qwen2.5-coder # 명시적 REPL + Ollama 모델 선택');
10426
10530
  log('');
10427
10531
  log('REPL 메타 명령: :help / :model / :role / :provider / :history / :save / :quit');
10428
10532
  log('REPL Slash 명령 (1.9.150): :verify / :audit / :handoff / :health (sandboxed runCommandSafe)');
10533
+ log('REPL Multi-provider (1.9.153): ollama / claude / codex / gemini / copilot — 활성 CLI 자동 감지');
10429
10534
  log('');
10430
10535
  log('현재 활성 provider: ' + (_activeCliAgents().join(', ') || '(없음) — .env에서 LEERNESS_ENABLE_* 활성화'));
10431
10536
  log('권한 모드: ' + (_readPermissions(root).mode || 'basic'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.152",
3
+ "version": "1.9.153",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",