leerness 1.9.188 → 1.9.190

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,214 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.190 — 2026-05-21
4
+
5
+ **🚨 설치 가이드 Ctrl+C 미작동 BUG fix (사용자 명시 — npx 진행 차단).**
6
+
7
+ 자율 모드 120 라운드. 사용자 명시 BUG:
8
+ > `npx --yes leerness@latest init` 으로 설치 가이드에서 Ctrl+C 누르면 설치 진행이 취소되어야 하는데, 진행되는 버그.
9
+
10
+ ### 근본 원인 (코드 audit)
11
+
12
+ 1.9.34~1.9.189 의 `_selectOne` / `_selectMany` 의 raw mode 코드:
13
+ ```js
14
+ } else if (key === '\x03' || key === 'q' || key === '\x1b') {
15
+ cleanup();
16
+ stdout.write('\n 취소됨\n');
17
+ resolve(opts.defaultIndex != null ? options[opts.defaultIndex] : null); // ← BUG
18
+ }
19
+ ```
20
+
21
+ Ctrl+C (`\x03`)가 `q`/`\x1b`(ESC)와 같은 분기로 들어가서 **default 값 반환** → install 흐름이 default 옵션으로 계속 진행 → 사용자 의도(취소)와 정반대 동작.
22
+
23
+ 또한 1.9.184에서 추가한 `process.on('SIGINT', _sigintHandler)` 의 2단계 confirm 은 readline raw mode 가 SIGINT signal 자체를 가로채서 호출되지 않음.
24
+
25
+ ### Fix #1 — `_selectOne` / `_selectMany`: Ctrl+C 명시 분기 + 즉시 종료
26
+
27
+ ```diff
28
+ } else if (key === '\x03') {
29
+ + // 1.9.190 (사용자 명시 BUG fix): raw mode 에서 Ctrl+C → 즉시 설치 종료 (npx 진행 차단).
30
+ + // 이전 (1.9.34~1.9.189): default 값 반환 → install 흐름 계속 진행 (사용자 BUG 보고).
31
+ cleanup();
32
+ stdout.write('\n \x1b[31m✗ 설치 중단됨 (Ctrl+C)\x1b[0m\n');
33
+ process.exit(130);
34
+ +} else if (key === '\x1b' || key === 'q' || key === 'Q') {
35
+ + // ESC/q/Q → 단순 취소 (default 반환, install 흐름 계속)
36
+ cleanup();
37
+ stdout.write('\n 취소됨\n');
38
+ resolve(opts.defaultIndex != null ? options[opts.defaultIndex] : null);
39
+ }
40
+ ```
41
+
42
+ Ctrl+C (\x03) 와 ESC/q 분기를 **완전 분리**:
43
+ - **Ctrl+C** → `process.exit(130)` 즉시 종료
44
+ - **ESC/q/Q** → default 값 반환 (취소만, 흐름 계속)
45
+
46
+ ### Fix #2 — `ask()` readline SIGINT 명시 처리
47
+
48
+ ```js
49
+ function ask(question) {
50
+ return new Promise(resolve => {
51
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
52
+ // 1.9.190: readline 의 자체 SIGINT 처리 — Ctrl+C 시 즉시 종료.
53
+ rl.on('SIGINT', () => {
54
+ try { rl.close(); } catch {}
55
+ process.stdout.write('\n \x1b[31m✗ 설치 중단됨 (Ctrl+C)\x1b[0m\n');
56
+ process.exit(130);
57
+ });
58
+ rl.question(question, answer => { rl.close(); resolve(String(answer || '').trim()); });
59
+ });
60
+ }
61
+ ```
62
+
63
+ readline 의 `'SIGINT'` 이벤트 → 명시 처리 → 즉시 exit.
64
+
65
+ ### Fix #3 — `install()` SIGINT 핸들러 단순화 (2단계 confirm 제거)
66
+
67
+ ```diff
68
+ - // 1.9.184: 2단계 confirm (첫 누름 → 안내, 2초 내 두 번째 → 종료)
69
+ - let _sigintCount = 0; let _sigintTimer = null;
70
+ - const _sigintHandler = () => {
71
+ - _sigintCount++;
72
+ - if (_sigintCount === 1) {
73
+ - process.stdout.write('Ctrl+C 를 2초 이내에 한 번 더 누르면 종료됩니다...');
74
+ - _sigintTimer = setTimeout(() => { _sigintCount = 0; }, 2000);
75
+ - return;
76
+ - }
77
+ - process.exit(130);
78
+ - };
79
+ + // 1.9.184+1.9.190: 설치 도중 Ctrl+C 시 즉시 종료 (사용자 명시 BUG fix — npx 진행 차단).
80
+ + // readline raw mode 가 SIGINT 가로채서 1.9.184 2단계 confirm 동작 안 함 → 즉시 종료로 단순화.
81
+ + const _sigintHandler = () => {
82
+ + process.stdout.write('\n \x1b[31m✗ 설치 중단됨 (Ctrl+C)\x1b[0m\n');
83
+ + process.exit(130);
84
+ + };
85
+ ```
86
+
87
+ ### 시각 효과 (모든 fix 통합)
88
+
89
+ ```
90
+ $ npx --yes leerness@latest init
91
+ 🎁 leerness 설치 시작...
92
+ 설치 언어를 선택하세요
93
+ ↑↓ 이동, Enter 확정, q 취소
94
+ ❯ 자동 감지
95
+ 한국어
96
+ English
97
+ ^C
98
+ ✗ 설치 중단됨 (Ctrl+C)
99
+ $
100
+ ```
101
+
102
+ (이전엔 Ctrl+C 후에도 설치가 계속 진행되어 default 값으로 모든 파일 생성됨 → 사용자 의도와 정반대)
103
+
104
+ ### Verified
105
+ - stress-v135: **13/13 PASS** (사용자 명시 5 + 회귀 2 + 누적 6)
106
+ - e2e 217/217 baseline 유지
107
+ - `--yes` non-interactive install: 회귀 없음
108
+ - ESC/q 단순 취소: 회귀 없음 (default 반환)
109
+ - VERSION = 1.9.190 · autonomous-rounds = 120 · main 자동 push 51 라운드 연속
110
+
111
+ ---
112
+
113
+ ## 1.9.189 — 2026-05-21
114
+
115
+ **⌨️ "/" slash 명령 자동 list + Tab cycle 한 줄 갱신 (사용자 명시 3종).**
116
+
117
+ 자율 모드 119 라운드. 사용자 명시:
118
+ 1. *"권한 기본값 설정을 / 으로 설정해둘 수 있고 (모든 모델 공통)"* — `/` slash default trigger
119
+ 2. *"/ 를 입력하면 관련 명령어가 첨부이미지처럼 나열"* — claude code 영감 UX
120
+ 3. *"탭키로 모델 변경시, 채팅 이력으로 남아서 지져분"* — 한 줄 갱신 (in-place)
121
+
122
+ ### Fix #1 — `/` slash 명령 자동 list
123
+
124
+ **Before** (claude code 화면 처럼 자동 안 됨):
125
+ ```
126
+ agent[claude/actor/▶]> /
127
+ (처리 안 됨 또는 에러)
128
+ ```
129
+
130
+ **After** (claude code 스타일):
131
+ ```
132
+ agent[claude · opus-4-7/actor/▶]> /
133
+
134
+ Available commands (also accepts ":" prefix)
135
+ ────────────────────────────────────────────────...
136
+ meta
137
+ /help 명령 목록
138
+ /quit REPL 종료 (세션 자동 저장)
139
+ /clear 대화 히스토리 초기화
140
+ /status 현재 provider / model / role / perms 상태
141
+ /provider <id> provider 전환
142
+ /model <id> 현재 provider의 모델 전환
143
+ /stream on|off 실시간 스트리밍 토글
144
+ review
145
+ /review "<req>" 무조건 구현 전 사전 검토
146
+ /permissions <m> 권한 변경 basic|extended|full
147
+ internal
148
+ /verify /audit /handoff /health
149
+ memory
150
+ /lessons /brainstorm <q> /tasks /plan
151
+ bridge
152
+ /web <op> /pc <op> /lsp <op>
153
+ ────────────────────────────────────────────────...
154
+ 💡 "/" 또는 ":" 접두사로 명령 호출
155
+ ```
156
+
157
+ `/` 와 `:` **양쪽 동등** (alias 처리). 모든 모델 공통.
158
+
159
+ ### Fix #2 — Tab cycle 한 줄 갱신 (in-place overwrite)
160
+
161
+ **Before** (1.9.188까지) — 매 Tab마다 새 줄 누적 → 채팅 이력 지저분:
162
+ ```
163
+ ⇄ provider [3/5]: codex ✓ ready
164
+ └ 5개 모델 catalog · Shift+Tab으로 model cycle
165
+ ⇄ provider [4/5]: gemini ✓ ready
166
+ └ 3개 모델 catalog · Shift+Tab으로 model cycle
167
+ ⇄ provider [5/5]: copilot ✓ ready
168
+ └ 1개 모델 catalog · Shift+Tab으로 model cycle
169
+ ⇄ provider [1/5]: ollama
170
+ └ 4개 모델 catalog · Shift+Tab으로 model cycle
171
+ ...
172
+ (채팅 영역이 cycle 흔적으로 가득 참)
173
+ ```
174
+
175
+ **After** (1.9.189) — 마지막 1건만 보임:
176
+ ```
177
+ ⇄ provider [2/5]: claude ✓ ready
178
+ └ 5개 모델 catalog · Shift+Tab으로 model cycle
179
+ agent[claude · opus-4-7/actor/▶]> _
180
+ ```
181
+
182
+ ```js
183
+ // 1.9.189: ANSI cursor up + line clear 로 이전 cycle 라인 덮어씀
184
+ let _lastCycleLines = 0;
185
+ const _clearLastCycle = () => {
186
+ if (!isTty || _lastCycleLines === 0) return;
187
+ process.stdout.write('\r\x1b[K'); // 현재 prompt 줄 클리어
188
+ for (let i = 0; i < _lastCycleLines; i++) {
189
+ process.stdout.write('\x1b[1A\x1b[K'); // 한 줄 위로 + 클리어
190
+ }
191
+ _lastCycleLines = 0;
192
+ };
193
+ // cycleProvider/cycleModel 시작 시 호출 → 이전 cycle 출력 제거 → 새 cycle 출력
194
+ // 사용자 line 입력 시 _lastCycleLines = 0 reset (cycle 흔적 더 이상 클리어 X)
195
+ ```
196
+
197
+ ### Fix #3 — Welcome 안내 갱신
198
+ ```
199
+ ───────────────────────────── 채팅 시작 ─────────────────────────────
200
+ 메시지 입력 후 Enter · "/" 입력 시 명령 list · :help 으로도 가능 · Ctrl+C 로 종료
201
+ ```
202
+
203
+ ### Verified
204
+ - stress-v134: **15/15 PASS** (사용자 명시 7 + live 2 + 누적 6)
205
+ - e2e 217/217 baseline 유지
206
+ - `/` 접두사 7개 명령 모두 정상 동작 (alias 처리)
207
+ - Tab cycle in-place overwrite — 채팅 영역 깔끔 유지
208
+ - VERSION = 1.9.189 · autonomous-rounds = 119 · main 자동 push 50 라운드 연속
209
+
210
+ ---
211
+
3
212
  ## 1.9.188 — 2026-05-21
