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 +99 -0
- package/README.md +3 -3
- package/bin/harness.js +91 -9
- package/package.json +1 -1
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
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []() []() []() []() []() []() []()
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
15
|
+
║ v1.9.189 AI Agent Reliability Harness + Sandbox ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · sandbox · drift ║
|
|
17
|
-
║
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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(
|
|
11700
|
-
|
|
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; }
|