claude-token-saver 3.8.1 → 3.8.2

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
@@ -263,6 +263,11 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
263
263
 
264
264
  ## Release notes
265
265
 
266
+ ### v3.8.2 (2026-08-01)
267
+ - **Fixed: the context tier never came back down after compaction** — the tier was kept as a high-water mark, so a session that compacted at 80% stayed at tier 1 even though its context had just been emptied, and it refilled to the cap with no signal at all. The tier now follows the measurement down and warns again on the next climb.
268
+ - **Both windows are named** — with `autoCompactWindow` at 400k the briefing measured against 400k (80%) while Claude Code's own display measured against 1M (33%); two irreconcilable numbers for one session. The text now reads `자동 압축 창(400k)의 80% (… 화면의 1M 창 기준으로는 33%)`.
269
+ - **No "start a new session" advice when a compact window is set** — that threshold is exactly where compaction runs on its own, so the briefing suggests writing decisions and next steps to a file instead.
270
+
266
271
  ### v3.8.1 (2026-07-31)
267
272
  - **Fixed: 1M sessions were judged against a 200k window** — the briefing inferred the window from the largest request seen so far, so a 1M session counted as 200k until it had already grown past 250k. At 160k of input it announced "past 80% of the 200k window" — really 16%. The window now comes from the configured model id, and when `autoCompactWindow` is set that is where the session actually turns over, so the percentage is measured against it (the text says `(autoCompactWindow 기준)`). The observed-size heuristic remains only as the fallback for an unreadable model id.
268
273
 
package/README.md CHANGED
@@ -220,6 +220,11 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
220
220
 
221
221
  ## 릴리스 노트
222
222
 
223
+ ### v3.8.2 (2026-08-01)
224
+ - **압축 뒤에도 경고 티어가 안 내려가던 문제 수정** — 컨텍스트 티어를 최고치로만 기억해서, 80%에서 자동 압축이 돌아 컨텍스트가 다시 비어도 티어가 1로 남았습니다. 그 세션은 창을 다시 꽉 채워도 아무 신호를 못 받았습니다. 이제 측정치가 내려가면 티어도 같이 내려가고, 다시 차오르면 정상적으로 경고합니다.
225
+ - **두 가지 창 표기 혼선 정리** — `autoCompactWindow`를 40만으로 잡으면 브리핑은 40만 기준(80%)인데 Claude Code 화면은 1M 기준(33%)이라, 같은 세션의 두 숫자가 서로 안 맞아 보였습니다. 이제 `자동 압축 창(400k)의 80%(… 화면의 1M 창 기준으로는 33%)`처럼 둘 다 적습니다.
226
+ - **압축 창이 설정돼 있으면 "새 세션 시작" 권고를 하지 않습니다** — 그 지점은 압축이 자동으로 처리하는 지점이라, 대신 결정·다음 할 일을 파일에 남기라고 안내합니다.
227
+
223
228
  ### v3.8.1 (2026-07-31)
224
229
  - **1M 세션을 200k 창으로 오판하던 브리핑 버그 수정** — 세션 창을 "지금까지 본 가장 큰 요청"으로 추정해서, 1M 세션이라도 25만 토큰을 넘기 전까지는 200k로 취급했습니다. 그래서 입력 160k에서 "200k 창의 80%를 넘었습니다" 경고가 떴습니다(실제로는 16%). 이제 설정된 모델 ID로 창을 판정하고, `autoCompactWindow`가 잡혀 있으면 그 값이 실제로 세션이 넘어가는 지점이므로 그쪽을 기준으로 %를 계산합니다(문구에도 `(autoCompactWindow 기준)` 표기). 모델 ID를 못 읽는 경우에만 기존 관측치 추정으로 되돌아갑니다.
