claude-token-saver 3.3.0 → 3.3.1

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/README.en.md CHANGED
@@ -269,6 +269,9 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
269
269
 
270
270
  ## Release notes
271
271
 
272
+ ### v3.3.1 (2026-07-13)
273
+ - **Plain language instead of codes** — bare `R1`/`T2` codes were unreadable for first-time users. Every user-facing listing (SessionStart hook briefing, `route-scan` candidates, `route-scan rules`) now spells tiers out as `T2 (simple — haiku-class is enough)` and scopes as `this project only` / `all projects (global)`. The hook briefing also instructs the model to relay the spelled-out explanation, not the codes. (Statusline chips keep the terse `route? R1` for width; the session briefing carries the meaning.)
274
+
272
275
  ### v3.3.0 (2026-07-13)
273
276
  - **Delegation visibility & briefing** — the whole model-fitting loop is now surfaced to the user:
274
277
  - The SessionStart hook injects, per candidate, **the exact rule text that would be written to ratchet-model.md** — you see precisely what you're approving.
package/README.md CHANGED
@@ -226,6 +226,9 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
226
226
 
227
227
  ## 릴리스 노트
228
228
 
229
+ ### v3.3.1 (2026-07-13)
230
+ - **코드 대신 풀어쓴 설명** — `R1`, `T2` 같은 코드가 설명 없이 노출돼 처음 쓰는 사람이 알 수 없던 문제 수정. 모든 사용자 대면 출력(SessionStart 훅 브리핑, `route-scan` 후보 목록, `route-scan rules` 목록)에서 티어를 `T2 (단순 작업 — haiku급이면 충분)` 식으로 풀어쓰고, scope도 `이 프로젝트만`/`모든 프로젝트(글로벌)`로 표기. 훅 브리핑에는 "사용자에게 전달할 때 코드가 아니라 풀어쓴 설명으로 브리핑하라"는 지시 포함. (statusline 칩은 폭 제약상 `route? R1` 유지 — 의미는 세션 브리핑이 설명)
231
+
229
232
  ### v3.3.0 (2026-07-13)
230
233
  - **위임 가시화·브리핑 강화** — 모델 피팅의 전 과정이 사용자에게 보이도록:
231
234
  - SessionStart 훅이 후보마다 **등록 시 ratchet-model.md에 기록될 룰 원문**을 함께 주입 — 무엇이 등록될지 정확히 보고 승인.
package/bin/cli.js CHANGED
@@ -520,8 +520,13 @@ async function main() {
520
520
  }
521
521
  console.log(lang === 'ko' ? '📐 모델 피팅 룰 (로그 기반 자동 갱신):' : '📐 Model-fitting rules (auto-refreshed from logs):');
522
522
  rules.forEach((r, i) => {
523
- const health = r.status === 'review' ? ' ⚠ rule-health' : '';
524
- console.log(` #${i + 1} [${r.tier}|${r.scope}] ×${r.count || 0} err ${Math.round((r.errRate || 0) * 100)}%${health}`);
523
+ const health = r.status === 'review'
524
+ ? (lang === 'ko' ? ' 에러율 초과 재검토 필요' : ' ⚠ error rate over threshold — needs review')
525
+ : '';
526
+ const stat = lang === 'ko'
527
+ ? `${r.tier} (${rs.tierLabel(r.tier)}) · ${rs.scopeLabel(r.scope)} · 반복 ${r.count || 0}회 · 에러율 ${Math.round((r.errRate || 0) * 100)}%`
528
+ : `${r.tier} (${rs.tierLabel(r.tier, 'en')}) · ${rs.scopeLabel(r.scope, 'en')} · seen ×${r.count || 0} · err ${Math.round((r.errRate || 0) * 100)}%`;
529
+ console.log(` #${i + 1} ${stat}${health}`);
525
530
  console.log(` ${r.rule}`);
526
531
  });
527
532
  console.log('\n제거: claude-token-saver route-scan rules rm <N>');
