leerness 1.9.245 → 1.9.246
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 +56 -0
- package/README.md +3 -3
- package/bin/harness.js +80 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.246 — 2026-05-26 — UR-0016 REPL UX/UI 개선
|
|
4
|
+
|
|
5
|
+
**🎯 사용자 명시 (UR-0016): REPL agent 채팅 입력칸 옆 컨텍스트 게이지 + 서브 에이전트 가시화 + 정상 완료 초록색 강조.**
|
|
6
|
+
|
|
7
|
+
### 사용자 명시 (UR-0016)
|
|
8
|
+
> *"REPL agent 모드의 기능과 UX UI를 개선해줘: 서브 에이전트 활성화 여부, 채팅입력칸 근처에는 컨텍스트 창 게이지 등 표시, 정상완료된 작업은 초록색 등으로 표시. 별도의 신규 프로젝트를 생성하여 직접 REPL agent 모드를 구동하여 유저가 사용하기 쉽게 개선해줘."*
|
|
9
|
+
|
|
10
|
+
dogfood 검증: `C:\Users\leehy\AppData\Local\Temp\dogfood-1.9.246` 신규 프로젝트에서 `leerness init` + REPL 진입 흐름 직접 검증.
|
|
11
|
+
|
|
12
|
+
### 1. 컨텍스트 창 게이지 (채팅 입력칸 직전 자동 표시)
|
|
13
|
+
모든 prompt 호출 직전에 status bar 한 줄 자동 출력:
|
|
14
|
+
```
|
|
15
|
+
💬 ctx 12msg ~3.4k/200k ████░░░░░░░░░░ 1.7% · agents: *claude ✓codex ·gemini ·copilot · ▶stream
|
|
16
|
+
```
|
|
17
|
+
- `ctx Nmsg` — 누적 대화 메시지 수
|
|
18
|
+
- `~XXk/YYk` — 추정 토큰 / provider별 컨텍스트 윈도우 (claude 200k, claude-4-7 1M, gpt-5 200k, gpt-4.1 1M, gemini 1M, copilot 64k, ollama 8k)
|
|
19
|
+
- `████░░░░░░░░░░ %` — 14문자 progress bar (50% 이상 cyan, 80% 이상 yellow 경고)
|
|
20
|
+
- 토큰 추정 휴리스틱: `chars / 3.5` (CJK/영문 평균치)
|
|
21
|
+
|
|
22
|
+
### 2. 서브 에이전트 활성화 가시화 (한 줄 5종 표시)
|
|
23
|
+
- `*claude` — 현재 활성 provider (bold + green)
|
|
24
|
+
- `✓codex` — 설치+인증 완료 ready (green)
|
|
25
|
+
- `·gemini` — 미설치 또는 비활성 (dim)
|
|
26
|
+
- 5종 모두 한 줄에 표시 → 사용자가 즉시 다른 provider 가용성 인지 → Tab 키로 전환
|
|
27
|
+
|
|
28
|
+
### 3. 정상 완료 작업 초록색 강조 (사용자 명시)
|
|
29
|
+
- assistant 응답 헤더에 `✓` 마커 + `C.green()` 적용
|
|
30
|
+
- stream 모드: `✓ [assistant: claude/sonnet-4-7, role=actor, 1234ms · 567자]` (green)
|
|
31
|
+
- non-stream: `✓ assistant (claude, role=actor, 1234ms)` (green bold)
|
|
32
|
+
- 기존 `[assistant: ...]` (dim) → `✓ [assistant: ...]` (green)
|
|
33
|
+
|
|
34
|
+
### 4. dogfood 신규 프로젝트 직접 구동 검증
|
|
35
|
+
- 임시 디렉토리에 빈 프로젝트 생성 → `leerness init` 정상 동작 확인
|
|
36
|
+
- `leerness agent --interactive` 진입 흐름 검증 (provider 없음 시 안내 출력)
|
|
37
|
+
- `--version` 등 핵심 명령 동작 확인
|
|
38
|
+
|
|
39
|
+
### 5. promptWithStatus wrapper 도입
|
|
40
|
+
- `rl.prompt()` 7회 호출처 모두 `promptWithStatus()` 로 통일
|
|
41
|
+
- line handler / cycle / 초기 진입 등 일관된 status bar 노출
|
|
42
|
+
- wrapper 내부 1건만 실제 `rl.prompt()` 호출 (DRY)
|
|
43
|
+
|
|
44
|
+
### 6. 누적 회귀 (1.9.207~245) — 모두 유지
|
|
45
|
+
- api-skill cache (1.9.245) + CJK 분류 (1.9.243) + env encoding --apply (1.9.242) + 모두 유지
|
|
46
|
+
- handoff JSON 11 필드 · MCP 70 도구 · CJK + py-check + 비정상종료 모두 유지
|
|
47
|
+
|
|
48
|
+
### 7. stress-v191 — **23/23 PASS · 100%**
|
|
49
|
+
- 1.9.246 (11): VERSION + 4 helper + promptWithStatus + 컨텍스트 bar + sub-agent 가시화 + 초록색 ✓ + dogfood init + 통일성
|
|
50
|
+
- 성능 (1): cold start avg 378ms
|
|
51
|
+
- 누적 회귀 (11): 1.9.207~245
|
|
52
|
+
|
|
53
|
+
### 8. 자동 release (108 라운드 main-push streak · 69 라운드 npm publish streak · R202)
|
|
54
|
+
|
|
55
|
+
🎯 **REPL UX 사용자 요구 100% 반영** — 컨텍스트 + 에이전트 + 초록색 강조 통합 status bar.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
3
59
|
## 1.9.245 — 2026-05-26 — UR-0015 API skill cache
|
|
4
60
|
|
|
5
61
|
**📚 사용자 명시 (UR-0015): API 문서/관련링크 자동 정리 + AI 자동 참조 시스템.**
|
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.246 AI Agent Reliability Harness + Sandbox ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · sandbox · drift ║
|
|
17
|
-
║
|
|
17
|
+
║ 🎯 REPL UX (UR-0016) — 컨텍스트 게이지 + 에이전트 + 초록색 ✓ ║
|
|
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.246';
|
|
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 처리).
|
|
@@ -16802,6 +16802,74 @@ async function _agentRepl(root, opts) {
|
|
|
16802
16802
|
const modelTag = modelShort ? C.dim(' · ') + C.mag(modelShort) : '';
|
|
16803
16803
|
return C.cy(`agent[${state.provider}${modelTag}${C.cy('/' + state.role)}${state.streamMode ? C.cy('/▶') : ''}${C.cy(']>')} `);
|
|
16804
16804
|
};
|
|
16805
|
+
// 1.9.246: REPL UX/UI 개선 (사용자 명시 UR-0016) — 컨텍스트 게이지 + 서브 에이전트 가시화 + 정상 작업 강조
|
|
16806
|
+
// 3종 통합 status bar를 채팅 입력칸 직전에 표시.
|
|
16807
|
+
const _maxContextTokens = (provider, model) => {
|
|
16808
|
+
// provider/model 별 컨텍스트 윈도우 (대략값, 사용자 정보 제공용)
|
|
16809
|
+
const m = (model || '').toLowerCase();
|
|
16810
|
+
if (provider === 'claude') {
|
|
16811
|
+
if (m.includes('opus-4-7') || m.includes('sonnet-4-7') || m.includes('4-7')) return 1000000; // 1M extended
|
|
16812
|
+
if (m.includes('opus') || m.includes('sonnet') || m.includes('haiku')) return 200000;
|
|
16813
|
+
return 200000;
|
|
16814
|
+
}
|
|
16815
|
+
if (provider === 'codex' || provider === 'openai') {
|
|
16816
|
+
if (m.includes('gpt-5') || m.includes('5.5')) return 200000;
|
|
16817
|
+
if (m.includes('gpt-4-1') || m.includes('4.1')) return 1000000;
|
|
16818
|
+
if (m.includes('gpt-4')) return 128000;
|
|
16819
|
+
return 128000;
|
|
16820
|
+
}
|
|
16821
|
+
if (provider === 'gemini') return 1000000; // 2M까지 가능하지만 보수적으로 1M
|
|
16822
|
+
if (provider === 'copilot') return 64000;
|
|
16823
|
+
if (provider === 'ollama') return 8000; // 모델별 다르나 default 8k
|
|
16824
|
+
return 100000;
|
|
16825
|
+
};
|
|
16826
|
+
const _estimateTokens = (history) => {
|
|
16827
|
+
// 단순 휴리스틱: 영문 4chars=1tok, CJK 2chars=1tok 평균치 → 3.5chars/tok
|
|
16828
|
+
let totalChars = 0;
|
|
16829
|
+
for (const m of (history || [])) totalChars += (m.content || '').length;
|
|
16830
|
+
return Math.round(totalChars / 3.5);
|
|
16831
|
+
};
|
|
16832
|
+
const _renderStatusBar = () => {
|
|
16833
|
+
if (!isTty) return;
|
|
16834
|
+
try {
|
|
16835
|
+
// (1) 컨텍스트 게이지
|
|
16836
|
+
const msgs = (state.history || []).length;
|
|
16837
|
+
const tokens = _estimateTokens(state.history);
|
|
16838
|
+
const maxTok = _maxContextTokens(state.provider, state.model);
|
|
16839
|
+
const ratio = Math.min(1, tokens / maxTok);
|
|
16840
|
+
const pct = Math.round(ratio * 100);
|
|
16841
|
+
const barWidth = 14;
|
|
16842
|
+
const filled = Math.round(ratio * barWidth);
|
|
16843
|
+
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
16844
|
+
const barColor = pct >= 80 ? C.yel : (pct >= 50 ? C.cy : C.green);
|
|
16845
|
+
const tokStr = tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : `${tokens}`;
|
|
16846
|
+
const maxStr = maxTok >= 1000 ? `${Math.round(maxTok / 1000)}k` : `${maxTok}`;
|
|
16847
|
+
// (2) 서브 에이전트 활성화 — EXTERNAL_AGENTS 5종 + ollama
|
|
16848
|
+
let ready = [];
|
|
16849
|
+
try {
|
|
16850
|
+
ready = EXTERNAL_AGENTS
|
|
16851
|
+
.map(a => ({ id: a.id, st: _checkAgent(a).status }))
|
|
16852
|
+
.filter(x => x.st === 'ready');
|
|
16853
|
+
} catch {}
|
|
16854
|
+
const allAgents = ['claude', 'codex', 'gemini', 'copilot'];
|
|
16855
|
+
const subBar = allAgents.map(id => {
|
|
16856
|
+
if (id === state.provider) return C.bold(C.green(`*${id}`));
|
|
16857
|
+
const isReady = ready.find(r => r.id === id);
|
|
16858
|
+
return isReady ? C.green(`✓${id}`) : C.dim(`·${id}`);
|
|
16859
|
+
}).join(' ');
|
|
16860
|
+
// (3) 한 줄 출력
|
|
16861
|
+
const line = ' ' + C.dim('💬 ctx ') + C.bold(`${msgs}msg`) + C.dim(` ~${tokStr}/${maxStr} `) + barColor(bar) + C.dim(` ${pct}%`)
|
|
16862
|
+
+ C.dim(' · agents: ') + subBar
|
|
16863
|
+
+ C.dim(' · ') + (state.streamMode ? C.green('▶stream') : C.dim('□stream'));
|
|
16864
|
+
process.stdout.write(line + '\n');
|
|
16865
|
+
} catch {}
|
|
16866
|
+
};
|
|
16867
|
+
// 1.9.246: rl.prompt() wrapper — status bar 자동 표시 (사용자 명시 UR-0016)
|
|
16868
|
+
const promptWithStatus = (forceRedraw) => {
|
|
16869
|
+
_renderStatusBar();
|
|
16870
|
+
if (forceRedraw === true) rl.prompt(true);
|
|
16871
|
+
else rl.prompt();
|
|
16872
|
+
};
|
|
16805
16873
|
rl.setPrompt(prompt());
|
|
16806
16874
|
|
|
16807
16875
|
// 1.9.244 HOTFIX: _lastCycleLines 를 outer 스코프로 hoist
|
|
@@ -16963,7 +17031,8 @@ async function _agentRepl(root, opts) {
|
|
|
16963
17031
|
log(C.dim(' 💡 메시지는 직접 입력 · "/" 또는 ":" 접두사로 명령 호출 · Tab=provider · Shift+Tab=model'));
|
|
16964
17032
|
log('');
|
|
16965
17033
|
};
|
|
16966
|
-
|
|
17034
|
+
// 1.9.246: 첫 prompt 표시 전에도 status bar 노출 (사용자 명시 UR-0016)
|
|
17035
|
+
promptWithStatus();
|
|
16967
17036
|
const handleMeta = async (cmd) => {
|
|
16968
17037
|
const [op, ...rest] = cmd.slice(1).split(/\s+/);
|
|
16969
17038
|
if (op === 'quit' || op === 'exit' || op === 'q') {
|
|
@@ -17253,24 +17322,24 @@ async function _agentRepl(root, opts) {
|
|
|
17253
17322
|
rl.on('line', async (line) => {
|
|
17254
17323
|
const input = line.trim();
|
|
17255
17324
|
_lastCycleLines = 0; // 1.9.189: 사용자 입력 시 cycle overwrite 추적 reset
|
|
17256
|
-
if (!input) {
|
|
17325
|
+
if (!input) { promptWithStatus(); return; }
|
|
17257
17326
|
// 1.9.189 (사용자 명시): "/" slash 입력만으로 명령 list 표시 (claude code 영감).
|
|
17258
17327
|
// "/" 단독 → 명령 list 출력 후 prompt 복귀.
|
|
17259
17328
|
// "/help", "/quit" 등 "/" 접두사 → ":" 동등 처리 (alias).
|
|
17260
17329
|
if (input === '/' || input === '/?') {
|
|
17261
17330
|
_showSlashCommandList();
|
|
17262
|
-
|
|
17331
|
+
promptWithStatus(); return;
|
|
17263
17332
|
}
|
|
17264
17333
|
// 1.9.189: "/" 접두사를 ":" 와 동등하게 처리 (사용자 명시 — "/ 가 모든 모델 공통 권한 trigger default")
|
|
17265
17334
|
if (input.startsWith('/') && !input.startsWith('//')) {
|
|
17266
17335
|
const shouldQuit = await handleMeta(':' + input.slice(1));
|
|
17267
17336
|
if (shouldQuit) { resolve(); return; }
|
|
17268
|
-
|
|
17337
|
+
promptWithStatus(); return;
|
|
17269
17338
|
}
|
|
17270
17339
|
if (input.startsWith(':')) {
|
|
17271
17340
|
const shouldQuit = await handleMeta(input);
|
|
17272
17341
|
if (shouldQuit) { resolve(); return; }
|
|
17273
|
-
|
|
17342
|
+
promptWithStatus(); return;
|
|
17274
17343
|
}
|
|
17275
17344
|
// LLM 호출
|
|
17276
17345
|
state.history.push({ role: 'user', content: input });
|
|
@@ -17293,18 +17362,19 @@ async function _agentRepl(root, opts) {
|
|
|
17293
17362
|
}
|
|
17294
17363
|
} else {
|
|
17295
17364
|
log(C.yel(` ⚠ ${state.provider} provider 미지원 — :provider ollama|claude|codex|gemini|copilot`));
|
|
17296
|
-
|
|
17365
|
+
promptWithStatus(); return;
|
|
17297
17366
|
}
|
|
17298
17367
|
const dt = Date.now() - t0;
|
|
17299
17368
|
_recordRun(root, { kind: 'agent_repl_turn', provider: state.provider, model: state.model, role: state.role, durationMs: dt, ok: result.ok, error: result.error, promptChars: finalPrompt.length, responseChars: (result.response || '').length });
|
|
17300
17369
|
if (result.ok) {
|
|
17301
17370
|
state.history.push({ role: 'assistant', content: result.response });
|
|
17302
17371
|
// 1.9.170: stream 모드에서는 이미 실시간으로 출력됐으므로 헤더만 표시 (응답 중복 방지)
|
|
17372
|
+
// 1.9.246: 정상 완료 작업은 초록색 ✓ 강조 (사용자 명시 UR-0016)
|
|
17303
17373
|
if (state.streamMode && ['claude', 'codex', 'gemini', 'copilot'].includes(state.provider)) {
|
|
17304
|
-
log(C.
|
|
17374
|
+
log(C.green(` ✓ [assistant: ${state.provider}/${state.model || 'default'}, role=${state.role}, ${dt}ms · ${result.response.length}자]`));
|
|
17305
17375
|
} else {
|
|
17306
17376
|
log('');
|
|
17307
|
-
log(C.
|
|
17377
|
+
log(C.green(`✓ assistant (${state.model || state.provider}, role=${state.role}, ${dt}ms)`));
|
|
17308
17378
|
log(result.response);
|
|
17309
17379
|
}
|
|
17310
17380
|
// 1.9.188 (사용자 명시): 응답 끝 + 입력 구분선 (입력칸 시각 명확)
|
|
@@ -17335,7 +17405,7 @@ async function _agentRepl(root, opts) {
|
|
|
17335
17405
|
}
|
|
17336
17406
|
} catch {}
|
|
17337
17407
|
}
|
|
17338
|
-
|
|
17408
|
+
promptWithStatus();
|
|
17339
17409
|
});
|
|
17340
17410
|
rl.on('close', () => { saveSession(); resolve(); });
|
|
17341
17411
|
});
|