claude-token-saver 3.21.0 โ 3.22.0
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 +34 -5
- package/README.md +33 -4
- package/bin/cli.js +2 -0
- package/package.json +1 -1
- package/src/commands/install.js +2 -0
- package/src/commands/route-scan.js +12 -0
- package/src/korean-style.js +3 -1
- package/src/model-alias.js +17 -3
- package/src/route-scan.js +25 -1
- package/src/session-records.js +9 -1
package/README.en.md
CHANGED
|
@@ -61,7 +61,7 @@ By run (newest first):
|
|
|
61
61
|
|---|---|
|
|
62
62
|
| ๐จ **No surprise rate limits** | Warns when the 5H/7D window hits 90%; `handoff` backs up your work |
|
|
63
63
|
| ๐ง **Cache waste detection** | Hit rate, TTL, 1M-context detection โ spikes diagnosed with issue codes |
|
|
64
|
-
| ๐ฐ๐ท **Korean writing guidance** |
|
|
64
|
+
| ๐ฐ๐ท **Korean writing guidance** | Offered at install time, defaulting to your locale ([below](#-korean-writing-guidance)) |
|
|
65
65
|
|
|
66
66
|
## Not a router โ 60 seconds
|
|
67
67
|
|
|
@@ -103,7 +103,9 @@ npm i -g claude-token-saver
|
|
|
103
103
|
|
|
104
104
|
The statusline appears at the bottom of Claude Code right away. If auto-registration was skipped (`--ignore-scripts`, sudo, sandboxed installs), run `claude-token-saver install`.
|
|
105
105
|
|
|
106
|
-
One install sets up everything: **statusline, Skill, SessionStart hook, the ๐
ท Harness (5 principles), and a first route-scan.** The harness is **appended** to `~/.claude/CLAUDE.md` as a marked block (your existing content is backed up and preserved) and is left alone if one is already there.
|
|
106
|
+
One install sets up everything: **statusline, Skill, SessionStart hook, the ๐
ท Harness (5 principles), and a first route-scan.** The harness and the Korean writing guidance **show what they add and ask before enabling it.** The harness is **appended** to `~/.claude/CLAUDE.md` as a marked block (your existing content is backed up and preserved) and is left alone if one is already there.
|
|
107
|
+
|
|
108
|
+
Outside a terminal โ npm `postinstall`, CI, piped stdin โ the question is skipped and the old defaults apply. Use `--yes` or `--no-input` to skip it deliberately, `CTS_NO_HARNESS=1 npm i -g claude-token-saver` to skip the harness entirely, and `claude-token-saver harness uninit --global` to undo it.
|
|
107
109
|
|
|
108
110
|
> โ ๏ธ Avoid `sudo` global installs โ the Skill lands in root's `~/.claude` instead of yours. Use nvm/fnm/Volta or `npm config set prefix ~/.npm-global`.
|
|
109
111
|
|
|
@@ -291,9 +293,31 @@ claude-token-saver korean off # off
|
|
|
291
293
|
|
|
292
294
|
Claude Code's output styles can do the same thing, but an output style is **a single global slot**: turning it on takes that slot away from anything else and has to be configured per machine. This ships the guidance inside the package and delivers it through the SessionStart hook that is already installed, so it **applies wherever the CLI is installed and leaves the output-style slot free.** It survives `/clear`, because the hook fires again.
|
|
293
295
|
|
|
294
|
-
Cost is **~1.5k tokens per session, injected once at session start rather than per turn**, and covered by the prompt cache from the second request on. When it is on, a
|
|
296
|
+
Cost is **~1.5k tokens per session, injected once at session start rather than per turn**, and covered by the prompt cache from the second request on. When it is on, a `โ๏ธ` chip appears in the statusline.
|
|
297
|
+
|
|
298
|
+
### Before and after
|
|
299
|
+
|
|
300
|
+
From a real pipeline (yaml-sns-agent), where a Slack bot shells out to `claude -p` to investigate the repository and answers in a thread. The guidance was toggled off and then on, with the same question both times: "explain in three Korean sentences how to narrow down a render failure."
|
|
301
|
+
|
|
302
|
+
**Before** (guidance off)
|
|
303
|
+
|
|
304
|
+
> ๋ ๋ ์คํจ ์์ธ ์ขํ๊ธฐ 3๋จ๊ณ: ๋จผ์ `hn:still --check`๋ก ์คํธ์ด ๋ช
์ธ๋ณด๋ค ์ต์ ์ธ์ง(FRESH/STALE) ํ์ธ โ STALE์ด๋ฉด ์ฌ์์ฑ ์ ํด์ ์ ์์ฐ์ผ๋ก ๋ ๋ ์๋ํ ๊ฒ ์์ธ. ๋ค์ caption-blocks๊ฐ ์ต์ ๋๋ ์ด์
๊ณผ ๋จ์ด์ด ์ผ์นํ๋์ง ์ง์ ๋์กฐ โ TTS ์ฌ์์ฑ ํ blocks ์ฌ์์ฑ ๋น ์ง๋ฉด ์๋งยท์์ฑ ์ด๊ธ๋ ๋ ๋ ๊นจ์ง.
|
|
305
|
+
|
|
306
|
+
**After** (guidance on)
|
|
295
307
|
|
|
296
|
-
|
|
308
|
+
> ๋จผ์ `hn:still --check` ๋๋ ค ์คํธ์ด ๋ช
์ธ๋ณด๋ค ์ต์ ์ธ์ง ํ์ธํ๋ค. STALE์ด๋ฉด ์ฌ์์ฑ ์ ํด์ ์๊ธด ๋ฌธ์ .
|
|
309
|
+
>
|
|
310
|
+
> ๋ค์ caption-blocks๊ฐ captions.json ๋จ์ด์ด๊ณผ ์ผ์นํ๋์ง ๋ณธ๋ค. ๋ด๋ ์ด์
์ฌTTS ํ blocks ์ฌ์์ฑ ๋น ์ง๋ฉด ์ ์๋ง์ด ์ ์์ฑ ์์ ๋ฌ๋ค.
|
|
311
|
+
|
|
312
|
+
Three things change. Clauses chained with em dashes become separate sentences, so one sentence carries one fact. Noun-stopped phrases (ํ์ธ, ๋์กฐ, ๋ ๋ ๊นจ์ง โ "check", "compare", "render breaks") become predicates (ํ์ธํ๋ค, ๋ณธ๋ค, ๋ฌ๋ค), which makes it explicit that these are steps to take. And the particles come back where they had been dropped, so subject and object are legible on the first read.
|
|
313
|
+
|
|
314
|
+
The technical content is identical in both. The guidance touches sentence construction only, not judgement or accuracy: the answer does not change, it just stops needing a second read. In a channel people scroll through, that difference cuts follow-up questions โ and the tokens those follow-ups would have cost.
|
|
315
|
+
|
|
316
|
+
### Asked at install time
|
|
317
|
+
|
|
318
|
+
The install **prints what the guidance changes, its per-session cost and its source, then asks.** A Korean system locale (`ko_KR` and friends; on macOS the system setting is checked too) makes the question default to yes; anything else defaults to no, so users who never write Korean are not billed 1.5k tokens a session. The locale is only a default, so an English-locale machine used for Korean work can still turn it on right there.
|
|
319
|
+
|
|
320
|
+
Installs with nobody attached โ npm `postinstall`, CI, piped stdin โ skip the question and apply the locale default, because a blocked prompt hangs the install. In that case, if the locale is not Korean the setting is **left undecided rather than recorded**, so a later run at a terminal still gets to ask. Use `--yes` or `--no-input` to force the non-interactive path, or `CTS_NO_KOREAN=1` to skip the feature entirely. **Once you have turned it on or off yourself, that choice sticks โ an upgrade never overrides it.**
|
|
297
321
|
|
|
298
322
|
> **Source and license**
|
|
299
323
|
> The guidance text comes from [fluent-korean](https://github.com/snflkd/fluent-korean). Copyright (c) 2026 snflkd, MIT License.
|
|
@@ -379,12 +403,17 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
|
|
|
379
403
|
|
|
380
404
|
## Release notes
|
|
381
405
|
|
|
406
|
+
### v3.21.0 (2026-08-22)
|
|
407
|
+
- **The install shows what it is about to enable, then asks** โ the harness 5 principles and the Korean writing guidance used to be switched on by the installer, leaving the user with the result rather than the choice. The install now prints the five principle headings, and for the Korean guidance what it changes plus its per-session cost and source, before asking. Locale detection is demoted from an answer to the question's default, so an English-locale machine used for Korean work can enable it on the spot.
|
|
408
|
+
- **Unattended installs behave exactly as before** โ npm `postinstall`, CI, piped stdin and `CTS_NO_INPUT=1` skip the question and apply the old defaults, because a blocked prompt hangs an install. `--yes` and `--no-input` force that path explicitly. A non-interactive install on a non-Korean machine now leaves the setting undecided instead of recording an answer nobody gave, so a later run at a terminal still asks.
|
|
409
|
+
- **The Korean-guidance statusline chip is now `โ๏ธ`, not `๊ฐ`** โ every other chip is an emoji, so a bare syllable read as a stray character rather than a status indicator.
|
|
410
|
+
|
|
382
411
|
### v3.20.0 (2026-08-22)
|
|
383
412
|
- **The README opening is now scannable** โ prose replaced by a one-line summary, the screenshot, and the install command up top. The three parts (routing, harness, ratchet) are a table; how a saving is computed (before โ after โ gap) is a diagram. The old 30-second pitch table, which repeated all of it, now lists only what the statusline additionally catches.
|
|
384
413
|
|
|
385
414
|
### v3.19.0 (2026-08-22)
|
|
386
415
|
- **Korean writing guidance** โ corrects how Claude writes Korean (dropped sentence parts, noun-stopped sentences, translationese, em-dash overuse), injected once per session. Claude Code's output styles occupy a single global slot and must be configured per machine; this ships the guidance in the package and delivers it through the SessionStart hook already installed, so it **applies in every project and leaves the output-style slot free.** Text vendored from [fluent-korean](https://github.com/snflkd/fluent-korean) (Copyright (c) 2026 snflkd, MIT), license included.
|
|
387
|
-
- **Decided at install time** โ a Korean system locale turns it on; anything else leaves it off. Your own on/off choice is preserved, so upgrades never override it. Skip with `CTS_NO_KOREAN=1
|
|
416
|
+
- **Decided at install time** โ a Korean system locale turns it on; anything else leaves it off. Your own on/off choice is preserved, so upgrades never override it. Skip with `CTS_NO_KOREAN=1`. (From v3.21.0 the install asks instead of deciding for you.)
|
|
388
417
|
- **Corrected an overstated README claim** โ routing savings were described as "the whole product", but the measured โ18.6% comes from the harness and ratchet. The relationship between the three is now stated accurately.
|
|
389
418
|
|
|
390
419
|
### v3.18.0 (2026-08-22)
|
package/README.md
CHANGED
|
@@ -102,7 +102,9 @@ npm i -g claude-token-saver
|
|
|
102
102
|
|
|
103
103
|
์ค์นํ๋ฉด Claude Code ํ๋ฉด ํ๋จ์ statusline์ด ๊ณง๋ฐ๋ก ๋ํ๋ฉ๋๋ค. `--ignore-scripts` ์ต์
์ด๋ sudo ์ฌ์ฉ ๋ฑ์ผ๋ก ์๋ ๋ฑ๋ก์ด ๋์ง ์์๋ค๋ฉด `claude-token-saver install`์ ์คํํด ์ง์ ๋ฑ๋กํ์ญ์์ค.
|
|
104
104
|
|
|
105
|
-
์ค์น ํ ๋ฒ์ผ๋ก **statusline๊ณผ Skill, SessionStart ํ
, ๐
ท Harness(5์์น), ์ต์ด route-scan์ด** ๋ชจ๋ ์ค๋น๋ฉ๋๋ค. Harness๋ `~/.claude/CLAUDE.md`์ ํ์๊ฐ ๋ถ์ ๋ธ๋ก์ผ๋ก **์ถ๊ฐ๋๋ฉฐ**, ๊ธฐ์กด์ ์์ฑํด ๋ ๋ด์ฉ์ ๋ฐฑ์
ํ ๋ค ๊ทธ๋๋ก ๋ณด์กดํฉ๋๋ค. ์ด๋ฏธ ์ค์ ๋์ด ์๋ ๊ฒฝ์ฐ์๋ ์๋ฌด๊ฒ๋ ๋ฐ๊พธ์ง ์์ต๋๋ค.
|
|
105
|
+
์ค์น ํ ๋ฒ์ผ๋ก **statusline๊ณผ Skill, SessionStart ํ
, ๐
ท Harness(5์์น), ์ต์ด route-scan์ด** ๋ชจ๋ ์ค๋น๋ฉ๋๋ค. Harness์ ํ๊ตญ์ด ๋ฌธ์ฒด ์ง์นจ์ **๋ฌด์์ด ์ถ๊ฐ๋๋์ง ๋ณด์ฌ ์ค ๋ค ์ผค์ง ๋ฌผ์ด๋ด
๋๋ค.** Harness๋ `~/.claude/CLAUDE.md`์ ํ์๊ฐ ๋ถ์ ๋ธ๋ก์ผ๋ก **์ถ๊ฐ๋๋ฉฐ**, ๊ธฐ์กด์ ์์ฑํด ๋ ๋ด์ฉ์ ๋ฐฑ์
ํ ๋ค ๊ทธ๋๋ก ๋ณด์กดํฉ๋๋ค. ์ด๋ฏธ ์ค์ ๋์ด ์๋ ๊ฒฝ์ฐ์๋ ์๋ฌด๊ฒ๋ ๋ฐ๊พธ์ง ์์ต๋๋ค.
|
|
106
|
+
|
|
107
|
+
ํฐ๋ฏธ๋์ด ์๋ ํ๊ฒฝ(npm์ `postinstall`, CI, ํ์ดํ ์
๋ ฅ)์์๋ ์ง๋ฌธ์ ๊ฑด๋๋ฐ๊ณ ๊ธฐ์กด ๊ธฐ๋ณธ๊ฐ์ ์ ์ฉํฉ๋๋ค. ์ง๋ฌธ ์์ด ์งํํ๋ ค๋ฉด `--yes`๋ `--no-input`, ์์ ๊ฑด๋๋ฐ๋ ค๋ฉด `CTS_NO_HARNESS=1 npm i -g claude-token-saver`๋ฅผ ์ฐ์ญ์์ค. ์ด๋ฏธ ์ ์ฉํ ์ค์ ์ ๋๋๋ฆฌ๋ ค๋ฉด `claude-token-saver harness uninit --global`์ ์คํํ์ญ์์ค.
|
|
106
108
|
|
|
107
109
|
> โ ๏ธ sudo๋ก ๊ธ๋ก๋ฒ ์ค์น๋ฅผ ํ๋ฉด Skill์ด ์ฌ์ฉ์ ๊ณ์ ์ด ์๋๋ผ root์ `~/.claude`์ ๋ฑ๋ก๋๋ ํจ์ ์ด ์์ต๋๋ค. nvm์ด๋ fnm, Volta๋ฅผ ์ฌ์ฉํด ์ฌ์ฉ์ ์์ญ์ ์ค์นํ๊ธฐ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
|
|
108
110
|
|
|
@@ -281,9 +283,31 @@ claude-token-saver korean off # ๋๊ธฐ
|
|
|
281
283
|
|
|
282
284
|
Claude Code์ output style๋ก๋ ๊ฐ์ ์ผ์ ํ ์ ์์ง๋ง, output style์ **์ ์ญ ์ฌ๋กฏ ํ๋**๋ผ์ ์ผ๋ ์๊ฐ ๋ค๋ฅธ ์คํ์ผ์ ๋ชป ์ฐ๊ฒ ๋๊ณ ๋จธ์ ๋ง๋ค ๋ฐ๋ก ์ค์ ํด์ผ ํฉ๋๋ค. ์ด ๊ธฐ๋ฅ์ ์ง์นจ์ ํจํค์ง์ ๋ด๊ณ ์ด๋ฏธ ์ค์น๋ SessionStart ํ
์ผ๋ก ์ ๋ฌํ๋ฏ๋ก, **CLI๊ฐ ์ค์น๋ ๋ชจ๋ ํ๋ก์ ํธ์ ์ ์ฉ๋๋ฉฐ output style ์ฌ๋กฏ์ ๋น์ ๋ก๋๋ค.** `/clear` ์ดํ์๋ ํ
์ด ๋ค์ ์คํ๋์ด ์ ์ง๋ฉ๋๋ค.
|
|
283
285
|
|
|
284
|
-
๋น์ฉ์ **์ธ์
๋น ์ฝ 1,500 ํ ํฐ์ด๋ฉฐ ๋งค ํด์ด ์๋๋ผ ์ธ์
์์์ ํ ๋ฒ๋ง** ์ฃผ์
๋ฉ๋๋ค. ๋ ๋ฒ์งธ ์์ฒญ๋ถํฐ๋ ํ๋กฌํํธ ์บ์์ ์ฌ๋ผ๊ฐ๋ฏ๋ก ์ถ๊ฐ ๋ถ๋ด์ด ๊ฑฐ์ ์์ต๋๋ค. ์ผ์ ธ ์์ผ๋ฉด statusline์
|
|
286
|
+
๋น์ฉ์ **์ธ์
๋น ์ฝ 1,500 ํ ํฐ์ด๋ฉฐ ๋งค ํด์ด ์๋๋ผ ์ธ์
์์์ ํ ๋ฒ๋ง** ์ฃผ์
๋ฉ๋๋ค. ๋ ๋ฒ์งธ ์์ฒญ๋ถํฐ๋ ํ๋กฌํํธ ์บ์์ ์ฌ๋ผ๊ฐ๋ฏ๋ก ์ถ๊ฐ ๋ถ๋ด์ด ๊ฑฐ์ ์์ต๋๋ค. ์ผ์ ธ ์์ผ๋ฉด statusline์ `โ๏ธ` ์นฉ์ด ํ์๋ฉ๋๋ค.
|
|
287
|
+
|
|
288
|
+
### ์ ์ฉ ์ ํ ๋น๊ต
|
|
289
|
+
|
|
290
|
+
์ฌ๋ ๋ด์ด `claude -p`๋ก ์ ์ฅ์๋ฅผ ์กฐ์ฌํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ์ค๋ ๋์ ๋ตํ๋ ์ค์ ํ์ดํ๋ผ์ธ(yaml-sns-agent)์์, ์ง์นจ๋ง ๊ป๋ค ์ผ๊ณ ๊ฐ์ ์ง๋ฌธ์ ๋์ง ๊ฒฐ๊ณผ์
๋๋ค. ์ง๋ฌธ์ "๋ ๋๊ฐ ์คํจํ์ ๋ ์์ธ์ ์ด๋ป๊ฒ ์ขํ๋์ง ํ๊ตญ์ด 3๋ฌธ์ฅ์ผ๋ก ์ค๋ช
ํด๋ผ"์์ต๋๋ค.
|
|
291
|
+
|
|
292
|
+
**์ด์ ** (์ง์นจ ๊บผ์ง)
|
|
293
|
+
|
|
294
|
+
> ๋ ๋ ์คํจ ์์ธ ์ขํ๊ธฐ 3๋จ๊ณ: ๋จผ์ `hn:still --check`๋ก ์คํธ์ด ๋ช
์ธ๋ณด๋ค ์ต์ ์ธ์ง(FRESH/STALE) ํ์ธ โ STALE์ด๋ฉด ์ฌ์์ฑ ์ ํด์ ์ ์์ฐ์ผ๋ก ๋ ๋ ์๋ํ ๊ฒ ์์ธ. ๋ค์ caption-blocks๊ฐ ์ต์ ๋๋ ์ด์
๊ณผ ๋จ์ด์ด ์ผ์นํ๋์ง ์ง์ ๋์กฐ โ TTS ์ฌ์์ฑ ํ blocks ์ฌ์์ฑ ๋น ์ง๋ฉด ์๋งยท์์ฑ ์ด๊ธ๋ ๋ ๋ ๊นจ์ง.
|
|
295
|
+
|
|
296
|
+
**์ดํ** (์ง์นจ ์ผ์ง)
|
|
285
297
|
|
|
286
|
-
|
|
298
|
+
> ๋จผ์ `hn:still --check` ๋๋ ค ์คํธ์ด ๋ช
์ธ๋ณด๋ค ์ต์ ์ธ์ง ํ์ธํ๋ค. STALE์ด๋ฉด ์ฌ์์ฑ ์ ํด์ ์๊ธด ๋ฌธ์ .
|
|
299
|
+
>
|
|
300
|
+
> ๋ค์ caption-blocks๊ฐ captions.json ๋จ์ด์ด๊ณผ ์ผ์นํ๋์ง ๋ณธ๋ค. ๋ด๋ ์ด์
์ฌTTS ํ blocks ์ฌ์์ฑ ๋น ์ง๋ฉด ์ ์๋ง์ด ์ ์์ฑ ์์ ๋ฌ๋ค.
|
|
301
|
+
|
|
302
|
+
๋ฐ๋ ์ง์ ์ ์ธ ๊ฐ์ง์
๋๋ค. ์ฒซ์งธ, ์ ๋์๋ก ์ด์ด ๋ถ์ด๋ ์ ์ด ๋ง์นจํ๋ก ๋์ด์ ธ ํ ๋ฌธ์ฅ์ด ํ ๊ฐ์ง ์ฌ์ค๋ง ์ ๋ฌํฉ๋๋ค. ๋์งธ, "ํ์ธ", "๋์กฐ", "๋ ๋ ๊นจ์ง" ๊ฐ์ ๋ช
์ฌํ ์ข
๊ฒฐ์ด "ํ์ธํ๋ค", "๋ณธ๋ค", "๋ฌ๋ค"์ฒ๋ผ ์์ ์ด๋ก ๋ฐ๋์ด ๋ฌด์์ ํ๋ผ๋ ๊ฒ์ธ์ง๊ฐ ๋ถ๋ช
ํด์ง๋๋ค. ์
์งธ, "์๋งยท์์ฑ ์ด๊ธ๋"์ฒ๋ผ ์กฐ์ฌ๊ฐ ๋น ์ง ์๋ฆฌ์ ์กฐ์ฌ๊ฐ ๋์์ ์ด๋ค ์ฑ๋ถ์ด ์ฃผ์ด์ด๊ณ ๋ชฉ์ ์ด์ธ์ง ์ฝ๋ ์ฆ์ ์กํ๋๋ค.
|
|
303
|
+
|
|
304
|
+
๊ธฐ์ ์ ๋ด์ฉ์ ์์ชฝ์ด ๋์ผํฉ๋๋ค. ์ง์นจ์ ํ๋จ์ด๋ ์ ํ๋๊ฐ ์๋๋ผ ๋ฌธ์ฅ์ ์์ฑ๋์๋ง ๊ด์ฌํ๋ฏ๋ก, ๋ต์ด ๋ฌ๋ผ์ง๋ ๊ฒ์ด ์๋๋ผ ๊ฐ์ ๋ต์ ๋ค์ ์ฝ์ง ์์๋ ๋๋ ํํ๋ก ๋ง๋ค์ด ์ค๋๋ค. ์ฌ๋์ฒ๋ผ ์ฌ๋์ด ์คํฌ๋กคํ๋ฉฐ ์ฝ๋ ์ฑ๋์์๋ ์ด ์ฐจ์ด๊ฐ ๋๋ฌป๋ ํ์๋ฅผ ์ค์ด๊ณ , ๋๋ฌป์ง ์๋ ๋งํผ ํ ํฐ๋ ์๋๋๋ค.
|
|
305
|
+
|
|
306
|
+
### ์ค์นํ ๋ ๋ฌผ์ด๋ด
๋๋ค
|
|
307
|
+
|
|
308
|
+
์ค์น ๊ณผ์ ์์ **์ง์นจ์ ๋ด์ฉ๊ณผ ์ธ์
๋น ๋น์ฉ, ์ถ์ฒ๋ฅผ ๋จผ์ ๋ณด์ฌ ์ค ๋ค์ ์ผค์ง ๋ฌผ์ด๋ด
๋๋ค.** ์์คํ
๋ก์บ์ด ํ๊ตญ์ด์ด๋ฉด(`ko_KR` ๋ฑ, macOS๋ ์์คํ
์ค์ ๊น์ง ํ์ธ) ์ง๋ฌธ์ ๊ธฐ๋ณธ๊ฐ์ด "์ผฌ"์ด ๋๊ณ , ํ๊ตญ์ด ํ๊ฒฝ์ด ์๋๋ฉด ๊ธฐ๋ณธ๊ฐ์ด "๋"์
๋๋ค. ๋ก์บ์ ๋ต์ด ์๋๋ผ ๊ธฐ๋ณธ๊ฐ์ผ ๋ฟ์ด๋ฏ๋ก ์์ด ๋ก์บ์์ ํ๊ตญ์ด๋ก ์์
ํ๋ ๊ฒฝ์ฐ์๋ ๊ทธ ์๋ฆฌ์์ ์ผค ์ ์์ต๋๋ค.
|
|
309
|
+
|
|
310
|
+
npm์ `postinstall`์ด๋ CI์ฒ๋ผ ์ฌ๋์ด ๋ถ์ด ์์ง ์์ ์ค์น์์๋ ์ง๋ฌธ์ ๊ฑด๋๋ฐ๊ณ ๋ก์บ ๊ธฐ๋ณธ๊ฐ์ ๊ทธ๋๋ก ์ ์ฉํฉ๋๋ค. ํ๋กฌํํธ๊ฐ ๋ฉ์ถฐ ์๋ฉด ์ค์น ์์ฒด๊ฐ ๊ฑธ๋ฆฌ๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด๋ ๋ก์บ์ด ํ๊ตญ์ด๊ฐ ์๋๋ฉด **์ค์ ์ ์ ์ฅํ์ง ์๊ณ ๋ฏธ๊ฒฐ์ ์ผ๋ก ๋จ๊ฒจ ๋๋ฏ๋ก**, ๋์ค์ ํฐ๋ฏธ๋์์ ๋ค์ ์ค์นํ๋ฉด ๊ทธ๋ ๋ฌผ์ด๋ด
๋๋ค. ์ง๋ฌธ ์์ด ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋๊ธฐ๋ ค๋ฉด `--yes`๋ `--no-input`์, ๊ธฐ๋ฅ ์์ฒด๋ฅผ ๊ฑด๋๋ฐ๋ ค๋ฉด `CTS_NO_KOREAN=1`์ ์ฐ์ญ์์ค. **ํ ๋ฒ์ด๋ผ๋ ์ง์ ์ผ๊ฑฐ๋ ๋ ๋ค์๋ ๊ทธ ์ ํ์ ์ ์งํ๋ฏ๋ก, ์
๋ฐ์ดํธ ์ค์น๊ฐ ์ฌ์ฉ์์ ๊ฒฐ์ ์ ๋๋๋ฆฌ์ง ์์ต๋๋ค.**
|
|
287
311
|
|
|
288
312
|
> **์ถ์ฒ์ ๋ผ์ด์ ์ค**
|
|
289
313
|
> ์ง์นจ ์๋ฌธ์ [fluent-korean](https://github.com/snflkd/fluent-korean)์์ ๊ฐ์ ธ์์ต๋๋ค. Copyright (c) 2026 snflkd, MIT License.
|
|
@@ -345,12 +369,17 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
|
|
|
345
369
|
|
|
346
370
|
## ๋ฆด๋ฆฌ์ค ๋
ธํธ
|
|
347
371
|
|
|
372
|
+
### v3.21.0 (2026-08-22)
|
|
373
|
+
- **์ค์น๊ฐ ๋ฌด์์ ์ผ๋์ง ๋ณด์ฌ ์ค ๋ค ๋ฌผ์ด๋ด
๋๋ค** โ ์ง๊ธ๊น์ง๋ ๐
ท Harness 5์์น๊ณผ ํ๊ตญ์ด ๋ฌธ์ฒด ์ง์นจ์ ์ค์น๊ฐ ์ค์ค๋ก ์ผฐ๊ณ , ์ฌ์ฉ์๋ ๊ฒฐ๊ณผ๋ง ๋ณผ ์ ์์์ต๋๋ค. ์ด์ harness๋ ๋ค์ฏ ์์น์ ์ ๋ชฉ์, ํ๊ตญ์ด ์ง์นจ์ ๋ฐ๊พธ๋ ๋ด์ฉ๊ณผ ์ธ์
๋น ๋น์ฉ๊ณผ ์ถ์ฒ๋ฅผ ๋จผ์ ์ถ๋ ฅํ ๋ค์ ์ผค์ง ๋ฌป์ต๋๋ค. ๋ก์บ ๊ฐ์ง๋ ๋ต์ด ์๋๋ผ ์ง๋ฌธ์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋ด๋ ค์์ผ๋ฏ๋ก, ์์ด ๋ก์บ์์ ํ๊ตญ์ด๋ก ์์
ํ๋ ๊ฒฝ์ฐ์๋ ๊ทธ ์๋ฆฌ์์ ์ผค ์ ์์ต๋๋ค.
|
|
374
|
+
- **์ฌ๋์ด ๋ถ์ด ์์ง ์์ ์ค์น๋ ์์ ๋๋ก ๋์ํฉ๋๋ค** โ npm์ `postinstall`, CI, ํ์ดํ ์
๋ ฅ, `CTS_NO_INPUT=1`์์๋ ์ง๋ฌธ์ ๊ฑด๋๋ฐ๊ณ ๊ธฐ์กด ๊ธฐ๋ณธ๊ฐ์ ๊ทธ๋๋ก ์ ์ฉํฉ๋๋ค. ํ๋กฌํํธ๊ฐ ๋ฉ์ถฐ ์๋ฉด ์ค์น๊ฐ ๊ฑธ๋ฆฌ๊ธฐ ๋๋ฌธ์
๋๋ค. `--yes`์ `--no-input`์ผ๋ก ์ง์ ๊ฐ์ ํ ์๋ ์์ต๋๋ค. ๋น๋ํํ์ด๋ฉด์ ๋ก์บ์ด ํ๊ตญ์ด๊ฐ ์๋ ๊ฒฝ์ฐ์๋ ์ค์ ์ ์ ์ฅํ์ง ์๊ณ ๋ฏธ๊ฒฐ์ ์ผ๋ก ๋จ๊ฒจ ๋๋ฏ๋ก, ๋์ค์ ํฐ๋ฏธ๋์์ ์ค์นํ๋ฉด ๊ทธ๋ ๋ฌผ์ด๋ด
๋๋ค.
|
|
375
|
+
- **statusline์ ํ๊ตญ์ด ์ง์นจ ์นฉ์ `๊ฐ`์์ `โ๏ธ`๋ก ๋ฐ๊ฟจ์ต๋๋ค** โ ๋ค๋ฅธ ์นฉ์ด ๋ชจ๋ ์ด๋ชจ์ง๋ผ์ ์์ ํ๋๊ฐ ์ํ ํ์๊ฐ ์๋๋ผ ์๋ชป ์์ฌ ๋ค์ด๊ฐ ๊ธ์๋ก ๋ณด์์ต๋๋ค.
|
|
376
|
+
|
|
348
377
|
### v3.20.0 (2026-08-22)
|
|
349
378
|
- **README ์ฒซ ํ๋ฉด์ ํ์ด ์ฝ์ ์ ์๊ฒ ๋ค์ ์งฐ์ต๋๋ค** โ ์ค๋ช
๋ฌธ๋จ์ ๊ฑท์ด๋ด๊ณ ํ ์ค ์์ฝ, ์คํฌ๋ฆฐ์ท, ์ค์น ๋ช
๋ น์ ์์ ๋์์ต๋๋ค. ๋ผ์ฐํ
ยทHarnessยทratchet ์ธ ๊ธฐ๋ฅ์ ํ๋ก, ์ ๊ฐ์ก ๊ณ์ฐ ๋ฐฉ์(๊ธฐ์ค ๋ชจ๋ธ โ ์คํ ๋ชจ๋ธ โ ์ฐจ์ก)์ ๋์์ผ๋ก ๋ฐ๊ฟจ์ต๋๋ค. ๋ค์์ ๊ฐ์ ๋ด์ฉ์ ๋ฐ๋ณตํ๋ "30์ด ์์ฝ" ํ๋ statusline์ด ์ถ๊ฐ๋ก ์ก์ ์ฃผ๋ ํญ๋ชฉ๋ง ๋จ๊ฒจ ์ ๋ฆฌํ์ต๋๋ค.
|
|
350
379
|
|
|
351
380
|
### v3.19.0 (2026-08-22)
|
|
352
381
|
- **ํ๊ตญ์ด ๋ฌธ์ฒด ์ง์นจ ๊ธฐ๋ฅ์ ์ถ๊ฐํ์ต๋๋ค** โ Claude๊ฐ ํ๊ตญ์ด๋ฅผ ์ธ ๋ ๋ํ๋๋ ๋ฌธ์ฒด ๊ฒฐํจ(๋ฌธ์ฅ ์ฑ๋ถ ์๋ต, ๋ช
์ฌํ ์ข
๊ฒฐ, ๋ฒ์ญ์ฒด, ์ ๋์ ๋จ์ฉ)์ ๊ต์ ํ๋ ์ง์นจ์ ์ธ์
์์ ์ ํ ๋ฒ ์ฃผ์
ํฉ๋๋ค. Claude Code์ output style์ ์ ์ญ ์ฌ๋กฏ ํ๋๋ฅผ ์ฐจ์งํ๊ณ ๋จธ์ ๋ง๋ค ์ค์ ํด์ผ ํ์ง๋ง, ์ด ๊ธฐ๋ฅ์ ์ง์นจ์ ํจํค์ง์ ๋ด๊ณ ์ด๋ฏธ ์ค์น๋ SessionStart ํ
์ผ๋ก ์ ๋ฌํ๋ฏ๋ก **CLI๊ฐ ์ค์น๋ ๋ชจ๋ ํ๋ก์ ํธ์ ์ ์ฉ๋๋ฉฐ output style ์ฌ๋กฏ์ ๋น์ ๋ก๋๋ค.** ์ง์นจ ์๋ฌธ์ [fluent-korean](https://github.com/snflkd/fluent-korean)(Copyright (c) 2026 snflkd, MIT)์์ ๊ฐ์ ธ์๊ณ ๋ผ์ด์ ์ค ์ ๋ฌธ์ ํจ๊ป ๋ฐฐํฌํฉ๋๋ค.
|
|
353
|
-
- **์ค์น์ ๋์์ ๊ฒฐ์ ๋ฉ๋๋ค** โ ์์คํ
๋ก์บ์ด ํ๊ตญ์ด์ด๋ฉด ์ค์น ์ ์๋์ผ๋ก ์ผ์ง๊ณ , ์๋๋ฉด ๊บผ ๋ก๋๋ค. ์ง์ ์ผ๊ฑฐ๋ ๋ ๋ค์๋ ๊ทธ ์ ํ์ ์ ์งํ๋ฏ๋ก ์
๋ฐ์ดํธ๊ฐ ์ฌ์ฉ์์ ๊ฒฐ์ ์ ๋๋๋ฆฌ์ง ์์ต๋๋ค. `CTS_NO_KOREAN=1`๋ก ๊ฑด๋๋ธ ์
|
|
382
|
+
- **์ค์น์ ๋์์ ๊ฒฐ์ ๋ฉ๋๋ค** โ ์์คํ
๋ก์บ์ด ํ๊ตญ์ด์ด๋ฉด ์ค์น ์ ์๋์ผ๋ก ์ผ์ง๊ณ , ์๋๋ฉด ๊บผ ๋ก๋๋ค. ์ง์ ์ผ๊ฑฐ๋ ๋ ๋ค์๋ ๊ทธ ์ ํ์ ์ ์งํ๋ฏ๋ก ์
๋ฐ์ดํธ๊ฐ ์ฌ์ฉ์์ ๊ฒฐ์ ์ ๋๋๋ฆฌ์ง ์์ต๋๋ค. `CTS_NO_KOREAN=1`๋ก ๊ฑด๋๋ธ ์ ์์ต๋๋ค(v3.21.0๋ถํฐ๋ ์ค์น๊ฐ ๋ฌป์ต๋๋ค).
|
|
354
383
|
- **README์ ๊ณผ์ฅ๋ ์ค๋ช
์ ๋ฐ๋ก์ก์์ต๋๋ค** โ ๋ผ์ฐํ
์ ๊ฐ์ก์ "์ด ๋๊ตฌ์ ์ ๋ถ"๋ผ๊ณ ์ ์์ผ๋, ์ค์ธก โ18.6%๋ Harness์ ratchet์ ๋์
ํ ํจ๊ณผ์
๋๋ค. ์ธ ๊ธฐ๋ฅ์ ๊ด๊ณ๋ฅผ ์ ํํ ๋ค์ ์ผ์ต๋๋ค.
|
|
355
384
|
|
|
356
385
|
### v3.18.0 (2026-08-22)
|
package/bin/cli.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* npx claude-token-saver --format csv # CSV output
|
|
11
11
|
* npx claude-token-saver --project myproj # filter by project
|
|
12
12
|
* npx claude-token-saver route-scan # detect recurring easy work โ haiku-delegation candidates
|
|
13
|
+
* npx claude-token-saver install # set up skill/hooks/statusline; asks about harness + Korean guidance
|
|
14
|
+
* npx claude-token-saver install --yes # take the defaults without asking (same as --no-input)
|
|
13
15
|
* npx claude-token-saver --install-hook # install PostToolUse hook
|
|
14
16
|
* npx claude-token-saver --uninstall-hook # remove hook
|
|
15
17
|
* npx claude-token-saver --hook-run # internal: called by hook
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-token-saver",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.22.0",
|
|
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": {
|
package/src/commands/install.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* a legacy command file is removed automatically. Cross-platform.
|
|
6
6
|
* claude-token-saver install # install/update the skill
|
|
7
7
|
* claude-token-saver install --force # overwrite existing skill file
|
|
8
|
+
* claude-token-saver install --yes # take the defaults instead of asking
|
|
9
|
+
* claude-token-saver install --no-input # same as --yes; also implied by CTS_NO_INPUT=1
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { debug } from '../debug.js';
|
|
@@ -107,6 +107,18 @@ export async function run({ args, hasFlag, numArg }) {
|
|
|
107
107
|
// what the model reads.
|
|
108
108
|
console.log(` ${mr.composeRuleText(r.rule, r, lang)}`);
|
|
109
109
|
});
|
|
110
|
+
// Runs on a model id we cannot price never reach the aggregate, so a
|
|
111
|
+
// rule whose tier runs entirely on such an id reads as "never fired".
|
|
112
|
+
// Say so here rather than leaving the zero unexplained.
|
|
113
|
+
const scan = rs.readRouteScan();
|
|
114
|
+
if (scan?.unresolvedRuns > 0) {
|
|
115
|
+
const ids = (scan.unresolvedModels || []).join(', ');
|
|
116
|
+
console.log(lang === 'ko'
|
|
117
|
+
? `\nโ ํด์๋์ง ์์ ๋ชจ๋ธ ID ๋๋ฌธ์ ์์ ${scan.unresolvedRuns}๊ฑด์ด ์ง๊ณ์์ ์ ์ธ๋์ต๋๋ค${ids ? ` (${ids})` : ''}.`
|
|
118
|
+
+ '\n profile-map.json ์ modelAliases ์ ํด๋น ID ๋ฅผ ๋งคํํ ๋ค route-scan --refresh ๋ฅผ ์คํํ์ญ์์ค.'
|
|
119
|
+
: `\nโ ${scan.unresolvedRuns} delegated run(s) were excluded โ unpriceable model id${ids ? ` (${ids})` : ''}.`
|
|
120
|
+
+ '\n Map it under modelAliases in profile-map.json, then run route-scan --refresh.');
|
|
121
|
+
}
|
|
110
122
|
console.log(lang === 'ko'
|
|
111
123
|
? '\n์ ๊ฑฐ: claude-token-saver route-scan rules rm <N>'
|
|
112
124
|
: '\nRemove with: claude-token-saver route-scan rules rm <N>');
|
package/src/korean-style.js
CHANGED
|
@@ -123,7 +123,9 @@ export function koreanStyleInjection({ cfg = loadConfig() } = {}) {
|
|
|
123
123
|
if (!text) return null;
|
|
124
124
|
return [
|
|
125
125
|
'[claude-token-saver korean-style] ์ด ์ธ์
์์ ํ๊ตญ์ด๋ฅผ ์ถ๋ ฅํ ๋๋ ์๋ ์ง์นจ์ ๋ฐ๋ฅด์ญ์์ค.',
|
|
126
|
-
'์ด ์ง์นจ์ ์ฌ์ฉ์๊ฐ claude-token-saver์ ์ค์ ํ
|
|
126
|
+
'์ด ์ง์นจ์ ์ฌ์ฉ์๊ฐ claude-token-saver์ ์ค์ ํ ๊ฒ์
๋๋ค.',
|
|
127
|
+
'์ ์ฉ ๋์: ๋ํ ๋ต๋ณ, ๊ทธ๋ฆฌ๊ณ ์๋ก ์์ฑํ๊ฑฐ๋ ์์ ํ๋ ๋งํฌ๋ค์ดยท๋ฌธ์ยท๋ณด๊ณ ์์ ํ๊ตญ์ด ์ฐ๋ฌธ์ ๋ชจ๋ ํฌํจํฉ๋๋ค.',
|
|
128
|
+
'์ ์ฉ ์์ธ: ์๋ฌธ์ ๊ทธ๋๋ก ์ฎ๊ธฐ๋ ์ธ์ฉ, ์ฝ๋, ์ฝ๋ ์ฃผ์, ๊ทธ๋ฆฌ๊ณ ํ๋ก์ ํธ์ ๊ธฐ์กด ํ๊ธฐ ๊ด๋ก๋ฅผ ๋ฐ๋ผ์ผ ํ๋ ์ปค๋ฐ ๋ฉ์์ง์ ๋ก๊ทธ ๋ฌธ์์ด์
๋๋ค.',
|
|
127
129
|
`(์ถ์ฒ: ${KOREAN_STYLE_SOURCE})`,
|
|
128
130
|
'',
|
|
129
131
|
text,
|
package/src/model-alias.js
CHANGED
|
@@ -357,9 +357,13 @@ function topRole(tally) {
|
|
|
357
357
|
* against 1,702 times as a subagent, and those 16 inferred 'main' votes beat
|
|
358
358
|
* the single explicit 'haiku' one at exactly the 80% line.
|
|
359
359
|
*
|
|
360
|
-
* When
|
|
361
|
-
*
|
|
362
|
-
*
|
|
360
|
+
* When a thin explicit bucket vetoes a decisive inference, the explicit role
|
|
361
|
+
* is adopted ('explicit-veto'): a stated `Task(model: ...)` is the stronger
|
|
362
|
+
* evidence, and it is the only evidence left once the inference is rejected.
|
|
363
|
+
*
|
|
364
|
+
* When neither bucket says anything usable the id stays unresolved. An
|
|
365
|
+
* 'unknown' that drops out of the aggregate beats a confident wrong answer
|
|
366
|
+
* that silently re-tiers every run on that profile.
|
|
363
367
|
*/
|
|
364
368
|
export function tallyVotes(votes, { minVotes = MIN_VOTES, minAgreement = MIN_AGREEMENT } = {}) {
|
|
365
369
|
const learned = {};
|
|
@@ -385,6 +389,16 @@ export function tallyVotes(votes, { minVotes = MIN_VOTES, minAgreement = MIN_AGR
|
|
|
385
389
|
// flag, so probably the session model" observations.
|
|
386
390
|
role = inferred.role;
|
|
387
391
|
source = 'inferred';
|
|
392
|
+
} else if (explicit.total > 0
|
|
393
|
+
&& explicit.top / explicit.total >= minAgreement
|
|
394
|
+
&& inferred.role
|
|
395
|
+
&& !rolesAgree(explicit.role, inferred.role)) {
|
|
396
|
+
// The veto above is only coherent if we then believe what did the
|
|
397
|
+
// vetoing. Leaving the id unresolved instead drops every delegated run
|
|
398
|
+
// on that profile out of the aggregate, which is how a haiku profile
|
|
399
|
+
// that doubles as somebody's session model reported zero delegations.
|
|
400
|
+
role = explicit.role;
|
|
401
|
+
source = 'explicit-veto';
|
|
388
402
|
}
|
|
389
403
|
|
|
390
404
|
learned[pid] = {
|
package/src/route-scan.js
CHANGED
|
@@ -280,6 +280,18 @@ export function dominantModel(counts) {
|
|
|
280
280
|
return best;
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
// A delegated run counts as failed on error DENSITY, not on the presence of a
|
|
284
|
+
// single is_error. Binary counting scored a 218-call run that finished its task
|
|
285
|
+
// identically to one that died on its first call. The floor keeps very short
|
|
286
|
+
// runs honest: 1 error in 3 calls is still a failure.
|
|
287
|
+
export const DELEGATED_ERR_DENSITY = 0.1;
|
|
288
|
+
|
|
289
|
+
export function isFailedRun(run) {
|
|
290
|
+
if (!run || !(run.toolErrors > 0)) return false;
|
|
291
|
+
const calls = run.calls > 0 ? run.calls : 1;
|
|
292
|
+
return run.toolErrors / calls > DELEGATED_ERR_DENSITY;
|
|
293
|
+
}
|
|
294
|
+
|
|
283
295
|
export function worthDelegating(tier, rank) {
|
|
284
296
|
const target = TIER_TARGET_RANK[tier];
|
|
285
297
|
return target !== undefined && rank > target;
|
|
@@ -480,10 +492,12 @@ export async function runRouteScan({ days = 14 } = {}) {
|
|
|
480
492
|
// T2 rule firing, a sonnet run a T1 one. Runs that were not a downgrade
|
|
481
493
|
// (same tier or higher) carry no delegation saving and are skipped.
|
|
482
494
|
const delegatedStats = new Map(); // "tier|category|project" โ outcome aggregate
|
|
495
|
+
let unresolvedRuns = 0; // delegated runs dropped for an unpriceable model id
|
|
496
|
+
const unresolvedModels = new Set();
|
|
483
497
|
const bumpDelegated = (key, run, saved) => {
|
|
484
498
|
const d = delegatedStats.get(key) || { runs: 0, errRuns: 0, outTokens: 0, savedUsd: 0 };
|
|
485
499
|
d.runs += 1;
|
|
486
|
-
if (run
|
|
500
|
+
if (isFailedRun(run)) d.errRuns += 1;
|
|
487
501
|
d.outTokens += run.out || 0;
|
|
488
502
|
d.savedUsd += saved;
|
|
489
503
|
delegatedStats.set(key, d);
|
|
@@ -537,6 +551,14 @@ export async function runRouteScan({ days = 14 } = {}) {
|
|
|
537
551
|
}
|
|
538
552
|
for (const run of runs) {
|
|
539
553
|
const runTier = tierForRank(modelRank(run.model));
|
|
554
|
+
// Dropping unresolved model ids is deliberate (see cost.js) โ pricing
|
|
555
|
+
// a gateway id as Sonnet would poison every number here. But dropping
|
|
556
|
+
// them SILENTLY is what made a whole tier of rules report zero
|
|
557
|
+
// delegations with no way to tell why, so keep a count to surface.
|
|
558
|
+
if (!isRecognizedModelId(run.model)) {
|
|
559
|
+
unresolvedRuns += 1;
|
|
560
|
+
if (run.model) unresolvedModels.add(run.model);
|
|
561
|
+
}
|
|
540
562
|
if (!runTier || !worthDelegating(runTier, mainRank)) continue;
|
|
541
563
|
const saved = runSaving(run, mainModel);
|
|
542
564
|
bumpDelegated(`${runTier}|${cat.id}|${projectDir}`, run, saved);
|
|
@@ -641,6 +663,8 @@ export async function runRouteScan({ days = 14 } = {}) {
|
|
|
641
663
|
thresholds,
|
|
642
664
|
candidates,
|
|
643
665
|
resolved: [...resolved],
|
|
666
|
+
unresolvedRuns,
|
|
667
|
+
unresolvedModels: [...unresolvedModels].slice(0, 5),
|
|
644
668
|
};
|
|
645
669
|
try {
|
|
646
670
|
const dir = stateDir();
|
package/src/session-records.js
CHANGED
|
@@ -55,6 +55,14 @@ const REJECTION_RE = /doesn't want to proceed|tool use was rejected|doesn't want
|
|
|
55
55
|
// does not exist" on a Read) stay counted โ those are genuine difficulty signal.
|
|
56
56
|
const SELF_CORRECTED_RE = /File has not been read yet|has been modified since read|String to replace not found|is not running \(status:|<tool_use_error>Blocked:/i;
|
|
57
57
|
|
|
58
|
+
// Environment constraints โ a sandbox without `curl`/`wc`, a corporate proxy
|
|
59
|
+
// timing a fetch out โ are not task difficulty either. The agent routinely
|
|
60
|
+
// routes around them and finishes: one 218-turn run that produced a 9,870-char
|
|
61
|
+
// sourced report was scored a failure on two `command not found` results.
|
|
62
|
+
// NARROW on purpose: only the shell's own "this binary is absent" wording and
|
|
63
|
+
// curl's transport-timeout exit, never a generic non-zero exit.
|
|
64
|
+
const ENVIRONMENT_RE = /command not found|curl: \(28\)|Operation timed out after|ETIMEDOUT|ENOTFOUND|getaddrinfo/i;
|
|
65
|
+
|
|
58
66
|
function toolResultText(content) {
|
|
59
67
|
if (typeof content === 'string') return content;
|
|
60
68
|
if (!Array.isArray(content)) return '';
|
|
@@ -64,7 +72,7 @@ function toolResultText(content) {
|
|
|
64
72
|
function isRealToolError(block) {
|
|
65
73
|
if (!block || block.type !== 'tool_result' || !block.is_error) return false;
|
|
66
74
|
const txt = toolResultText(block.content);
|
|
67
|
-
return !REJECTION_RE.test(txt) && !SELF_CORRECTED_RE.test(txt);
|
|
75
|
+
return !REJECTION_RE.test(txt) && !SELF_CORRECTED_RE.test(txt) && !ENVIRONMENT_RE.test(txt);
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
export async function collectSessionRecords(filePath, { includeContent = true } = {}) {
|