225
230
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "3.8.1",
3
+ "version": "3.8.2",
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/brief.js CHANGED
@@ -85,18 +85,19 @@ function saveState(state, now) {
85
85
  * is unreadable (env override, settings we do not resolve).
86
86
  */
87
87
  export function ctxWindowFor(observedMax = 0, root = process.cwd()) {
88
- let window = observedMax > WINDOW_1M_MIN_INPUT ? 1_000_000 : 200_000;
88
+ let modelWindow = observedMax > WINDOW_1M_MIN_INPUT ? 1_000_000 : 200_000;
89
+ let window = modelWindow;
89
90
  let compactCapped = false;
90
91
  try {
91
92
  const { model } = resolveModelId(root);
92
- if (isOneMillionModel(model)) window = 1_000_000;
93
+ if (isOneMillionModel(model)) modelWindow = window = 1_000_000;
93
94
  const cap = effectiveWindow(root).value;
94
95
  if (cap !== null && cap < window) {
95
96
  window = cap;
96
97
  compactCapped = true;
97
98
  }
98
99
  } catch { /* settings unreadable — the observed-size fallback still holds */ }
99
- return { window, compactCapped };
100
+ return { window, modelWindow, compactCapped };
100
101
  }
101
102
 
102
103
  /**
@@ -129,8 +130,8 @@ export function sessionCtx(transcriptPath, { root = process.cwd() } = {}) {
129
130
  if (total > 0) { input = total; maxInput = Math.max(maxInput, total); }
130
131
  }
131
132
  if (input == null) return null;
132
- const { window, compactCapped } = ctxWindowFor(maxInput, root);
133
- return { input, window, compactCapped, pct: input / window };
133
+ const { window, modelWindow, compactCapped } = ctxWindowFor(maxInput, root);
134
+ return { input, window, modelWindow, compactCapped, pct: input / window };
134
135
  }
135
136
 
136
137
  function ctxTierOf(pct) {
@@ -175,15 +176,30 @@ export async function runBrief({ sessionId, transcriptPath, now = Date.now() })
175
176
  const ctx = transcriptPath ? sessionCtx(transcriptPath) : null;
176
177
  if (ctx) {
177
178
  const tier = ctxTierOf(ctx.pct);
179
+ // The tier is not monotonic: auto-compaction drops the live context back to
180
+ // a fraction of the window, which starts a new fill cycle. Holding the old
181
+ // high-water tier meant a session that compacted at 80% was never warned
182
+ // again — it silently refilled to the cap with no signal at all.
183
+ if (tier < (s.ctxTier || 0)) s.ctxTier = tier;
178
184
  if (tier > (s.ctxTier || 0)) {
179
- // Name the window the percentage was actually computed against when
180
- // autoCompactWindow caps a 1M model at 400k, "1M 창의 80%" would be a
181
- // number the user cannot reconcile with anything they configured.
185
+ // Name both denominators when they differ. Claude Code's own UI counts
186
+ // against the model window, so a bare "400k 창의 80%" reads as wrong to
187
+ // anyone looking at a statusline that says 32% of 1M — same session,
188
+ // two different windows, no way to reconcile them from the text alone.
182
189
  const winLabel = ctx.window >= 1_000_000 ? '1M' : fmtK(ctx.window);
183
- const capNote = ctx.compactCapped ? ' (autoCompactWindow 기준)' : '';
190
+ const modelPct = Math.round((ctx.input / ctx.modelWindow) * 100);
191
+ const modelLabel = ctx.modelWindow >= 1_000_000 ? '1M' : fmtK(ctx.modelWindow);
192
+ const capNote = ctx.compactCapped ? `, 화면의 ${modelLabel} 창 기준으로는 ${modelPct}%` : '';
193
+ // With autoCompactWindow set, crossing the threshold means compaction is
194
+ // about to run on its own. Telling the user to start a new session there
195
+ // would be advice for a problem the setting already handles.
184
196
  items.push(tier === 2
185
- ? `이 세션의 컨텍스트가 ${winLabel} 창${capNote}의 95%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}). 곧 자동 압축으로 맥락 손실이 생길 수 있으니, 진행 중인 작업을 일단락하고 새 세션을 시작하는 편이 좋습니다.`
186
- : `이 세션의 컨텍스트가 ${winLabel} 창${capNote}80%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}). 이후 요청은 비용이 커지는 구간입니다작업이 일단락되면 세션 시작을 권합니다.`);
197
+ ? (ctx.compactCapped
198
+ ? `이 세션의 컨텍스트가 자동 압축 창(${winLabel})95%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}${capNote}). 자동 압축이 돌아 이전 대화가 요약으로 바뀝니다 지금 단계를 마무리하고 이어서 일은 파일에 적어두면 압축 뒤에도 안전합니다.`
199
+ : `이 세션의 컨텍스트가 ${winLabel} 창의 95%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}). 곧 자동 압축으로 맥락 손실이 생길 수 있으니, 진행 중인 작업을 일단락하고 새 세션을 시작하는 편이 좋습니다.`)
200
+ : (ctx.compactCapped
201
+ ? `이 세션의 컨텍스트가 자동 압축 창(${winLabel})의 80%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}${capNote}). 설정해 둔 압축 지점이 가까워졌습니다 — 압축은 알아서 돌아가니 새 세션을 서두를 필요는 없고, 여기까지의 결정과 다음 할 일만 파일에 남겨두면 됩니다.`
202
+ : `이 세션의 컨텍스트가 ${winLabel} 창의 80%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}). 이후 요청은 비용이 커지는 구간입니다 — 작업이 일단락되면 새 세션 시작을 권합니다.`));
187
203
  s.ctxTier = tier;
188
204
  }
189
205
  }