4
213
 
5
214
  **🐛 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.190-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v135-13%2F13-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-120-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-51_rounds-success)]() [![ctrl-c](https://img.shields.io/badge/설치_Ctrl%2BC-즉시_종료_BUG_fix-success)]() [![slash-list](https://img.shields.io/badge/"/"_slash-자동_명령_list-success)]() [![cycle-clean](https://img.shields.io/badge/Tab_cycle-한_줄_갱신-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.190 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- 🐛 한글 prompt stdin 전달 · 세부모델 표시 · 입력 구분선
17
+ 🚨 설치 Ctrl+C 즉시 종료 BUG fix · 51 라운드 main push
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.190';
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 처리).
@@ -163,6 +163,13 @@ function fm(role, readWhen, updateWhen, body) {
163
163
  function ask(question) {
164
164
  return new Promise(resolve => {
165
165
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
166
+ // 1.9.190 (사용자 명시 BUG fix): readline 의 자체 SIGINT 처리 — Ctrl+C 시 즉시 종료.
167
+ // process.on('SIGINT') 핸들러는 readline raw mode 에 가려져 호출 안 됨 → 여기서 명시 처리.
168
+ rl.on('SIGINT', () => {
169
+ try { rl.close(); } catch {}
170
+ process.stdout.write('\n \x1b[31m✗ 설치 중단됨 (Ctrl+C)\x1b[0m\n');
171
+ process.exit(130);
172
+ });
166
173
  rl.question(question, answer => { rl.close(); resolve(String(answer || '').trim()); });
167
174
  });
168
175
  }
@@ -783,20 +790,12 @@ async function resolveInstallOptions(root, opts = {}) {
783
790
 
784
791
  async function install(root, opts = {}) {
785
792
  root = absRoot(root); mkdirp(root);
786
- // 1.9.184 (사용자 명시): 설치 도중 Ctrl+C 시 종료 확인 prompt.
787
- // Ctrl+C 안내 (2초 이내 종료, 계속).
788
- // readline raw mode readline 'SIGINT' 이벤트로 흡수할 있어 process-level + finally 해제.
789
- let _sigintCount = 0; let _sigintTimer = null;
793
+ // 1.9.184+1.9.190: 설치 도중 Ctrl+C 시 즉시 종료 (사용자 명시 BUG fix — npx 설치 진행 차단).
794
+ // 1.9.184 2단계 confirm readline raw mode SIGINT 가로채서 호출 1.9.190 즉시 종료로 단순화.
795
+ // _selectOne/_selectMany stdin '\x03' 직접 처리, ask() 는 rl.on('SIGINT') 처리, 핸들러는 fallback.
796
+ let _sigintTimer = null; // back-compat: 1.9.184 placeholder
790
797
  const _sigintHandler = () => {
791
- _sigintCount++;
792
- if (_sigintCount === 1) {
793
- try { process.stdout.write('\n\n ⚠ 설치 중단하시겠습니까? Ctrl+C 를 2초 이내에 한 번 더 누르면 종료됩니다. (그 외 → 계속 진행)\n'); } catch {}
794
- clearTimeout(_sigintTimer);
795
- _sigintTimer = setTimeout(() => { _sigintCount = 0; }, 2000);
796
- return;
797
- }
798
- clearTimeout(_sigintTimer);
799
- try { process.stdout.write('\n ✗ 설치 중단됨 (사용자 요청)\n'); } catch {}
798
+ try { process.stdout.write('\n \x1b[31m✗ 설치 중단됨 (Ctrl+C)\x1b[0m\n'); } catch {}
800
799
  process.exit(130);
801
800
  };
802
801
  if (!opts.migration && !opts.nonInteractive && process.stdin.isTTY) {
@@ -5539,7 +5538,13 @@ async function _selectOne(question, options, opts = {}) {
5539
5538
  cleanup();
5540
5539
  stdout.write('\n');
5541
5540
  resolve(options[idx]);
5542
- } else if (key === '' || key === 'q' || key === '') {
5541
+ } else if (key === '') {
5542
+ // 1.9.190 (사용자 명시 BUG fix): raw mode 에서 Ctrl+C → 즉시 설치 종료 (npx 진행 차단).
5543
+ // 이전 (1.9.34~1.9.189): default 값 반환 → install 흐름 계속 진행 (사용자 BUG 보고).
5544
+ cleanup();
5545
+ stdout.write('\n \x1b[31m✗ 설치 중단됨 (Ctrl+C)\x1b[0m\n');
5546
+ process.exit(130);
5547
+ } else if (key === '' || key === 'q' || key === 'Q') {
5543
5548
  cleanup();
5544
5549
  stdout.write('\n' + C.dim(' 취소됨') + '\n');
5545
5550
  resolve(opts.defaultIndex != null ? options[opts.defaultIndex] : null);
@@ -5607,7 +5612,13 @@ async function _selectMany(question, options, opts = {}) {
5607
5612
  cleanup();
5608
5613
  stdout.write('\n');
5609
5614
  resolve([...selected].sort((a, b) => a - b).map(i => options[i]));
5610
- } else if (key === '' || key === 'q' || key === '') {
5615
+ } else if (key === '') {
5616
+ // 1.9.190 (사용자 명시 BUG fix): raw mode 에서 Ctrl+C → 즉시 설치 종료 (npx 진행 차단).
5617
+ // 이전 (1.9.34~1.9.189): default 값 반환 → install 흐름 계속 진행 (사용자 BUG 보고).
5618
+ cleanup();
5619
+ stdout.write('\n \x1b[31m✗ 설치 중단됨 (Ctrl+C)\x1b[0m\n');
5620
+ process.exit(130);
5621
+ } else if (key === '' || key === 'q' || key === 'Q') {
5611
5622
  cleanup();
5612
5623
  stdout.write('\n' + C.dim(' 취소됨 (기본값 사용)') + '\n');
5613
5624
  resolve((opts.defaults || []).map(d => typeof d === 'number' ? options[d] : d).filter(Boolean));
@@ -11609,7 +11620,7 @@ async function _agentRepl(root, opts) {
11609
11620
  // 환영 화면 끝에 명확한 구분선 + 안내 → 채팅 시작 명확.
11610
11621
  log('');
11611
11622
  log(C.dim(' ───────────────────────────── 채팅 시작 ─────────────────────────────'));
11612
- log(C.dim(' 메시지 입력 후 Enter · :help 으로 명령 목록 · Ctrl+C 로 종료'));
11623
+ log(C.dim(' 메시지 입력 후 Enter · ') + C.cy('"/"') + C.dim(' 입력 시 명령 list · ') + C.cy(':help') + C.dim(' 으로도 가능 · Ctrl+C 로 종료'));
11613
11624
  // 1.9.155: REPL 진입 시 handoff 컨텍스트 자동 노출 (UX 개선 — 사용자가 매번 :handoff 안 해도 컨텍스트 인지)
11614
11625
  try {
11615
11626
  const hf = cp.spawnSync(process.execPath, [__filename, 'handoff', root, '--compact', '--no-drift-check', '--no-headline'], {
@@ -11664,17 +11675,31 @@ async function _agentRepl(root, opts) {
11664
11675
  } catch {}
11665
11676
  return _PROVIDER_CYCLE_ORDER.slice();
11666
11677
  };
11667
- // 1.9.180: cycleProvider/cycleModel — 시각 피드백 강화 (사용자 명시 "원활하지 않다").
11668
- // 이전: 한 줄 ⇄ 메시지만. 1.9.180: provider 위치 표시 [1/5] + bold highlight + 활성 여부 표시.
11678
+ // 1.9.180+1.9.189: cycleProvider/cycleModel — 갱신 (in-place overwrite).
11679
+ // 1.9.180까지: Tab 누름마다 출력 채팅 이력으로 누적 (사용자 명시: "지져분해보여").
11680
+ // 1.9.189: ANSI cursor up + line clear 로 이전 cycle 라인 덮어씀 → 마지막 1건만 표시.
11681
+ let _lastCycleLines = 0; // 직전 cycle 출력 라인 수 (overwrite 용)
11682
+ const _clearLastCycle = () => {
11683
+ if (!isTty || _lastCycleLines === 0) return;
11684
+ // 현재 prompt 라인 + 이전 cycle 라인들 클리어
11685
+ // 현재 cursor는 prompt 줄. 위로 _lastCycleLines+1 만큼 올라가서 클리어.
11686
+ process.stdout.write('\r\x1b[K'); // 현재 prompt 줄 클리어
11687
+ for (let i = 0; i < _lastCycleLines; i++) {
11688
+ process.stdout.write('\x1b[1A\x1b[K'); // 한 줄 위로 + 클리어
11689
+ }
11690
+ _lastCycleLines = 0;
11691
+ };
11669
11692
  const cycleProvider = (reverse) => {
11693
+ _clearLastCycle();
11670
11694
  const list = getProviders();
11671
11695
  let idx = list.indexOf(state.provider);
11672
11696
  if (idx < 0) idx = 0;
11673
11697
  idx = reverse ? (idx - 1 + list.length) % list.length : (idx + 1) % list.length;
11674
11698
  state.provider = list[idx];
11675
11699
  state.model = null;
11700
+ // 1.9.188: state.model auto-default → 다음 cycle/prompt 에 catalog 첫 모델
11676
11701
  const cat = _PROVIDER_MODEL_CATALOG[state.provider];
11677
- // 활성 여부 사전 확인 (1.9.180 UX — 사용자가 즉시 인지)
11702
+ if (cat && cat.length) state.model = cat[0].id;
11678
11703
  let activeMark = '';
11679
11704
  try {
11680
11705
  if (state.provider !== 'ollama') {
@@ -11685,19 +11710,21 @@ async function _agentRepl(root, opts) {
11685
11710
  }
11686
11711
  }
11687
11712
  } catch {}
11688
- process.stdout.write('\r\x1b[K');
11689
11713
  process.stdout.write(C.bold(C.green(` ⇄ provider [${idx + 1}/${list.length}]: ${state.provider}`)) + activeMark + '\n');
11714
+ _lastCycleLines = 1;
11690
11715
  if (cat?.length) {
11691
11716
  process.stdout.write(C.dim(` └ ${cat.length}개 모델 catalog · Shift+Tab으로 model cycle\n`));
11717
+ _lastCycleLines = 2;
11692
11718
  }
11693
11719
  rl.setPrompt(prompt());
11694
11720
  rl.prompt(true);
11695
11721
  };
11696
11722
  const cycleModel = (reverse) => {
11723
+ _clearLastCycle();
11697
11724
  const cat = _PROVIDER_MODEL_CATALOG[state.provider] || [];
11698
11725
  if (cat.length === 0) {
11699
- process.stdout.write('\r\x1b[K');
11700
- process.stdout.write(C.yel(` ⚠ ${state.provider} 추천 모델 catalog 없음 (:model <name> 으로 직접 지정)\n`));
11726
+ process.stdout.write(C.yel(` ⚠ ${state.provider} 추천 모델 catalog 없음 (/model <name> 으로 직접 지정)\n`));
11727
+ _lastCycleLines = 1;
11701
11728
  rl.prompt(true);
11702
11729
  return;
11703
11730
  }
@@ -11705,14 +11732,17 @@ async function _agentRepl(root, opts) {
11705
11732
  if (idx < 0) idx = -1;
11706
11733
  idx = reverse ? (idx - 1 + cat.length) % cat.length : (idx + 1) % cat.length;
11707
11734
  state.model = cat[idx].id;
11708
- process.stdout.write('\r\x1b[K');
11709
11735
  process.stdout.write(C.bold(C.mag(` ⇄ model [${idx + 1}/${cat.length}]: ${state.model}`)) + '\n');
11736
+ _lastCycleLines = 1;
11710
11737
  if (cat[idx].note) {
11711
11738
  process.stdout.write(C.dim(` └ ${cat[idx].note}\n`));
11739
+ _lastCycleLines = 2;
11712
11740
  }
11713
11741
  rl.setPrompt(prompt());
11714
11742
  rl.prompt(true);
11715
11743
  };
11744
+ // 1.9.189: 사용자가 다른 입력 시작하면 cycle line clear 흔적 정리 (다음 Tab까지 유지하면 cycle 위치 보임)
11745
+ // 단순화: 사용자가 enter 누르면 _lastCycleLines=0 으로 reset (응답 후 다시 cycle 출력 시 새로 시작).
11716
11746
  process.stdin.on('keypress', (str, key) => {
11717
11747
  if (!key) return;
11718
11748
  if (key.name === 'tab') {
@@ -11731,6 +11761,55 @@ async function _agentRepl(root, opts) {
11731
11761
  }
11732
11762
  }
11733
11763
 
11764
+ // 1.9.189 (사용자 명시): "/" 입력만으로 명령 list 표시 (claude code 영감 — autocomplete UX).
11765
+ // "/" 또는 "/?" → 명령 카탈로그 출력 후 prompt 복귀. 이미지 같은 list 스타일 (명령 + 한 줄 설명).
11766
+ const _showSlashCommandList = () => {
11767
+ log('');
11768
+ log(C.bold(' Available commands') + C.dim(' (also accepts ":" prefix)'));
11769
+ log(C.dim(' ────────────────────────────────────────────────────────────────────────────────'));
11770
+ const groups = [
11771
+ { title: 'meta', items: [
11772
+ ['/help', '명령 목록 (이 화면) — 또는 ":?"'],
11773
+ ['/quit', 'REPL 종료 (세션 자동 저장)'],
11774
+ ['/clear', '대화 히스토리 초기화'],
11775
+ ['/status', '현재 provider / model / role / perms 상태'],
11776
+ ['/provider <id>', 'provider 전환 (ollama, claude, codex, gemini, copilot)'],
11777
+ ['/model <id>', '현재 provider의 모델 전환'],
11778
+ ['/role <id>', '역할 전환 (actor / planner / reviewer)'],
11779
+ ['/stream on|off', '실시간 스트리밍 토글 (default ON)']
11780
+ ]},
11781
+ { title: 'review', items: [
11782
+ ['/review "<req>"', '무조건 구현 전 사전 검토 (1.9.176)'],
11783
+ ['/permissions <m>', '권한 변경 basic|extended|full (1.9.174)']
11784
+ ]},
11785
+ { title: 'internal', items: [
11786
+ ['/verify', 'task evidence 검증'],
11787
+ ['/audit', 'audit 실행'],
11788
+ ['/handoff', 'handoff context 출력'],
11789
+ ['/health', 'health check']
11790
+ ]},
11791
+ { title: 'memory', items: [
11792
+ ['/lessons', 'lessons.md 회수'],
11793
+ ['/brainstorm <q>', 'brainstorm 회수 (memory + code + tasks)'],
11794
+ ['/tasks', '현재 task list'],
11795
+ ['/plan', 'plan.md milestones']
11796
+ ]},
11797
+ { title: 'bridge', items: [
11798
+ ['/web <op>', 'playwright bridge (check/screenshot/extract)'],
11799
+ ['/pc <op>', 'robotjs bridge (check/click/type/screenshot)'],
11800
+ ['/lsp <op>', 'LSP adapter (check/symbols/references)']
11801
+ ]}
11802
+ ];
11803
+ for (const g of groups) {
11804
+ log(C.bold(` ${g.title}`));
11805
+ for (const [name, desc] of g.items) {
11806
+ log(' ' + C.cy(name.padEnd(22)) + C.dim(desc));
11807
+ }
11808
+ }
11809
+ log(C.dim(' ────────────────────────────────────────────────────────────────────────────────'));
11810
+ log(C.dim(' 💡 메시지는 직접 입력 · "/" 또는 ":" 접두사로 명령 호출 · Tab=provider · Shift+Tab=model'));
11811
+ log('');
11812
+ };
11734
11813
  rl.prompt();
11735
11814
  const handleMeta = async (cmd) => {
11736
11815
  const [op, ...rest] = cmd.slice(1).split(/\s+/);
@@ -12020,7 +12099,21 @@ async function _agentRepl(root, opts) {
12020
12099
  return new Promise(resolve => {
12021
12100
  rl.on('line', async (line) => {
12022
12101
  const input = line.trim();
12102
+ _lastCycleLines = 0; // 1.9.189: 사용자 입력 시 cycle overwrite 추적 reset
12023
12103
  if (!input) { rl.prompt(); return; }
12104
+ // 1.9.189 (사용자 명시): "/" slash 입력만으로 명령 list 표시 (claude code 영감).
12105
+ // "/" 단독 → 명령 list 출력 후 prompt 복귀.
12106
+ // "/help", "/quit" 등 "/" 접두사 → ":" 동등 처리 (alias).
12107
+ if (input === '/' || input === '/?') {
12108
+ _showSlashCommandList();
12109
+ rl.prompt(); return;
12110
+ }
12111
+ // 1.9.189: "/" 접두사를 ":" 와 동등하게 처리 (사용자 명시 — "/ 가 모든 모델 공통 권한 trigger default")
12112
+ if (input.startsWith('/') && !input.startsWith('//')) {
12113
+ const shouldQuit = await handleMeta(':' + input.slice(1));
12114
+ if (shouldQuit) { resolve(); return; }
12115
+ rl.prompt(); return;
12116
+ }
12024
12117
  if (input.startsWith(':')) {
12025
12118
  const shouldQuit = await handleMeta(input);
12026
12119
  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.190",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",