claude-token-saver 2.13.2 โ†’ 2.14.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 CHANGED
@@ -13,11 +13,11 @@ A CLI to **diagnose and save tokens on Claude Code**. Cache hit rate, TTL countd
13
13
 
14
14
  ๐Ÿ“บ [Launch Short (60s)](https://www.youtube.com/shorts/RaD8qMsPTnA)
15
15
 
16
- ## Real-world impact โ€” harness 5/5 + ratchet adoption
16
+ ## Cost-savings report โ€” harness + ratchet adoption
17
17
 
18
- ![claude-token-saver before/after](./docs/harness-impact.png)
18
+ ![claude-token-saver โ€” harness + ratchet adoption impact](./docs/harness-impact.png)
19
19
 
20
- Author's own Claude Code logs, normalized **per user message**, before/after adopting harness rules on 2026-05-02 (Opus 4.7 pricing):
20
+ The recently added **harness 5/5 + ratchet** features applied to the author's own Claude Code work, normalized **per user message** (cutoff 2026-05-02, Opus 4.7 pricing):
21
21
 
22
22
  | metric | before (7d / 739 msgs) | after (2d / 157 msgs) | ฮ” |
23
23
  |---|---:|---:|---:|
@@ -26,7 +26,17 @@ Author's own Claude Code logs, normalized **per user message**, before/after ado
26
26
  | assistant turns / user message | 9.73 | 8.83 | โˆ’9.2% |
27
27
  | tool calls / user message | 5.72 | 5.25 | โˆ’8.2% |
28
28
 
29
- > โš ๏ธ **Sample caveats** โ€” POST window is only 2 days (157 msgs); statistical confidence is low. The work topic mix differs week to week (PRE week was video-script production with long pasted text, POST week was package release with short directives), so the tool/harness effect is not cleanly isolated. Refresh planned around **2026-05-09** once 5 more days of POST data are in.
29
+ Same request resolved in fewer round-trips โ†’ first-try success rate up. Looks like the effect of PEV + Structured Task forcing one-shot delivery.
30
+
31
+ ### Why cache hit rate isn't in this measurement โ€” Max vs Pro
32
+
33
+ **Cache hit rate improvement isn't included** in this comparison. The author is on the Max plan with a 1-hour cache TTL and stays inside the same context for the full hour, so the hit rate had already converged near ~98% with little headroom left. **Pro-plan users (5-minute TTL)** see caches expire frequently, so the harness "one-shot" pattern + a "handoff right before TTL expiry" workflow likely **lifts hit rate itself**.
34
+
35
+ ### Handoff-before-expiry workflow
36
+
37
+ Watching the TTL countdown in the statusline, the habit is now: just before expiry, run `claude-token-saver handoff` to dump current work state into a markdown brief, then start a fresh cache cycle. Same flow handles the 1M-context warning and 5H/7D cap chips.
38
+
39
+ > โš ๏ธ **Sample caveats** โ€” POST window is only 2 days (157 msgs); statistical confidence is low. The work topic mix differs week to week (PRE was video-script production with long pasted text, POST was package release with short directives), so the tool/harness effect isn't cleanly isolated. **Once 5 more days of POST data are in (around 2026-05-09)**, the same analysis will be re-run to check whether the trend stabilises, and an update will be posted.
30
40
 
31
41
  ---
32
42
 
@@ -254,6 +264,12 @@ Node.js โ‰ฅ 18 ยท macOS / Windows / Linux / WSL ยท zero dependencies.
254
264
 
255
265
  ## Release notes
256
266
 
267
+ ### v2.13.3 (2026-05-04)
268
+ - "Real-world impact" section restructured as a **harness + ratchet adoption cost-savings report**. Added Max(1h)/Pro(5m) cache TTL distinction (different hit-rate headroom), the handoff-before-expiry workflow, and the 2026-05-09 refresh promise. Chart title updated to match.
269
+
270
+ ### v2.13.2 (2026-05-04)
271
+ - YouTube channel handle corrected to `@DeepPulseKR` (package.json + both READMEs).
272
+
257
273
  ### v2.13.1 (2026-05-04)
258
274
  - README now opens with the actual statusline screenshot and a "harness 5/5 + ratchet โ€” before/after" impact chart, with daily/monthly/yearly cost-savings impact card. Author's own logs show โˆ’18.6% cost / user message, โˆ’9.2% assistant turns. Sample caveats, work-topic confound, and refresh schedule (2026-05-09) called out.
259
275
  - npm package metadata cleaned up (homepage / bugs / author) โ€” package page now links to the DeepPulse YouTube channel.
package/README.md CHANGED
@@ -13,20 +13,30 @@ Claude Code์˜ **ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰์„ ์ง„๋‹จยท์ ˆ์•ฝ**ํ•˜๋Š” CLI. ์บ์‹œ ํžˆํŠธ
13
13
 
14
14
  ๐Ÿ“บ [์ถœ์‹œ ์˜์ƒ (60์ดˆ)](https://www.youtube.com/shorts/RaD8qMsPTnA)
15
15
 
16
- ## ์‹ค์ œ ํšจ๊ณผ โ€” harness 5/5 + ratchet ์ ์šฉ ์ „ํ›„
16
+ ## ์‹ค์ œ ํšจ๊ณผ โ€” harness + ratchet ๋„์ž… ์ „ํ›„ ๋น„์šฉ ์ ˆ๊ฐ ๋ฆฌํฌํŠธ
17
17
 
18
- ![claude-token-saver ์„ค์น˜ ์ „/ํ›„ ํšจ๊ณผ](./docs/harness-impact.png)
18
+ ![claude-token-saver โ€” harness + ratchet ๋„์ž… ํšจ๊ณผ](./docs/harness-impact.png)
19
19
 
20
- ์ €์ž ๋ณธ์ธ์˜ Claude Code ์‚ฌ์šฉ ๋กœ๊ทธ๋ฅผ **์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ 1๊ฑด๋‹น**์œผ๋กœ ์ •๊ทœํ™”ํ•ด ๋น„๊ตํ•œ ๊ฒฐ๊ณผ (2026-05-02 ์ ์šฉ ์‹œ์ , Opus 4.7 ๊ฐ€๊ฒฉ ๊ธฐ์ค€):
20
+ claude-token-saver์— ์ตœ๊ทผ ์ถ”๊ฐ€๋œ **harness 5/5 + ratchet** ๊ธฐ๋Šฅ์„ ์‹ค์ œ ์ ์šฉํ•ด๋ณด๊ณ  ๋„์ž… ์ „ํ›„๋ฅผ ๋น„๊ตํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค (์ €์ž ๋ณธ์ธ์˜ Claude Code ์‚ฌ์šฉ ๋กœ๊ทธ, **์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ 1๊ฑด๋‹น**์œผ๋กœ ์ •๊ทœํ™”, 2026-05-02 ๊ธฐ์ค€, Opus 4.7 ๊ฐ€๊ฒฉ):
21
21
 
22
- | ๋ฉ”ํŠธ๋ฆญ | ์„ค์น˜ ์ „ (7์ผ / 739msg) | ์„ค์น˜ ํ›„ (2์ผ / 157msg) | ๋ณ€ํ™” |
22
+ | ๋ฉ”ํŠธ๋ฆญ | ๋„์ž… ์ „ (7์ผ / 739msg) | ๋„์ž… ํ›„ (2์ผ / 157msg) | ๋ณ€ํ™” |
23
23
  |---|---:|---:|---:|
24
24
  | ๋ฉ”์‹œ์ง€๋‹น ๋น„์šฉ | $2.345 | $1.910 | **โˆ’18.6%** |
25
25
  | ๋ฉ”์‹œ์ง€๋‹น ์ถœ๋ ฅ ํ† ํฐ | 7,391 | 6,052 | โˆ’18.1% |
26
26
  | ๋ฉ”์‹œ์ง€๋‹น assistant ์™•๋ณต | 9.73 | 8.83 | โˆ’9.2% |
27
27
  | ๋ฉ”์‹œ์ง€๋‹น ๋„๊ตฌ ํ˜ธ์ถœ | 5.72 | 5.25 | โˆ’8.2% |
28
28
 
29
- > โš ๏ธ **์ƒ˜ํ”Œ ์ฃผ์˜** โ€” POST ๋ฐ์ดํ„ฐ๋Š” 2์ผ์น˜(157 msgs)๋กœ ํ†ต๊ณ„์  ์˜๋ฏธ๊ฐ€ ์•ฝํ•˜๊ณ , ๊ทธ ์ฃผ์— ๋ฌด์Šจ ์ž‘์—…์„ ํ–ˆ๋ƒ(๊ธด ํ…์ŠคํŠธ vs ์งง์€ ์ง€์‹œ)๊ฐ€ ๊ฒฐ๊ณผ์— ์„ž์—ฌ ์žˆ์Šต๋‹ˆ๋‹ค. 5์ผ์น˜๊ฐ€ ๋” ์Œ“์ด๋Š” **2026-05-09๊ฒฝ** ๊ฐ™์€ ๋ถ„์„์„ ๋‹ค์‹œ ๋Œ๋ ค ๊ฐฑ์‹ ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.
29
+ ๊ฐ™์€ ์š”์ฒญ์„ ๋” ์ ์€ ์™•๋ณต์œผ๋กœ ๋๋‚ธ๋‹ค = ์ฒซ ์‹œ๋„ ์ ์ค‘๋ฅ  โ†‘. PEVยทStructured Task๊ฐ€ ๊ฐ•์ œ๋กœ ํ•œ ๋ฒˆ์— ๊ฐ€๊ฒŒ ๋งŒ๋“  ํšจ๊ณผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.
30
+
31
+ ### ์บ์‹œ ํžˆํŠธ์œจ์€ ์™œ ์ด๋ฒˆ ์ธก์ •์— ์—†๋Š”๊ฐ€ โ€” Pro ํ”Œ๋žœ๊ณผ์˜ ์ฐจ์ด
32
+
33
+ ์ด๋ฒˆ ๋น„๊ต์—๋Š” **์บ์‹œ ํžˆํŠธ์œจ ๊ฐœ์„ **์ด ํฌํ•จ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ €์ž๋Š” Max ํ”Œ๋žœ์ด๋ผ ์บ์‹œ TTL์ด 1์‹œ๊ฐ„์ด๊ณ , 1์‹œ๊ฐ„ ๋‚ด๋‚ด ๊ฐ™์€ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ ์ž‘์—…ํ•ด์„œ ํžˆํŠธ์œจ์ด ์ด๋ฏธ ~98%์— ์ˆ˜๋ ดํ•ด ์žˆ์–ด ์ถ”๊ฐ€ ๊ฐœ์„  ์—ฌ์ง€๊ฐ€ ์ž‘์Šต๋‹ˆ๋‹ค. **Pro ํ”Œ๋žœ ์‚ฌ์šฉ์ž(5๋ถ„ TTL)๋Š” ์บ์‹œ๊ฐ€ ์ž์ฃผ ๋งŒ๋ฃŒ๋˜๊ธฐ ๋•Œ๋ฌธ์—**, harness๊ฐ€ ๋งŒ๋“  "ํ•œ ๋ฒˆ์— ๊ฐ€๋Š”" ํŒจํ„ด + ๋งŒ๋ฃŒ ์ง์ „ handoff ์›Œํฌํ”Œ๋กœ ์กฐํ•ฉ์œผ๋กœ **ํžˆํŠธ์œจ ์ž์ฒด๊ฐ€ ์˜ฌ๋ผ๊ฐˆ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค.**
34
+
35
+ ### ๋งŒ๋ฃŒ ์ง์ „ handoff ์›Œํฌํ”Œ๋กœ
36
+
37
+ statusline์— TTL์ด ์นด์šดํŠธ๋‹ค์šด๋˜๋Š” ๊ฑธ ๋ณด๋ฉด์„œ, ๋งŒ๋ฃŒ ์ง์ „์— `claude-token-saver handoff`๋กœ ํ˜„์žฌ ์ž‘์—… ์ƒํƒœ๋ฅผ ๋งˆํฌ๋‹ค์šด์œผ๋กœ ๋ฐฑ์—…ํ•ด๋‘๊ณ  ์ƒˆ ์บ์‹œ ์‚ฌ์ดํด์„ ์‹œ์ž‘ํ•˜๋Š” ํ๋ฆ„์ด ์ž๋ฆฌ์žก์•˜์Šต๋‹ˆ๋‹ค. 1M ์ปจํ…์ŠคํŠธ ๊ฒฝ๊ณ ๋‚˜ 5H/7D cap ์นฉ์ด ๋– ๋„ ๊ฐ™์€ ํ๋ฆ„์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
38
+
39
+ > โš ๏ธ **์ƒ˜ํ”Œ ์ฃผ์˜** โ€” ๋„์ž… ํ›„ ๋ฐ์ดํ„ฐ๋Š” 2์ผ์น˜(157 msgs)๋กœ ํ†ต๊ณ„์  ์˜๋ฏธ๊ฐ€ ์•ฝํ•˜๊ณ , ๊ทธ ์ฃผ์— ๋ฌด์Šจ ์ž‘์—…์„ ํ–ˆ๋ƒ(๊ธด ํ…์ŠคํŠธ vs ์งง์€ ์ง€์‹œ)๊ฐ€ ๊ฒฐ๊ณผ์— ์„ž์—ฌ ์žˆ์–ด ๋„๊ตฌ ํšจ๊ณผ๋งŒ ๊นจ๋—์ด ๋ถ„๋ฆฌ๋˜์ง„ ์•Š์Šต๋‹ˆ๋‹ค. **์ด 7์ผ์น˜(์ถ”๊ฐ€ 5์ผ)๊ฐ€ ์Œ“์ด๋Š” 2026-05-09๊ฒฝ** ๊ฐ™์€ ๋ถ„์„์„ ๋‹ค์‹œ ๋Œ๋ ค ์ถ”์„ธ ์•ˆ์ •ํ™” ์—ฌ๋ถ€์™€ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.
30
40
 
31
41
  ---
32
42
 
@@ -214,6 +224,12 @@ Node.js โ‰ฅ 18 ยท macOS / Linux / Windows / WSL ยท ์˜์กด์„ฑ 0.
214
224
 
215
225
  ## ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ
216
226
 
227
+ ### v2.13.3 (2026-05-04)
228
+ - "์‹ค์ œ ํšจ๊ณผ" ์„น์…˜์„ **harness + ratchet ๋„์ž… ์ „ํ›„ ๋น„์šฉ ์ ˆ๊ฐ ๋ฆฌํฌํŠธ** ํ˜•ํƒœ๋กœ ์žฌ๊ตฌ์„ฑ. Max(1h)/Pro(5m) ์บ์‹œ TTL ์ฐจ์ด์— ๋”ฐ๋ฅธ ํžˆํŠธ์œจ ๊ฐœ์„  ์—ฌ์ง€ ์ฐจ์ด ์„ค๋ช…, ๋งŒ๋ฃŒ ์ง์ „ handoff ์›Œํฌํ”Œ๋กœ ์•ˆ๋‚ด, 7์ผ์น˜ ๋ˆ„์  ์‹œ์ (2026-05-09) ๊ฐฑ์‹  ์˜ˆ๊ณ  ์ถ”๊ฐ€. ์ฐจํŠธ ์ œ๋ชฉ๋„ ๋™์ผํ•˜๊ฒŒ ๊ฐฑ์‹ .
229
+
230
+ ### v2.13.2 (2026-05-04)
231
+ - YouTube ์ฑ„๋„ ํ•ธ๋“ค `@DeepPulseKR`๋กœ ์ •์ • (package.json + ๋‘ README ์ผ๊ด„).
232
+
217
233
  ### v2.13.1 (2026-05-04)
218
234
  - README์— ์‹ค์ œ statusline ์Šคํฌ๋ฆฐ์ƒท๊ณผ "harness 5/5 + ratchet ์ ์šฉ ์ „ํ›„ ํšจ๊ณผ" ์ฐจํŠธ ์ถ”๊ฐ€. ์ž์ฒด ์‚ฌ์šฉ ๋กœ๊ทธ ๊ธฐ์ค€ ๋ฉ”์‹œ์ง€๋‹น ๋น„์šฉ โˆ’18.6%, assistant ์™•๋ณต โˆ’9.2%, ์ผ/์›”/๋…„ ํ™˜์‚ฐ ๋น„์šฉ ์ ˆ๊ฐ ์ž„ํŒฉํŠธ ์นด๋“œ ํฌํ•จ. ์ƒ˜ํ”Œ ์ฃผ์˜์‚ฌํ•ญยท์ž‘์—… ํ† ํ”ฝ ๋ณ€์ˆ˜ยท๊ฐฑ์‹  ์ผ์ •(2026-05-09) ๋ช…์‹œ.
219
235
  - npm ํŒจํ‚ค์ง€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(homepage / bugs / author) ์ •๋น„ โ€” DeepPulse YouTube ์ฑ„๋„ ๋งํฌ ๋…ธ์ถœ.
package/bin/cli.js CHANGED
@@ -488,10 +488,35 @@ async function main() {
488
488
  }
489
489
 
490
490
  if (sub === 'promote') {
491
- const raw = args.slice(2).join(' ').trim();
491
+ // Parse scope flags before stripping. Accepts: --global, --project,
492
+ // --scope=global|project, --scope global|project
493
+ const promoteArgs = args.slice(2);
494
+ let scope = null;
495
+ const scopeFlags = new Set();
496
+ for (let i = 0; i < promoteArgs.length; i++) {
497
+ const a = promoteArgs[i];
498
+ if (a === '--global') { scope = 'global'; scopeFlags.add(i); }
499
+ else if (a === '--project') { scope = 'project'; scopeFlags.add(i); }
500
+ else if (a === '--scope' && promoteArgs[i + 1]) {
501
+ const v = promoteArgs[i + 1];
502
+ if (v !== 'global' && v !== 'project') {
503
+ console.error(`Invalid --scope value: ${v} (expected "global" or "project")`);
504
+ process.exit(1);
505
+ }
506
+ scope = v; scopeFlags.add(i); scopeFlags.add(i + 1); i++;
507
+ } else if (a.startsWith('--scope=')) {
508
+ const v = a.slice('--scope='.length);
509
+ if (v !== 'global' && v !== 'project') {
510
+ console.error(`Invalid --scope value: ${v} (expected "global" or "project")`);
511
+ process.exit(1);
512
+ }
513
+ scope = v; scopeFlags.add(i);
514
+ }
515
+ }
516
+ const raw = promoteArgs.filter((_, i) => !scopeFlags.has(i)).join(' ').trim();
492
517
  if (!raw) {
493
- console.error('Usage: claude-token-saver harness promote <N> # from statusline ๐Ÿ…ทโš  ratchet? #N');
494
- console.error(' or: claude-token-saver harness promote "<rule text>"');
518
+ console.error('Usage: claude-token-saver harness promote [--global|--project] <N> # from statusline ๐Ÿ…ทโš  ratchet? #N');
519
+ console.error(' or: claude-token-saver harness promote [--global|--project] "<rule text>"');
495
520
  process.exit(1);
496
521
  }
497
522
  let rule = raw;
@@ -515,8 +540,33 @@ async function main() {
515
540
  }
516
541
  rule = `๋ฐ˜๋ณต ๊ฐ์ง€ ร—${cand.count}: ${cand.pattern} โ€” TODO: ์›์ธยท์˜ˆ๋ฐฉ์ฑ… ํ•œ ์ค„๋กœ`;
517
542
  }
518
- const r = harnessPromote(rule);
519
- console.log(`Appended to ${r.path}:`);
543
+ // Scope resolution: explicit flag wins. Otherwise prompt interactively
544
+ // when running on a TTY; in non-TTY (CI/scripts) require an explicit
545
+ // flag so the choice is never silently made for the caller.
546
+ if (!scope) {
547
+ if (process.stdin.isTTY && process.stdout.isTTY) {
548
+ const readline = await import('node:readline');
549
+ const { homedir: hd } = await import('node:os');
550
+ const { findProjectRoot: fpr } = await import('../src/harness.js');
551
+ const projPath = `${fpr()}/.claude/ratchet.md`;
552
+ const globPath = `${hd()}/.claude/ratchet.md`;
553
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
554
+ const ask = (q) => new Promise((res) => rl.question(q, res));
555
+ console.log('Where should this rule live?');
556
+ console.log(` [1] project (${projPath})`);
557
+ console.log(` [2] global (${globPath})`);
558
+ const ans = (await ask('Choose [1/2] (default 1): ')).trim();
559
+ rl.close();
560
+ scope = (ans === '2' || ans.toLowerCase() === 'global' || ans.toLowerCase() === 'g')
561
+ ? 'global' : 'project';
562
+ } else {
563
+ console.error('Scope required in non-interactive mode.');
564
+ console.error('Pass --project or --global (or --scope=project|global).');
565
+ process.exit(1);
566
+ }
567
+ }
568
+ const r = harnessPromote(rule, { scope });
569
+ console.log(`Appended to ${r.path} [${r.scope}]:`);
520
570
  console.log(` - ${rule}`);
521
571
  if (/^\d+$/.test(raw)) {
522
572
  console.log('\n๐Ÿ‘‰ ratchet.md๋ฅผ ์—ด์–ด TODO ๋ถ€๋ถ„์„ ์‹ค์ œ ๋ฃฐ๋กœ ๋‹ค๋“ฌ์–ด์ฃผ์„ธ์š”.');
@@ -570,21 +620,30 @@ async function main() {
570
620
  }
571
621
 
572
622
  if (sub === 'list' || sub === 'ls') {
573
- const { path, rules } = harnessListRules();
574
- if (!rules.length) {
575
- console.log(`No ratchet rules in ${path}`);
576
- return;
577
- }
578
- console.log(`๐Ÿ“‹ Ratchet rules โ€” ${path}\n`);
579
- for (const r of rules) console.log(` #${r.index} ${r.text}`);
580
- console.log('\nRemove with: claude-token-saver harness rm <N>');
623
+ const wantGlobal = hasFlag('--global');
624
+ const wantProject = hasFlag('--project') || !wantGlobal;
625
+ const print = (scope) => {
626
+ const { path, rules } = harnessListRules({ scope });
627
+ if (!rules.length) {
628
+ console.log(`No ratchet rules in ${path} [${scope}]`);
629
+ return;
630
+ }
631
+ console.log(`๐Ÿ“‹ Ratchet rules [${scope}] โ€” ${path}\n`);
632
+ for (const r of rules) console.log(` #${r.index} ${r.text}`);
633
+ console.log('');
634
+ };
635
+ if (wantProject) print('project');
636
+ if (wantGlobal) print('global');
637
+ console.log('Remove with: claude-token-saver harness rm [--global|--project] <N>');
581
638
  return;
582
639
  }
583
640
 
584
641
  if (sub === 'rm') {
585
- const raw = (args[2] || '').trim();
642
+ const rmScope = hasFlag('--global') ? 'global' : 'project';
643
+ const rmArgs = args.slice(2).filter((a) => a !== '--global' && a !== '--project');
644
+ const raw = (rmArgs[0] || '').trim();
586
645
  if (!/^\d+$/.test(raw)) {
587
- console.error('Usage: claude-token-saver harness rm <N> # N from `harness list`');
646
+ console.error('Usage: claude-token-saver harness rm [--global|--project] <N> # N from `harness list`');
588
647
  process.exit(1);
589
648
  }
590
649
  const n = parseInt(raw, 10);
@@ -598,7 +657,7 @@ async function main() {
598
657
  console.log(' - ๋ฃฐ์ด ๋„ˆ๋ฌด ์ข์•„์„œ ๊ฑฐ์˜ ๋ฐœ๋™ ์•ˆ ๋˜๋‚˜? โ†’ ๊ทธ๋ƒฅ ๋‘๊ธฐ (๋น„์šฉ 0)');
599
658
  console.log(' - ์ •๋ง ์ž˜๋ชป๋œ ๋ฃฐ์ด๋ผ ํ™•์‹ ? โ†’ ๊ทธ๋•Œ๋งŒ ์‚ญ์ œ');
600
659
  console.log('');
601
- const r = harnessRmRule(n);
660
+ const r = harnessRmRule(n, { scope: rmScope });
602
661
  if (!r.ok) {
603
662
  console.error(`โŒ ${r.error}`);
604
663
  if (r.rules) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "2.13.2",
3
+ "version": "2.14.0",
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/harness.js CHANGED
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
11
11
  import { join, resolve, dirname } from 'node:path';
12
+ import { homedir } from 'node:os';
12
13
  import { createRequire } from 'node:module';
13
14
  import {
14
15
  HARNESS_SECTIONS,
@@ -58,6 +59,14 @@ function ratchetMdPath(root) {
58
59
  return join(root, '.claude', 'ratchet.md');
59
60
  }
60
61
 
62
+ function globalRatchetMdPath() {
63
+ return join(homedir(), '.claude', 'ratchet.md');
64
+ }
65
+
66
+ function resolveRatchetPath(scope, root) {
67
+ return scope === 'global' ? globalRatchetMdPath() : ratchetMdPath(root);
68
+ }
69
+
61
70
  /**
62
71
  * Count how many of the 5 harness sections appear in the project's CLAUDE.md.
63
72
  * Returns { configured, total, missing, hasBlock }. Cheap enough to call from
@@ -217,8 +226,8 @@ export function harnessUninit({ root = findProjectRoot(), purgeRatchet = false }
217
226
  * harness promote โ€” append a one-line rule to .claude/ratchet.md.
218
227
  * Creates the file from the initial template if missing.
219
228
  */
220
- export function harnessPromote(ruleText, { root = findProjectRoot() } = {}) {
221
- const rmPath = ratchetMdPath(root);
229
+ export function harnessPromote(ruleText, { root = findProjectRoot(), scope = 'project' } = {}) {
230
+ const rmPath = resolveRatchetPath(scope, root);
222
231
  let existing = '';
223
232
  if (existsSync(rmPath)) {
224
233
  existing = readFileSync(rmPath, 'utf8');
@@ -228,15 +237,15 @@ export function harnessPromote(ruleText, { root = findProjectRoot() } = {}) {
228
237
  }
229
238
  const next = appendRatchetRule(existing, ruleText);
230
239
  writeFileSync(rmPath, next);
231
- return { path: rmPath, root };
240
+ return { path: rmPath, root, scope };
232
241
  }
233
242
 
234
243
  /**
235
244
  * harness list โ€” return numbered ratchet rules from .claude/ratchet.md.
236
245
  * Numbering is 1-based and matches `harness rm <N>`.
237
246
  */
238
- export function harnessListRules({ root = findProjectRoot() } = {}) {
239
- const rmPath = ratchetMdPath(root);
247
+ export function harnessListRules({ root = findProjectRoot(), scope = 'project' } = {}) {
248
+ const rmPath = resolveRatchetPath(scope, root);
240
249
  if (!existsSync(rmPath)) return { path: rmPath, rules: [] };
241
250
  const lines = readFileSync(rmPath, 'utf8').split('\n');
242
251
  const rules = [];
@@ -260,8 +269,8 @@ export function harnessListRules({ root = findProjectRoot() } = {}) {
260
269
  * value is one-way accumulation; deleting should feel deliberate. The CLI
261
270
  * surfaces a "narrow the condition instead" reminder around this call.
262
271
  */
263
- export function harnessRmRule(n, { root = findProjectRoot() } = {}) {
264
- const { path: rmPath, rules } = harnessListRules({ root });
272
+ export function harnessRmRule(n, { root = findProjectRoot(), scope = 'project' } = {}) {
273
+ const { path: rmPath, rules } = harnessListRules({ root, scope });
265
274
  if (!existsSync(rmPath)) {
266
275
  return { ok: false, error: `ratchet.md not found at ${rmPath}` };
267
276
  }