@@ -571,21 +576,24 @@ async function main() {
571
576
  if (open.length === 0 && reviewRules.length === 0) return; // silent — nothing to inject
572
577
  const lines = [];
573
578
  if (open.length > 0) {
574
- lines.push(`[claude-token-saver route-scan] 최근 ${cache.days}일 세션에서 상위 모델(opus/fable)이 처리한 위임 가능 반복 작업이 감지되었습니다:`);
579
+ lines.push(`[claude-token-saver route-scan] 최근 ${cache.days}일 세션에서 비싼 모델(opus/fable)이 반복 처리해 온, 싼 모델로 넘겨도 되는 작업이 감지되었습니다.`);
580
+ lines.push('(R<N>은 후보 번호, T2/T1은 난이도 등급입니다 — 사용자에게 전달할 때는 코드가 아니라 아래 풀어쓴 설명으로 브리핑하세요)');
575
581
  for (const c of open) {
576
- const tierNote = c.tier === 'T1' ? 'sonnet 위임(중간 난도)' : `${c.agent} 위임(경량)`;
577
- lines.push(` R${c.id} [${c.tier || 'T2'}] (×${c.count}, ${c.project}): ${c.label} → ${tierNote} 권장 (scope 제안: ${c.suggestedScope})`);
578
- lines.push(` 예시: "${c.example}"`);
582
+ const tier = c.tier || 'T2';
583
+ lines.push(` 후보 R${c.id} — "${c.label}" 유형, ${c.count} 반복 (프로젝트: ${c.project})`);
584
+ lines.push(` 판정: ${tier} (${rs.tierLabel(tier)}) → ${c.agent} 서브에이전트 위임 권장 · 적용 범위 제안: ${rs.scopeLabel(c.suggestedScope)}`);
585
+ lines.push(` 예시 요청: "${c.example}"`);
579
586
  lines.push(` 등록 시 ratchet-model.md에 기록될 룰: "${c.rule}"`);
580
587
  }
581
- lines.push('등록하면 다음 세션부터 자동 위임됩니다. 사용자에게 등록 여부를 물을 때 위 룰 원문을 그대로 보여주고, scope까지 확인한 뒤 실행하세요:');
582
- lines.push(' claude-token-saver harness promote R<N> --project|--global # scope는 반드시 사용자에게 확인');
588
+ lines.push('등록하면 다음 세션부터 자동 위임됩니다. 사용자에게 등록 여부를 물을 때 위 룰 원문을 그대로 보여주고, 적용 범위까지 확인한 뒤 실행하세요:');
589
+ lines.push(' claude-token-saver harness promote R<N> --project|--global # 적용 범위는 반드시 사용자에게 확인');
583
590
  lines.push(' claude-token-saver route-scan dismiss <N> # 사용자가 원치 않으면');
584
591
  }
585
592
  if (reviewRules.length > 0) {
586
- lines.push(`[claude-token-saver rule-health] 등록된 모델 피팅 위임 대상 유형의 에러율이 기준(20%)을 넘은 룰이 있습니다 — 사용자에게 브리핑하고 조건 좁히기/제거를 상의하세요:`);
593
+ lines.push(`[claude-token-saver rule-health] 사용자가 승인한 위임중, 위임 대상 유형의 최근 에러율이 기준(20%)을 넘어 재검토가 필요한 룰이 있습니다 — 사용자에게 브리핑하고 조건 좁히기/제거를 상의하세요:`);
587
594
  for (const r of reviewRules) {
588
- lines.push(` #${r.n} [${r.tier}|${r.scope}] 에러율 ${Math.round((r.errRate || 0) * 100)}%: ${r.rule}`);
595
+ lines.push(` #${r.n} (${r.tier} ${rs.tierLabel(r.tier)} · ${rs.scopeLabel(r.scope)}) — 최근 에러율 ${Math.round((r.errRate || 0) * 100)}%`);
596
+ lines.push(` "${r.rule}"`);
589
597
  }
590
598
  lines.push(' 제거: claude-token-saver route-scan rules rm <N>');
591
599
  }
@@ -614,11 +622,18 @@ async function main() {
614
622
  : 'No delegation candidates (below recurrence threshold or already resolved).');
615
623
  return;
616
624
  }
