leerness 1.9.246 → 1.9.247
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 +40 -0
- package/README.md +3 -3
- package/bin/harness.js +89 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.247 — 2026-05-26 — UR-0016 2단계 + UR-0015 2단계
|
|
4
|
+
|
|
5
|
+
**🔁 REPL multi-provider auto-fallback + 📚 api-skill audit 통합.**
|
|
6
|
+
|
|
7
|
+
### 1. UR-0016 2단계 — REPL multi-provider auto-fallback
|
|
8
|
+
사용자 보고 (1.9.246 스크린샷): `codex CLI 응답 실패 (exit=null)` → 멀티 provider auto-fallback 검토.
|
|
9
|
+
|
|
10
|
+
**구현:**
|
|
11
|
+
- `state.autoFallback` 필드 추가 (default OFF — opt-in)
|
|
12
|
+
- 활성화: `:fallback on` (REPL slash) 또는 `LEERNESS_REPL_AUTO_FALLBACK=1` env
|
|
13
|
+
- transient 실패 (`exit=null|timeout`) 감지 시 next ready agent 로 자동 전환 + 재시도 1회
|
|
14
|
+
- 응답 헤더: `↪ auto-fallback (1.9.247): codex 실패 → claude 자동 전환 + 재시도 중...`
|
|
15
|
+
- 성공 시 `✓ fallback 성공 [claude/...]`, 실패 시 친절한 진단 + 수동 :provider 안내
|
|
16
|
+
|
|
17
|
+
### 2. UR-0015 2단계 — api-skill audit 통합
|
|
18
|
+
사용자 명시 (UR-0015): *"AI가 정리해둔 파일이 참조되는지 확인"*
|
|
19
|
+
|
|
20
|
+
**구현:**
|
|
21
|
+
- `audit` 명령에 새 finding 추가: `api_skill_missing`
|
|
22
|
+
- 현재 in-progress task 의 request/nextAction/evidence 에 API 키워드 (`API|endpoint|REST|GraphQL|OAuth|webhook|http[s]?://`) 감지
|
|
23
|
+
- 매칭되는 api-skill 0건 → `warn` + `leerness api-skill add <url> --direction "..."` 안내
|
|
24
|
+
- 매칭 1+건 → `✓ API skill 매칭 OK (현재 task → N건)` 로그
|
|
25
|
+
- non-API task 는 영향 없음
|
|
26
|
+
|
|
27
|
+
### 3. 누적 회귀 (1.9.207~246) — 모두 유지
|
|
28
|
+
- REPL UX status bar (1.9.246) · api-skill cache (1.9.245) · _lastCycleLines HOTFIX (1.9.244)
|
|
29
|
+
- CJK 분류 (1.9.243) · env encoding --apply (1.9.242) · 모두 유지
|
|
30
|
+
- handoff JSON 11 필드 · MCP 70 도구 · agents dispatch 거부 회귀 (1.9.246 flake 방지) 검증
|
|
31
|
+
|
|
32
|
+
### 4. stress-v192 — **20/20 PASS · 100%**
|
|
33
|
+
- 1.9.247 (8): VERSION + autoFallback state + :fallback slash + 재시도 로직 + audit api_skill_missing + 실제 동작 (3 시나리오)
|
|
34
|
+
- 성능 (1): cold start avg 588ms
|
|
35
|
+
- 누적 회귀 (11): 1.9.207~246 + 보안 + e2e flake 방지
|
|
36
|
+
|
|
37
|
+
### 5. 자동 release (109 main-push streak · 70 npm publish streak · R203)
|
|
38
|
+
|
|
39
|
+
🔁 **사용자 실 환경 대응 보강** — 1.9.246 보고된 codex CLI exit=null 케이스 자동 회복 + 후속 API task UR-0015 자동 참조 확인.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
3
43
|
## 1.9.246 — 2026-05-26 — UR-0016 REPL UX/UI 개선
|
|
4
44
|
|
|
5
45
|
**🎯 사용자 명시 (UR-0016): REPL agent 채팅 입력칸 옆 컨텍스트 게이지 + 서브 에이전트 가시화 + 정상 완료 초록색 강조.**
|
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.247 AI Agent Reliability Harness + Sandbox ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · sandbox · drift ║
|
|
17
|
-
║
|
|
17
|
+
║ 🔁 REPL multi-provider fallback + 📚 api-skill audit ║
|
|
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.247';
|
|
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 처리).
|
|
@@ -6491,6 +6491,35 @@ function audit(root, opts = {}) {
|
|
|
6491
6491
|
}
|
|
6492
6492
|
} catch {}
|
|
6493
6493
|
}
|
|
6494
|
+
// 1.9.247 (UR-0015 2단계): api-skill 참조 audit — API 관련 task 인데 .harness/api-skills/ 미참조 시 경고
|
|
6495
|
+
// 사용자 명시 (UR-0015): "AI가 정리해둔 파일이 참조되는지 확인"
|
|
6496
|
+
// 현재 in-progress task 의 request/nextAction 에 API 키워드 (URL, "API", "endpoint", "REST", "GraphQL", "OAuth", "webhook") 있는데
|
|
6497
|
+
// _matchAPISkills() 결과가 0 이면 → 경고 + leerness api-skill add <url> 안내
|
|
6498
|
+
try {
|
|
6499
|
+
const rows = readProgressRows(root);
|
|
6500
|
+
const ip = rows.find(r => r.status === 'in-progress');
|
|
6501
|
+
if (ip) {
|
|
6502
|
+
const taskText = (ip.request || '') + ' ' + (ip.nextAction || '') + ' ' + (ip.evidence || '');
|
|
6503
|
+
const apiKeywords = /\bAPI\b|endpoint|REST|GraphQL|OAuth|webhook|https?:\/\/[^\s]+/i;
|
|
6504
|
+
if (apiKeywords.test(taskText)) {
|
|
6505
|
+
const matched = _matchAPISkills(root, taskText);
|
|
6506
|
+
const allSkills = _listAPISkills(root);
|
|
6507
|
+
if (matched.length === 0) {
|
|
6508
|
+
warnings++;
|
|
6509
|
+
warn(`API 관련 task 감지 (현재: "${(ip.request || '').slice(0, 60)}") — .harness/api-skills/ 매칭 0건 (저장 ${allSkills.length})`);
|
|
6510
|
+
warn(` → leerness api-skill add <url> --direction "구현 방향" 으로 정리 권장 (1.9.245 UR-0015 / 1.9.247 audit)`);
|
|
6511
|
+
_finding('api_skill_missing', 'warn', 'API 관련 task 인데 .harness/api-skills/ 매칭 없음', {
|
|
6512
|
+
taskRequest: (ip.request || '').slice(0, 100),
|
|
6513
|
+
apiSkillsTotal: allSkills.length,
|
|
6514
|
+
matched: 0,
|
|
6515
|
+
hint: 'leerness api-skill add <url> --direction "..."'
|
|
6516
|
+
});
|
|
6517
|
+
} else {
|
|
6518
|
+
ok(`API skill 매칭 OK (현재 task → ${matched.length}건 매칭 in .harness/api-skills/, 1.9.247 UR-0015 2단계)`);
|
|
6519
|
+
}
|
|
6520
|
+
}
|
|
6521
|
+
}
|
|
6522
|
+
} catch {}
|
|
6494
6523
|
// 1.9.63: --strict — warnings ≥ threshold 시 failures로 승격 (CI 친화)
|
|
6495
6524
|
if (has('--strict')) {
|
|
6496
6525
|
const threshold = parseInt(arg('--threshold', '1'), 10);
|
|
@@ -16679,7 +16708,9 @@ async function _agentRepl(root, opts) {
|
|
|
16679
16708
|
startedAt: new Date().toISOString(),
|
|
16680
16709
|
sessionId: 'sess-' + new Date().toISOString().replace(/[:.]/g, '-'),
|
|
16681
16710
|
// 1.9.170: 실시간 스트리밍 모드 (사용자 명시 — 추론중/diff/thinking 실시간 표시)
|
|
16682
|
-
streamMode: opts.stream !== false // default ON (env 로 끄려면 LEERNESS_REPL_STREAM=0)
|
|
16711
|
+
streamMode: opts.stream !== false, // default ON (env 로 끄려면 LEERNESS_REPL_STREAM=0)
|
|
16712
|
+
// 1.9.247 (UR-0016 2단계): multi-provider auto-fallback — default OFF (opt-in)
|
|
16713
|
+
autoFallback: opts.autoFallback === true || process.env.LEERNESS_REPL_AUTO_FALLBACK === '1'
|
|
16683
16714
|
};
|
|
16684
16715
|
if (process.env.LEERNESS_REPL_STREAM === '0') state.streamMode = false;
|
|
16685
16716
|
const sessionPath = () => path.join(absRoot(root), '.harness', 'agent-sessions', `${state.sessionId}.jsonl`);
|
|
@@ -17167,6 +17198,20 @@ async function _agentRepl(root, opts) {
|
|
|
17167
17198
|
rl.setPrompt(prompt());
|
|
17168
17199
|
return false;
|
|
17169
17200
|
}
|
|
17201
|
+
// 1.9.247 (UR-0016 2단계): :fallback on|off — provider 실패 시 다음 ready agent 자동 전환 (opt-in)
|
|
17202
|
+
if (op === 'fallback') {
|
|
17203
|
+
const v = (rest[0] || '').toLowerCase();
|
|
17204
|
+
if (v === 'on' || v === '1' || v === 'true') {
|
|
17205
|
+
state.autoFallback = true;
|
|
17206
|
+
log(C.green(' ↪ auto-fallback: ON (1.9.247) — 현재 provider transient 실패 (timeout/exit=null) 시 다음 ready agent 자동 전환 + 재시도'));
|
|
17207
|
+
} else if (v === 'off' || v === '0' || v === 'false') {
|
|
17208
|
+
state.autoFallback = false;
|
|
17209
|
+
log(C.dim(' ↪ auto-fallback: OFF — 실패 시 사용자가 직접 :provider 전환'));
|
|
17210
|
+
} else {
|
|
17211
|
+
log(C.dim(` 현재 auto-fallback: ${state.autoFallback ? 'ON ↪' : 'OFF'} — 사용: :fallback on|off`));
|
|
17212
|
+
}
|
|
17213
|
+
return false;
|
|
17214
|
+
}
|
|
17170
17215
|
if (op === 'clear') { process.stdout.write('\x1b[2J\x1b[H'); return false; }
|
|
17171
17216
|
if (op === 'reset') { state.history = []; log(C.dim(' history 초기화됨')); return false; }
|
|
17172
17217
|
if (op === 'history') {
|
|
@@ -17393,6 +17438,47 @@ async function _agentRepl(root, opts) {
|
|
|
17393
17438
|
log(C.dim(` ↳ 가능 원인: 인증 누락 (login 필요)`));
|
|
17394
17439
|
log(C.dim(` ↳ 해결: ${state.provider} login ${state.provider === 'copilot' ? '(gh auth login)' : ''}`));
|
|
17395
17440
|
}
|
|
17441
|
+
// 1.9.247 (UR-0016 2단계): 자동 fallback — 사용자 명시 (`:fallback on` 또는 LEERNESS_REPL_AUTO_FALLBACK=1) 시
|
|
17442
|
+
// 현재 provider 가 transient 실패 (timeout/exit=null/network) 시 다음 ready agent 로 자동 전환 + 1회 재시도.
|
|
17443
|
+
// 사용자 명시 (이전 codex CLI exit=null 보고): "멀티 provider auto-fallback 도 검토".
|
|
17444
|
+
const isTransient = /exit=null|timeout/i.test(errMsg);
|
|
17445
|
+
if (state.autoFallback && isTransient) {
|
|
17446
|
+
try {
|
|
17447
|
+
const others = EXTERNAL_AGENTS.filter(a => a.id !== state.provider)
|
|
17448
|
+
.map(a => ({ def: a, status: _checkAgent(a) }))
|
|
17449
|
+
.filter(x => x.status.status === 'ready');
|
|
17450
|
+
if (others.length > 0) {
|
|
17451
|
+
const next = others[0].def.id;
|
|
17452
|
+
log(C.green(` ↪ auto-fallback (1.9.247): ${state.provider} 실패 → ${next} 자동 전환 + 재시도 중...`));
|
|
17453
|
+
state.provider = next;
|
|
17454
|
+
state.model = null;
|
|
17455
|
+
const cat = _PROVIDER_MODEL_CATALOG[state.provider];
|
|
17456
|
+
if (cat && cat.length) state.model = cat[0].id;
|
|
17457
|
+
rl.setPrompt(prompt());
|
|
17458
|
+
const t1 = Date.now();
|
|
17459
|
+
let retry;
|
|
17460
|
+
if (state.streamMode) retry = await _cliChatStream(root, state.provider, finalPrompt, { timeout: 120000 });
|
|
17461
|
+
else retry = await _cliChat(root, state.provider, finalPrompt, { timeout: 90000 });
|
|
17462
|
+
const dt1 = Date.now() - t1;
|
|
17463
|
+
_recordRun(root, { kind: 'agent_repl_turn_fallback', provider: state.provider, model: state.model, role: state.role, durationMs: dt1, ok: retry.ok, error: retry.error, promptChars: finalPrompt.length, responseChars: (retry.response || '').length });
|
|
17464
|
+
if (retry.ok) {
|
|
17465
|
+
state.history.push({ role: 'assistant', content: retry.response });
|
|
17466
|
+
log(C.green(` ✓ fallback 성공 [${state.provider}/${state.model || 'default'}, ${dt1}ms · ${retry.response.length}자]`));
|
|
17467
|
+
if (!state.streamMode) log(retry.response);
|
|
17468
|
+
_printInputDivider();
|
|
17469
|
+
if (state.history.length % 6 === 0) saveSession();
|
|
17470
|
+
promptWithStatus();
|
|
17471
|
+
return;
|
|
17472
|
+
} else {
|
|
17473
|
+
log(C.yel(` ⚠ fallback 도 실패 (${next}): ${retry.error || 'unknown'}`));
|
|
17474
|
+
}
|
|
17475
|
+
} else {
|
|
17476
|
+
log(C.dim(` (auto-fallback: 다른 ready provider 없음)`));
|
|
17477
|
+
}
|
|
17478
|
+
} catch (e) {
|
|
17479
|
+
log(C.dim(` (auto-fallback 오류: ${e.message})`));
|
|
17480
|
+
}
|
|
17481
|
+
}
|
|
17396
17482
|
// 전환 가능 provider 안내
|
|
17397
17483
|
try {
|
|
17398
17484
|
const others = EXTERNAL_AGENTS.filter(a => a.id !== state.provider)
|
|
@@ -17400,6 +17486,7 @@ async function _agentRepl(root, opts) {
|
|
|
17400
17486
|
.filter(x => x.status.status === 'ready');
|
|
17401
17487
|
if (others.length) {
|
|
17402
17488
|
log(C.dim(` 💡 즉시 전환: ${others.map(x => `:provider ${x.def.id}`).join(' · ')} 또는 Tab 키`));
|
|
17489
|
+
if (!state.autoFallback) log(C.dim(` 💡 다음 실패 시 자동 전환: \`:fallback on\` (1.9.247 UR-0016 2단계)`));
|
|
17403
17490
|
} else {
|
|
17404
17491
|
log(C.dim(` 💡 다른 provider 활성화: .env 에서 LEERNESS_ENABLE_<CLAUDE|CODEX|GEMINI|COPILOT>=1`));
|
|
17405
17492
|
}
|