claude-token-saver 3.23.0 → 3.23.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/package.json +1 -1
- package/src/commands/route-scan.js +8 -0
- package/src/model-rules.js +26 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-token-saver",
|
|
3
|
-
"version": "3.23.
|
|
3
|
+
"version": "3.23.1",
|
|
4
4
|
"description": "Route the easy work your expensive Claude model keeps repeating down to haiku/sonnet — post-hoc session analysis, no realtime router, no extra LLM calls.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -161,6 +161,14 @@ export async function run({ args, hasFlag, numArg }) {
|
|
|
161
161
|
// one rule and the file carries another.
|
|
162
162
|
const mrHook = await import('../model-rules.js');
|
|
163
163
|
const composed = (base, c) => mrHook.composeRuleText(base, c, lang);
|
|
164
|
+
// Re-render ratchet-model.md from THIS version's template. Rule text is
|
|
165
|
+
// composed in code, so an upgrade that rewords it leaves every existing
|
|
166
|
+
// file stale, and the rewrite used to ride along with refreshModelRules
|
|
167
|
+
// — which only runs on a rescan. A session that starts on a warm cache
|
|
168
|
+
// would keep reading the old wording indefinitely. The call writes only
|
|
169
|
+
// when the rendering differs, so the common case is a read and a string
|
|
170
|
+
// compare.
|
|
171
|
+
try { mrHook.syncAllFiles(); } catch (e) { debug('route-scan:sync-ratchet', e); }
|
|
164
172
|
// Registered rules whose delegated-category error rate crossed the
|
|
165
173
|
// health threshold since promotion — the user approved these, so a
|
|
166
174
|
// status change must be briefed, not just written into the md file.
|
package/src/model-rules.js
CHANGED
|
@@ -288,8 +288,15 @@ export function modelRatchetPathFor(scope, targetRoot) {
|
|
|
288
288
|
|
|
289
289
|
/**
|
|
290
290
|
* Regenerate ratchet-model.md for every target that carries model rules.
|
|
291
|
-
* A target whose rules are all gone gets its file
|
|
292
|
-
*
|
|
291
|
+
* A target whose rules are all gone gets its file emptied — not deleted,
|
|
292
|
+
* because CLAUDE.md imports the path (see the note at the loop below).
|
|
293
|
+
*
|
|
294
|
+
* Writes are content-conditional: a target whose rendering already matches
|
|
295
|
+
* what is on disk is left alone, and the returned list names only the files
|
|
296
|
+
* that actually changed. That makes the call cheap enough to run on a cache
|
|
297
|
+
* hit, which is what closes the upgrade gap — the file is rendered from THIS
|
|
298
|
+
* version's template, so an upgrade that reworded the rules reaches the disk
|
|
299
|
+
* on the next session rather than waiting for a rescan to happen to fire.
|
|
293
300
|
*/
|
|
294
301
|
export function syncAllFiles({ previousPaths = [] } = {}) {
|
|
295
302
|
const data = loadModelRules();
|
|
@@ -299,19 +306,30 @@ export function syncAllFiles({ previousPaths = [] } = {}) {
|
|
|
299
306
|
if (!byPath.has(p)) byPath.set(p, []);
|
|
300
307
|
byPath.get(p).push(r);
|
|
301
308
|
}
|
|
302
|
-
|
|
303
|
-
|
|
309
|
+
// Skip the write when the file already says exactly this. An unreadable or
|
|
310
|
+
// missing file reads as "" and therefore always differs, which is the right
|
|
311
|
+
// outcome — it gets written.
|
|
312
|
+
const writeIfChanged = (p, text) => {
|
|
313
|
+
try {
|
|
314
|
+
if (existsSync(p) && readFileSync(p, 'utf8') === text) return false;
|
|
315
|
+
} catch { /* unreadable → fall through and rewrite it */ }
|
|
304
316
|
try {
|
|
305
317
|
mkdirSync(dirname(p), { recursive: true });
|
|
306
|
-
writeFileSync(p,
|
|
307
|
-
|
|
308
|
-
} catch {
|
|
318
|
+
writeFileSync(p, text);
|
|
319
|
+
return true;
|
|
320
|
+
} catch {
|
|
321
|
+
return false; // unwritable target — skip, registry stays authoritative
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
const written = [];
|
|
325
|
+
for (const [p, rules] of byPath) {
|
|
326
|
+
if (writeIfChanged(p, renderModelRatchet(rules))) written.push(p);
|
|
309
327
|
}
|
|
310
328
|
// A target that lost its last rule is emptied, NOT deleted: CLAUDE.md
|
|
311
329
|
// imports this path, and a dangling `@` import is worse than an empty file.
|
|
312
330
|
for (const p of previousPaths) {
|
|
313
331
|
if (!byPath.has(p) && existsSync(p)) {
|
|
314
|
-
|
|
332
|
+
if (writeIfChanged(p, renderModelRatchet([]))) written.push(p);
|
|
315
333
|
}
|
|
316
334
|
}
|
|
317
335
|
return written;
|