617
- console.log(lang === 'ko' ? '\n위임 후보:' : '\nDelegation candidates:');
625
+ console.log(lang === 'ko' ? '\n위임 후보 (R<N>=후보 번호, T2/T1=난이도 등급):' : '\nDelegation candidates (R<N> = candidate id, T2/T1 = difficulty tier):');
618
626
  for (const c of open) {
619
- console.log(` R${c.id} [${c.tier || 'T2'}] ×${c.count} ${c.label} → ${c.agent} [${c.project}] (scope 제안: ${c.suggestedScope})`);
620
- console.log(` 예시: "${c.example}"`);
621
- console.log(` 룰: ${c.rule}`);
627
+ const tier = c.tier || 'T2';
628
+ if (lang === 'ko') {
629
+ console.log(` R${c.id} "${c.label}" ×${c.count}회 [${c.project}]`);
630
+ console.log(` 판정: ${tier} (${rs.tierLabel(tier)}) → ${c.agent} 위임 권장 · 적용 범위 제안: ${rs.scopeLabel(c.suggestedScope)}`);
631
+ } else {
632
+ console.log(` R${c.id} "${c.label}" ×${c.count} [${c.project}]`);
633
+ console.log(` verdict: ${tier} (${rs.tierLabel(tier, 'en')}) → delegate to ${c.agent} · suggested scope: ${rs.scopeLabel(c.suggestedScope, 'en')}`);
634
+ }
635
+ console.log(` ${lang === 'ko' ? '예시' : 'example'}: "${c.example}"`);
636
+ console.log(` ${lang === 'ko' ? '룰' : 'rule'}: ${c.rule}`);
622
637
  }
623
638
  console.log('');
624
639
  console.log(lang === 'ko' ? '등록 / 무시:' : 'Promote / dismiss:');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "description": "Save tokens on Claude Code — spike diagnosis, 1M-context detection, TTL countdown, statusline. (formerly claude-cache-monitor)",
5
5
  "type": "module",
6
6
  "bin": {
package/src/route-scan.js CHANGED
@@ -406,6 +406,30 @@ export async function shouldRescan(cache, { days = 14 } = {}) {
406
406
  return age >= RESCAN_MAX_AGE_MS;
407
407
  }
408
408
 
409
+ /**
410
+ * Human-readable labels for the T0/T1/T2 codes and scope values — used by
411
+ * every user-facing listing so a bare code never appears without its meaning
412
+ * (first-time users can't be expected to know the tier vocabulary).
413
+ */
414
+ export function tierLabel(tier, lang = 'ko') {
415
+ const ko = {
416
+ T2: '단순 작업 — haiku급이면 충분',
417
+ T1: '중간 난도 — sonnet급이면 충분',
418
+ T0: '고난도 — 지금 모델 유지',
419
+ };
420
+ const en = {
421
+ T2: 'simple — haiku-class is enough',
422
+ T1: 'moderate — sonnet-class is enough',
423
+ T0: 'hard — stays on the session model',
424
+ };
425
+ return (lang === 'ko' ? ko : en)[tier] || tier;
426
+ }
427
+
428
+ export function scopeLabel(scope, lang = 'ko') {
429
+ if (lang === 'ko') return scope === 'global' ? '모든 프로젝트(글로벌)' : '이 프로젝트만';
430
+ return scope === 'global' ? 'all projects (global)' : 'this project only';
431
+ }
432
+
409
433
  /** Candidates not yet promoted/dismissed. */
410
434
  export function openCandidates(cache) {
411
435
  if (!cache?.candidates) return [];