leerness 1.9.188 → 1.9.189

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,104 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.189 — 2026-05-21
4
+
5
+ **⌨️ "/" slash 명령 자동 list + Tab cycle 한 줄 갱신 (사용자 명시 3종).**
6
+
7
+ 자율 모드 119 라운드. 사용자 명시:
8
+ 1. *"권한 기본값 설정을 / 으로 설정해둘 수 있고 (모든 모델 공통)"* — `/` slash default trigger
9
+ 2. *"/ 를 입력하면 관련 명령어가 첨부이미지처럼 나열"* — claude code 영감 UX
10
+ 3. *"탭키로 모델 변경시, 채팅 이력으로 남아서 지져분"* — 한 줄 갱신 (in-place)
11
+
12
+ ### Fix #1 — `/` slash 명령 자동 list
13
+
14
+ **Before** (claude code 화면 처럼 자동 안 됨):
15
+ ```
16
+ agent[claude/actor/▶]> /
17
+ (처리 안 됨 또는 에러)
18
+ ```
19
+
20
+ **After** (claude code 스타일):
21
+ ```
22
+ agent[claude · opus-4-7/actor/▶]> /
23
+
24
+ Available commands (also accepts ":" prefix)
25
+ ────────────────────────────────────────────────...
26
+ meta
27
+ /help 명령 목록
28
+ /quit REPL 종료 (세션 자동 저장)
29
+ /clear 대화 히스토리 초기화
30
+ /status 현재 provider / model / role / perms 상태
31
+ /provider <id> provider 전환
32
+ /model <id> 현재 provider의 모델 전환
33
+ /stream on|off 실시간 스트리밍 토글
34
+ review
35
+ /review "<req>" 무조건 구현 전 사전 검토
36
+ /permissions <m> 권한 변경 basic|extended|full
37
+ internal
38
+ /verify /audit /handoff /health
39
+ memory
40
+ /lessons /brainstorm <q> /tasks /plan
41
+ bridge
42
+ /web <op> /pc <op> /lsp <op>
43
+ ────────────────────────────────────────────────...
44
+ 💡 "/" 또는 ":" 접두사로 명령 호출
45
+ ```
46
+
47
+ `/` 와 `:` **양쪽 동등** (alias 처리). 모든 모델 공통.
48
+
49
+ ### Fix #2 — Tab cycle 한 줄 갱신 (in-place overwrite)
50
+
51
+ **Before** (1.9.188까지) — 매 Tab마다 새 줄 누적 → 채팅 이력 지저분:
52
+ ```
53
+ ⇄ provider [3/5]: codex ✓ ready
54
+ └ 5개 모델 catalog · Shift+Tab으로 model cycle
55
+ ⇄ provider [4/5]: gemini ✓ ready
56
+ └ 3개 모델 catalog · Shift+Tab으로 model cycle
57
+ ⇄ provider [5/5]: copilot ✓ ready
58
+ └ 1개 모델 catalog · Shift+Tab으로 model cycle
59
+ ⇄ provider [1/5]: ollama
60
+ └ 4개 모델 catalog · Shift+Tab으로 model cycle
61
+ ...
62
+ (채팅 영역이 cycle 흔적으로 가득 참)
63
+ ```
64
+
65
+ **After** (1.9.189) — 마지막 1건만 보임:
66
+ ```
67
+ ⇄ provider [2/5]: claude ✓ ready
68
+ └ 5개 모델 catalog · Shift+Tab으로 model cycle
69
+ agent[claude · opus-4-7/actor/▶]> _
70
+ ```
71
+
72
+ ```js
73
+ // 1.9.189: ANSI cursor up + line clear 로 이전 cycle 라인 덮어씀
74
+ let _lastCycleLines = 0;
75
+ const _clearLastCycle = () => {
76
+ if (!isTty || _lastCycleLines === 0) return;
77
+ process.stdout.write('\r\x1b[K'); // 현재 prompt 줄 클리어
78
+ for (let i = 0; i < _lastCycleLines; i++) {
79
+ process.stdout.write('\x1b[1A\x1b[K'); // 한 줄 위로 + 클리어
80
+ }
81
+ _lastCycleLines = 0;
82
+ };
83
+ // cycleProvider/cycleModel 시작 시 호출 → 이전 cycle 출력 제거 → 새 cycle 출력
84
+ // 사용자 line 입력 시 _lastCycleLines = 0 reset (cycle 흔적 더 이상 클리어 X)
85
+ ```
86
+
87
+ ### Fix #3 — Welcome 안내 갱신
88
+ ```
89
+ ───────────────────────────── 채팅 시작 ─────────────────────────────
90
+ 메시지 입력 후 Enter · "/" 입력 시 명령 list · :help 으로도 가능 · Ctrl+C 로 종료
91
+ ```
92
+
93
+ ### Verified
94
+ - stress-v134: **15/15 PASS** (사용자 명시 7 + live 2 + 누적 6)
95
+ - e2e 217/217 baseline 유지
96
+ - `/` 접두사 7개 명령 모두 정상 동작 (alias 처리)
97
+ - Tab cycle in-place overwrite — 채팅 영역 깔끔 유지
98
+ - VERSION = 1.9.189 · autonomous-rounds = 119 · main 자동 push 50 라운드 연속
99
+
100
+ ---
101
+
3
102
  ## 1.9.188 — 2026-05-21
