claude-token-saver 3.4.2 → 3.4.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/route-scan.js +16 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "3.4.2",
3
+ "version": "3.4.3",
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
@@ -115,8 +115,10 @@ const MIN_BEHAVIOR_CALLS = 2; // fewer calls than this → too little behavior t
115
115
 
116
116
  /**
117
117
  * Narrow the candidate categories from the episode's tool mix.
118
- * Returns an array of category ids (ordered first is the default when
119
- * keywords stay silent), or null when behavior is inconclusive.
118
+ * Returns { ids, fallback } ids are the candidate categories, fallback is
119
+ * the id to use when keywords stay silent (null = keywords are REQUIRED, an
120
+ * id-less episode is not a delegation candidate). Returns null when behavior
121
+ * is inconclusive.
120
122
  */
121
123
  export function behaviorPool(toolCounts) {
122
124
  let run = 0, lookup = 0, write = 0, total = 0;
@@ -127,9 +129,15 @@ export function behaviorPool(toolCounts) {
127
129
  else if (WRITE_TOOLS.has(name)) write += n;
128
130
  }
129
131
  if (total < MIN_BEHAVIOR_CALLS) return null;
130
- if (run > lookup + write) return ['run'];
131
- if (lookup > run + write) return ['explore', 'read', 'check', 'translate'];
132
- return null; // mixed / write-heavy no reliable verdict, keywords decide
132
+ if (run > lookup + write) return { ids: ['run'], fallback: 'run' };
133
+ if (lookup > run + write) return { ids: ['explore', 'read', 'check', 'translate'], fallback: 'explore' };
134
+ // Write-dominant episodes are EDITING work, not delegable lookups without
135
+ // this they leak into read/explore via generic keywords ("설명이 필요해보이는데"
136
+ // + Edit×5 landed in read/T1). Only translate legitimately writes, so it is
137
+ // the sole candidate — and only with explicit translate keywords (no
138
+ // silent fallback: an editing episode is not a delegation candidate).
139
+ if (write > run + lookup) return { ids: ['translate'], fallback: null };
140
+ return null; // mixed — no reliable verdict, keywords decide
133
141
  }
134
142
 
135
143
  /** Weighted keyword score for one category (0 when it has no kw table). */
@@ -176,7 +184,7 @@ export function categorize(text, toolCounts) {
176
184
  if (text.length >= PASTE_MIN_LEN) return CATEGORIES.find((c) => c.id === 'paste');
177
185
  const pool = behaviorPool(toolCounts);
178
186
  const eligible = pool
179
- ? pool.map((id) => CATEGORIES.find((c) => c.id === id))
187
+ ? pool.ids.map((id) => CATEGORIES.find((c) => c.id === id))
180
188
  : CATEGORIES.filter((c) => c.kw);
181
189
  let best = null;
182
190
  let bestScore = 0;
@@ -185,7 +193,8 @@ export function categorize(text, toolCounts) {
185
193
  if (s > bestScore) { best = c; bestScore = s; }
186
194
  }
187
195
  if (best) return best;
188
- return pool ? eligible[0] : null;
196
+ if (pool?.fallback) return CATEGORIES.find((c) => c.id === pool.fallback);
197
+ return null;
189
198
  }
190
199
 
191
200
  function isSkippable(text) {