4
103
 
5
104
  **🐛 REPL 한글 prompt 전달 BUG fix (stdin) + 세부 모델 표시 + 입력 구분선 (사용자 명시 3종).**
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.188-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v133-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-118-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-49_rounds-success)]() [![hangul-fix](https://img.shields.io/badge/한글_prompt-stdin_정확전달-success)]() [![model-show](https://img.shields.io/badge/세부모델-prompt%2B상태바_표시-success)]() [![divider](https://img.shields.io/badge/입력_구분선-자동출력-success)]() [![install](https://img.shields.io/badge/npm_i_leerness-즉시_사용_가능-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.189-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v134-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-119-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-50_rounds-success)]() [![slash-list](https://img.shields.io/badge/"/"_slash-자동_명령_list-success)]() [![cycle-clean](https://img.shields.io/badge/Tab_cycle-한_줄_갱신-success)]() [![hangul](https://img.shields.io/badge/한글_prompt-stdin_정확전달-success)]() [![install](https://img.shields.io/badge/npm_i_leerness-즉시_사용_가능-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,9 +12,9 @@
12
12
  ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
13
13
  ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
14
14
  ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
15
- ║ v1.9.188 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.189 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- 🐛 한글 prompt stdin 전달 · 세부모델 표시 · 입력 구분선
17
+ "/" slash 자동 list · Tab cycle 한 줄 갱신 · 50 라운드
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.188';
10
+ const VERSION = '1.9.189';
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 처리).
@@ -11609,7 +11609,7 @@ async function _agentRepl(root, opts) {
11609
11609
  // 환영 화면 끝에 명확한 구분선 + 안내 → 채팅 시작 명확.
11610
11610
  log('');
11611
11611
  log(C.dim(' ───────────────────────────── 채팅 시작 ─────────────────────────────'));
11612
- log(C.dim(' 메시지 입력 후 Enter · :help 으로 명령 목록 · Ctrl+C 로 종료'));
11612
+ log(C.dim(' 메시지 입력 후 Enter · ') + C.cy('"/"') + C.dim(' 입력 시 명령 list · ') + C.cy(':help') + C.dim(' 으로도 가능 · Ctrl+C 로 종료'));
11613
11613
  // 1.9.155: REPL 진입 시 handoff 컨텍스트 자동 노출 (UX 개선 — 사용자가 매번 :handoff 안 해도 컨텍스트 인지)
11614
11614
  try {
11615
11615
  const hf = cp.spawnSync(process.execPath, [__filename, 'handoff', root, '--compact', '--no-drift-check', '--no-headline'], {
@@ -11664,17 +11664,31 @@ async function _agentRepl(root, opts) {
11664
11664
  } catch {}
11665
11665
  return _PROVIDER_CYCLE_ORDER.slice();
11666
11666
  };
11667
- // 1.9.180: cycleProvider/cycleModel — 시각 피드백 강화 (사용자 명시 "원활하지 않다").
11668
- // 이전: 한 줄 ⇄ 메시지만. 1.9.180: provider 위치 표시 [1/5] + bold highlight + 활성 여부 표시.
11667
+ // 1.9.180+1.9.189: cycleProvider/cycleModel — 갱신 (in-place overwrite).
11668
+ // 1.9.180까지: Tab 누름마다 출력 채팅 이력으로 누적 (사용자 명시: "지져분해보여").
11669
+ // 1.9.189: ANSI cursor up + line clear 로 이전 cycle 라인 덮어씀 → 마지막 1건만 표시.
11670
+ let _lastCycleLines = 0; // 직전 cycle 출력 라인 수 (overwrite 용)
11671
+ const _clearLastCycle = () => {
11672
+ if (!isTty || _lastCycleLines === 0) return;
11673
+ // 현재 prompt 라인 + 이전 cycle 라인들 클리어
11674
+ // 현재 cursor는 prompt 줄. 위로 _lastCycleLines+1 만큼 올라가서 클리어.
11675
+ process.stdout.write('\r\x1b[K'); // 현재 prompt 줄 클리어
11676
+ for (let i = 0; i < _lastCycleLines; i++) {
11677
+ process.stdout.write('\x1b[1A\x1b[K'); // 한 줄 위로 + 클리어
11678
+ }
11679
+ _lastCycleLines = 0;
11680
+ };
11669
11681
  const cycleProvider = (reverse) => {
11682
+ _clearLastCycle();
11670
11683
  const list = getProviders();
11671
11684
  let idx = list.indexOf(state.provider);
11672
11685
  if (idx < 0) idx = 0;
11673
11686
  idx = reverse ? (idx - 1 + list.length) % list.length : (idx + 1) % list.length;
11674
11687
  state.provider = list[idx];
11675
11688
  state.model = null;
11689
+ // 1.9.188: state.model auto-default → 다음 cycle/prompt 에 catalog 첫 모델
11676
11690
  const cat = _PROVIDER_MODEL_CATALOG[state.provider];
11677
- // 활성 여부 사전 확인 (1.9.180 UX — 사용자가 즉시 인지)
11691
+ if (cat && cat.length) state.model = cat[0].id;
11678
11692
  let activeMark = '';
11679
11693
  try {
11680
11694
  if (state.provider !== 'ollama') {
@@ -11685,19 +11699,21 @@ async function _agentRepl(root, opts) {
11685
11699
  }
11686
11700
  }
11687
11701
  } catch {}
11688
- process.stdout.write('\r\x1b[K');
11689
11702
  process.stdout.write(C.bold(C.green(` ⇄ provider [${idx + 1}/${list.length}]: ${state.provider}`)) + activeMark + '\n');
11703
+ _lastCycleLines = 1;
11690
11704
  if (cat?.length) {
11691
11705
  process.stdout.write(C.dim(` └ ${cat.length}개 모델 catalog · Shift+Tab으로 model cycle\n`));
11706
+ _lastCycleLines = 2;
11692
11707
  }
11693
11708
  rl.setPrompt(prompt());
11694
11709
  rl.prompt(true);
11695
11710
  };
11696
11711
  const cycleModel = (reverse) => {
11712
+ _clearLastCycle();
11697
11713
  const cat = _PROVIDER_MODEL_CATALOG[state.provider] || [];
11698
11714
  if (cat.length === 0) {
11699
- process.stdout.write('\r\x1b[K');
11700
- process.stdout.write(C.yel(` ⚠ ${state.provider} 추천 모델 catalog 없음 (:model <name> 으로 직접 지정)\n`));
11715
+ process.stdout.write(C.yel(` ⚠ ${state.provider} 추천 모델 catalog 없음 (/model <name> 으로 직접 지정)\n`));
11716
+ _lastCycleLines = 1;
11701
11717
  rl.prompt(true);
11702
11718
  return;
11703
11719
  }
@@ -11705,14 +11721,17 @@ async function _agentRepl(root, opts) {
11705
11721
  if (idx < 0) idx = -1;
11706
11722
  idx = reverse ? (idx - 1 + cat.length) % cat.length : (idx + 1) % cat.length;
11707
11723
  state.model = cat[idx].id;
11708
- process.stdout.write('\r\x1b[K');
11709
11724
  process.stdout.write(C.bold(C.mag(` ⇄ model [${idx + 1}/${cat.length}]: ${state.model}`)) + '\n');
11725
+ _lastCycleLines = 1;
11710
11726
  if (cat[idx].note) {
11711
11727
  process.stdout.write(C.dim(` └ ${cat[idx].note}\n`));
11728
+ _lastCycleLines = 2;
11712
11729
  }
11713
11730
  rl.setPrompt(prompt());
11714
11731
  rl.prompt(true);
11715
11732
  };
11733
+ // 1.9.189: 사용자가 다른 입력 시작하면 cycle line clear 흔적 정리 (다음 Tab까지 유지하면 cycle 위치 보임)
11734
+ // 단순화: 사용자가 enter 누르면 _lastCycleLines=0 으로 reset (응답 후 다시 cycle 출력 시 새로 시작).
11716
11735
  process.stdin.on('keypress', (str, key) => {
11717
11736
  if (!key) return;
11718
11737
  if (key.name === 'tab') {
@@ -11731,6 +11750,55 @@ async function _agentRepl(root, opts) {
11731
11750
  }
11732
11751
  }
11733
11752
 
11753
+ // 1.9.189 (사용자 명시): "/" 입력만으로 명령 list 표시 (claude code 영감 — autocomplete UX).
11754
+ // "/" 또는 "/?" → 명령 카탈로그 출력 후 prompt 복귀. 이미지 같은 list 스타일 (명령 + 한 줄 설명).
11755
+ const _showSlashCommandList = () => {
11756
+ log('');
11757
+ log(C.bold(' Available commands') + C.dim(' (also accepts ":" prefix)'));
11758
+ log(C.dim(' ────────────────────────────────────────────────────────────────────────────────'));
11759
+ const groups = [
11760
+ { title: 'meta', items: [
11761
+ ['/help', '명령 목록 (이 화면) — 또는 ":?"'],
11762
+ ['/quit', 'REPL 종료 (세션 자동 저장)'],
11763
+ ['/clear', '대화 히스토리 초기화'],
11764
+ ['/status', '현재 provider / model / role / perms 상태'],
11765
+ ['/provider <id>', 'provider 전환 (ollama, claude, codex, gemini, copilot)'],
11766
+ ['/model <id>', '현재 provider의 모델 전환'],
11767
+ ['/role <id>', '역할 전환 (actor / planner / reviewer)'],
11768
+ ['/stream on|off', '실시간 스트리밍 토글 (default ON)']
11769
+ ]},
11770
+ { title: 'review', items: [
11771
+ ['/review "<req>"', '무조건 구현 전 사전 검토 (1.9.176)'],
11772
+ ['/permissions <m>', '권한 변경 basic|extended|full (1.9.174)']
11773
+ ]},
11774
+ { title: 'internal', items: [
11775
+ ['/verify', 'task evidence 검증'],
11776
+ ['/audit', 'audit 실행'],
11777
+ ['/handoff', 'handoff context 출력'],
11778
+ ['/health', 'health check']
11779
+ ]},
11780
+ { title: 'memory', items: [
11781
+ ['/lessons', 'lessons.md 회수'],
11782
+ ['/brainstorm <q>', 'brainstorm 회수 (memory + code + tasks)'],
11783
+ ['/tasks', '현재 task list'],
11784
+ ['/plan', 'plan.md milestones']
11785
+ ]},
11786
+ { title: 'bridge', items: [
11787
+ ['/web <op>', 'playwright bridge (check/screenshot/extract)'],
11788
+ ['/pc <op>', 'robotjs bridge (check/click/type/screenshot)'],
11789
+ ['/lsp <op>', 'LSP adapter (check/symbols/references)']
11790
+ ]}
11791
+ ];
11792
+ for (const g of groups) {
11793
+ log(C.bold(` ${g.title}`));
11794
+ for (const [name, desc] of g.items) {
11795
+ log(' ' + C.cy(name.padEnd(22)) + C.dim(desc));
11796
+ }
11797
+ }
11798
+ log(C.dim(' ────────────────────────────────────────────────────────────────────────────────'));
11799
+ log(C.dim(' 💡 메시지는 직접 입력 · "/" 또는 ":" 접두사로 명령 호출 · Tab=provider · Shift+Tab=model'));
11800
+ log('');
11801
+ };
11734
11802
  rl.prompt();
11735
11803
  const handleMeta = async (cmd) => {
11736
11804
  const [op, ...rest] = cmd.slice(1).split(/\s+/);
@@ -12020,7 +12088,21 @@ async function _agentRepl(root, opts) {
12020
12088
  return new Promise(resolve => {
12021
12089
  rl.on('line', async (line) => {
12022
12090
  const input = line.trim();
12091
+ _lastCycleLines = 0; // 1.9.189: 사용자 입력 시 cycle overwrite 추적 reset
12023
12092
  if (!input) { rl.prompt(); return; }
12093
+ // 1.9.189 (사용자 명시): "/" slash 입력만으로 명령 list 표시 (claude code 영감).
12094
+ // "/" 단독 → 명령 list 출력 후 prompt 복귀.
12095
+ // "/help", "/quit" 등 "/" 접두사 → ":" 동등 처리 (alias).
12096
+ if (input === '/' || input === '/?') {
12097
+ _showSlashCommandList();
12098
+ rl.prompt(); return;
12099
+ }
12100
+ // 1.9.189: "/" 접두사를 ":" 와 동등하게 처리 (사용자 명시 — "/ 가 모든 모델 공통 권한 trigger default")
12101
+ if (input.startsWith('/') && !input.startsWith('//')) {
12102
+ const shouldQuit = await handleMeta(':' + input.slice(1));
12103
+ if (shouldQuit) { resolve(); return; }
12104
+ rl.prompt(); return;
12105
+ }
12024
12106
  if (input.startsWith(':')) {
12025
12107
  const shouldQuit = await handleMeta(input);
12026
12108
  if (shouldQuit) { resolve(); return; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.188",
3
+ "version": "1.9.189",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",