claude-token-saver 2.14.2 → 2.15.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
@@ -1,6 +1,8 @@
1
1
  [한국어](./README.md) · **English**
2
2
 
3
3
  [![DeepPulse YouTube](https://img.shields.io/badge/YouTube-@DeepPulseKR-FF0000?logo=youtube&logoColor=white)](https://www.youtube.com/@DeepPulseKR)
4
+ [![DeepPulseEN YouTube](https://img.shields.io/badge/YouTube-@DeepPulseEN-FF0000?logo=youtube&logoColor=white)](https://www.youtube.com/@DeepPulseEN)
5
+ [![Homepage](https://img.shields.io/badge/Homepage-rootstudioyaml.github.io-2ea44f)](https://rootstudioyaml.github.io/)
4
6
  [![npm](https://img.shields.io/npm/v/claude-token-saver.svg)](https://www.npmjs.com/package/claude-token-saver)
5
7
 
6
8
  # claude-token-saver
@@ -256,7 +258,9 @@ Node.js ≥ 18 · macOS / Windows / Linux / WSL · zero dependencies.
256
258
 
257
259
  - [GitHub Issue #46829](https://github.com/anthropics/claude-code/issues/46829) — cache TTL regression
258
260
  - [HN discussion](https://news.ycombinator.com/item?id=47736476) — 168 points, 142 comments
259
- - [HNPulse KR](https://www.youtube.com/@DeepPulseKR) — Korean HN tech deep-dives
261
+ - [DeepPulse KR](https://www.youtube.com/@DeepPulseKR) — Korean HN tech deep-dives
262
+ - [DeepPulse EN](https://www.youtube.com/@DeepPulseEN) — English HN tech deep-dives
263
+ - [Homepage](https://rootstudioyaml.github.io/) — project site
260
264
 
261
265
  ## Known environment quirks
262
266
 
@@ -264,6 +268,11 @@ Node.js ≥ 18 · macOS / Windows / Linux / WSL · zero dependencies.
264
268
 
265
269
  ## Release notes
266
270
 
271
+ ### v2.15.0 (2026-06-13)
272
+ - **Global harness init** — `harness init`/`uninit`/`check` gain the same scope concept ratchet already had. `harness init --global` installs the 5 sections into `~/.claude/CLAUDE.md` (+ `~/.claude/ratchet.md`) once, so they **apply to every project**. The no-flag default stays `project` (back-compat).
273
+ - `harness check` now treats global as a **fallback** — a project with no local block but a global harness installed reports `🅷 5/5 (covered by global)`, matching how Claude Code loads the global `CLAUDE.md` for every project. Use `--project`/`--global` to inspect a single scope.
274
+ - npm package homepage changed to `https://rootstudioyaml.github.io/`; README adds the **@DeepPulseEN** channel and a homepage badge.
275
+
267
276
  ### v2.13.3 (2026-05-04)
268
277
  - "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
278
 
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  **한국어** · [English](./README.en.md)
2
2
 
3
3
  [![DeepPulse YouTube](https://img.shields.io/badge/YouTube-@DeepPulseKR-FF0000?logo=youtube&logoColor=white)](https://www.youtube.com/@DeepPulseKR)
4
+ [![DeepPulseEN YouTube](https://img.shields.io/badge/YouTube-@DeepPulseEN-FF0000?logo=youtube&logoColor=white)](https://www.youtube.com/@DeepPulseEN)
5
+ [![Homepage](https://img.shields.io/badge/Homepage-rootstudioyaml.github.io-2ea44f)](https://rootstudioyaml.github.io/)
4
6
  [![npm](https://img.shields.io/npm/v/claude-token-saver.svg)](https://www.npmjs.com/package/claude-token-saver)
5
7
 
6
8
  # claude-token-saver
@@ -224,6 +226,11 @@ Node.js ≥ 18 · macOS / Linux / Windows / WSL · 의존성 0.
224
226
 
225
227
  ## 릴리스 노트
226
228
 
229
+ ### v2.15.0 (2026-06-13)
230
+ - **글로벌 harness init** — `harness init`/`uninit`/`check`에 ratchet과 동일한 스코프 개념 도입. `harness init --global`이 `~/.claude/CLAUDE.md`(+ `~/.claude/ratchet.md`)에 5개 섹션을 한 번에 깔아 **모든 프로젝트에 적용**됩니다. 무플래그 기본값은 종전대로 `project`(하위호환).
231
+ - `harness check`는 이제 글로벌을 **fallback**으로 인정 — 로컬 블록이 없어도 글로벌 harness가 깔려 있으면 `🅷 5/5 (covered by global)`로 표시(Claude Code가 전역 `CLAUDE.md`를 모든 프로젝트에 로드하는 실제 동작과 일치). `--project`/`--global`로 특정 스코프만 조회 가능.
232
+ - npm 패키지 homepage를 `https://rootstudioyaml.github.io/`로 변경, README에 **@DeepPulseEN** 채널·홈페이지 배지 추가.
233
+
227
234
  ### v2.13.3 (2026-05-04)
228
235
  - "실제 효과" 섹션을 **harness + ratchet 도입 전후 비용 절감 리포트** 형태로 재구성. Max(1h)/Pro(5m) 캐시 TTL 차이에 따른 히트율 개선 여지 차이 설명, 만료 직전 handoff 워크플로 안내, 7일치 누적 시점(2026-05-09) 갱신 예고 추가. 차트 제목도 동일하게 갱신.
229
236
 
package/bin/cli.js CHANGED
@@ -452,24 +452,44 @@ async function main() {
452
452
  // claude-token-saver harness off | on # toggle the statusline 🅷 segment
453
453
  if (args[0] === 'harness') {
454
454
  const sub = args[1];
455
+ // Scope flags for init/uninit/check (same convention as promote/list/rm):
456
+ // --global | --project | --scope=global|project | --scope global|project
457
+ const parseHarnessScope = (argv, dflt) => {
458
+ for (let i = 0; i < argv.length; i++) {
459
+ const a = argv[i];
460
+ if (a === '--global') return 'global';
461
+ if (a === '--project') return 'project';
462
+ if (a === '--scope' && (argv[i + 1] === 'global' || argv[i + 1] === 'project')) return argv[i + 1];
463
+ if (a.startsWith('--scope=')) {
464
+ const v = a.slice('--scope='.length);
465
+ if (v === 'global' || v === 'project') return v;
466
+ }
467
+ }
468
+ return dflt;
469
+ };
455
470
  const { harnessInit, harnessUninit, harnessStatus, harnessPromote, harnessListRules, harnessRmRule, findProjectRoot } =
456
471
  await import('../src/harness.js');
457
472
  const { HARNESS_SECTIONS } = await import('../src/harness-templates.js');
458
473
  const { loadConfig, saveConfig } = await import('../src/config.js');
459
474
 
460
475
  if (!sub || sub === 'check') {
476
+ const scope = parseHarnessScope(args.slice(2), 'auto'); // auto = project, else global fallback
461
477
  const root = findProjectRoot();
462
- const s = harnessStatus(root);
463
- console.log(`🅷 ${s.configured}/${s.total} ${root}`);
478
+ const s = harnessStatus(root, { scope });
479
+ // Only call out "covered by global" when we *fell back* to it (auto), not
480
+ // when the user explicitly asked for the global scope.
481
+ const via = (scope === 'auto' && s.source === 'global') ? ' (covered by global ~/.claude/CLAUDE.md)' : '';
482
+ console.log(`🅷 ${s.configured}/${s.total} — ${s.file}${via}`);
464
483
  console.log(`CLAUDE.md: ${s.hasFile ? 'present' : 'missing'}` +
465
- (s.hasFile ? `, harness block: ${s.hasBlock ? 'yes' : 'no'}` : ''));
484
+ (s.hasFile ? `, harness block: ${s.hasBlock ? 'yes' : 'no'}` : '') + ` [${s.source}]`);
466
485
  if (s.missing.length) {
467
486
  console.log('Missing sections:');
468
487
  for (const id of s.missing) {
469
488
  const sec = HARNESS_SECTIONS.find((x) => x.id === id);
470
489
  console.log(` - ${id}: ${sec ? sec.heading.replace(/^#+\s*/, '') : ''}`);
471
490
  }
472
- console.log('\nRun: claude-token-saver harness init');
491
+ console.log('\nRun: claude-token-saver harness init (this project)');
492
+ console.log(' or: claude-token-saver harness init --global (all projects, ~/.claude/CLAUDE.md)');
473
493
  } else {
474
494
  console.log('All 5 harness sections present. ✅');
475
495
  }
@@ -477,9 +497,10 @@ async function main() {
477
497
  }
478
498
 
479
499
  if (sub === 'init') {
500
+ const scope = parseHarnessScope(args.slice(2), 'project'); // default project (back-compat)
480
501
  const force = hasFlag('--force');
481
- const r = harnessInit({ force });
482
- console.log(`Project root: ${r.root}`);
502
+ const r = harnessInit({ force, scope });
503
+ console.log(`Scope: ${scope}${scope === 'global' ? ' (~/.claude/CLAUDE.md — applies to all projects)' : ` (${r.root})`}`);
483
504
  for (const p of r.backedUp) console.log(`Backed up: ${p}`);
484
505
  for (const p of r.wrote) console.log(`Wrote: ${p}`);
485
506
  for (const p of r.skipped) console.log(`Skipped: ${p}`);
@@ -609,9 +630,10 @@ async function main() {
609
630
  }
610
631
 
611
632
  if (sub === 'uninit' || sub === 'remove') {
633
+ const scope = parseHarnessScope(args.slice(2), 'project');
612
634
  const purgeRatchet = args.includes('--purge-ratchet');
613
- const r = harnessUninit({ purgeRatchet });
614
- console.log(`Project root: ${r.root}`);
635
+ const r = harnessUninit({ purgeRatchet, scope });
636
+ console.log(`Scope: ${scope}${scope === 'global' ? ' (~/.claude/CLAUDE.md)' : ` (${r.root})`}`);
615
637
  r.removed.forEach((f) => console.log(` removed: ${f}`));
616
638
  r.backedUp.forEach((f) => console.log(` backup: ${f}`));
617
639
  r.skipped.forEach((f) => console.log(` skip: ${f}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "2.14.2",
3
+ "version": "2.15.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": {
@@ -37,7 +37,7 @@
37
37
  "type": "git",
38
38
  "url": "git+https://github.com/rootstudioyaml/claude-token-saver.git"
39
39
  },
40
- "homepage": "https://www.youtube.com/@DeepPulseKR",
40
+ "homepage": "https://rootstudioyaml.github.io/",
41
41
  "bugs": {
42
42
  "url": "https://github.com/rootstudioyaml/claude-token-saver/issues"
43
43
  },
package/src/harness.js CHANGED
@@ -67,28 +67,41 @@ function resolveRatchetPath(scope, root) {
67
67
  return scope === 'global' ? globalRatchetMdPath() : ratchetMdPath(root);
68
68
  }
69
69
 
70
+ // Global harness lives in ~/.claude/CLAUDE.md — Claude Code loads this for every
71
+ // project, so a global init makes the 5 harness sections apply everywhere
72
+ // (mirrors the project/global split that ratchet.md already has).
73
+ function globalClaudeMdPath() {
74
+ return join(homedir(), '.claude', 'CLAUDE.md');
75
+ }
76
+
77
+ function resolveClaudeMdPath(scope, root) {
78
+ return scope === 'global' ? globalClaudeMdPath() : claudeMdPath(root);
79
+ }
80
+
70
81
  /**
71
82
  * Count how many of the 5 harness sections appear in the project's CLAUDE.md.
72
83
  * Returns { configured, total, missing, hasBlock }. Cheap enough to call from
73
84
  * statusline — single file read + regex.
74
85
  */
75
- export function harnessStatus(root = findProjectRoot()) {
76
- const path = claudeMdPath(root);
77
- if (!existsSync(path)) {
86
+ // Count harness sections in a single CLAUDE.md file. Shared by both scopes.
87
+ function statusForFile(filePath) {
88
+ if (!existsSync(filePath)) {
78
89
  return {
79
90
  configured: 0,
80
91
  total: HARNESS_SECTIONS.length,
81
92
  missing: HARNESS_SECTIONS.map((s) => s.id),
82
93
  hasBlock: false,
83
94
  hasFile: false,
84
- root,
95
+ optOut: false,
96
+ custom: false,
97
+ file: filePath,
85
98
  };
86
99
  }
87
100
  let content = '';
88
101
  try {
89
- content = readFileSync(path, 'utf8');
102
+ content = readFileSync(filePath, 'utf8');
90
103
  } catch {
91
- return { configured: 0, total: HARNESS_SECTIONS.length, missing: [], hasBlock: false, hasFile: true, root };
104
+ return { configured: 0, total: HARNESS_SECTIONS.length, missing: [], hasBlock: false, hasFile: true, optOut: false, custom: false, file: filePath };
92
105
  }
93
106
  const hasBlock = content.includes(HARNESS_BLOCK_BEGIN);
94
107
  // Opt-out marker — when the user intentionally customizes the harness block
@@ -114,10 +127,30 @@ export function harnessStatus(root = findProjectRoot()) {
114
127
  hasFile: true,
115
128
  optOut,
116
129
  custom,
117
- root,
130
+ file: filePath,
118
131
  };
119
132
  }
120
133
 
134
+ /**
135
+ * Harness status for a project, with scope control:
136
+ * scope 'project' — count only <root>/CLAUDE.md
137
+ * scope 'global' — count only ~/.claude/CLAUDE.md
138
+ * scope 'auto' (default) — use the project file if it carries the harness
139
+ * block, otherwise fall back to the global file. This makes a project that
140
+ * relies on a globally-installed harness report 🅷 5/5 (covered by global),
141
+ * matching reality: Claude Code loads ~/.claude/CLAUDE.md for every project.
142
+ * The returned `source` ('project'|'global') tells callers which file was used.
143
+ */
144
+ export function harnessStatus(root = findProjectRoot(), { scope = 'auto' } = {}) {
145
+ if (scope === 'project') return { ...statusForFile(claudeMdPath(root)), root, source: 'project' };
146
+ if (scope === 'global') return { ...statusForFile(globalClaudeMdPath()), root, source: 'global' };
147
+ const project = statusForFile(claudeMdPath(root));
148
+ if (project.hasBlock) return { ...project, root, source: 'project' };
149
+ const global = statusForFile(globalClaudeMdPath());
150
+ if (global.hasBlock) return { ...global, root, source: 'global' };
151
+ return { ...project, root, source: 'project' };
152
+ }
153
+
121
154
  /**
122
155
  * harness init — write CLAUDE.md (single file, 5 sections) + .claude/ratchet.md.
123
156
  * If CLAUDE.md exists, back it up to CLAUDE.md.bak-YYYYMMDD-HHMMSS first
@@ -125,10 +158,10 @@ export function harnessStatus(root = findProjectRoot()) {
125
158
  *
126
159
  * Returns { wrote: [], backedUp: [], skipped: [] } so the CLI can report.
127
160
  */
128
- export function harnessInit({ root = findProjectRoot(), force = false } = {}) {
129
- const cmPath = claudeMdPath(root);
130
- const rmPath = ratchetMdPath(root);
131
- const result = { wrote: [], backedUp: [], skipped: [], root };
161
+ export function harnessInit({ root = findProjectRoot(), force = false, scope = 'project' } = {}) {
162
+ const cmPath = resolveClaudeMdPath(scope, root); // global → ~/.claude/CLAUDE.md
163
+ const rmPath = resolveRatchetPath(scope, root); // global → ~/.claude/ratchet.md
164
+ const result = { wrote: [], backedUp: [], skipped: [], root, scope };
132
165
 
133
166
  // CLAUDE.md
134
167
  const block = harnessClaudeMdBlock();
@@ -158,11 +191,12 @@ export function harnessInit({ root = findProjectRoot(), force = false } = {}) {
158
191
  result.wrote.push(cmPath + ' (harness block appended)');
159
192
  }
160
193
  } else {
194
+ mkdirSync(dirname(cmPath), { recursive: true }); // global: ensure ~/.claude exists
161
195
  writeFileSync(cmPath, block);
162
196
  result.wrote.push(cmPath);
163
197
  }
164
198
 
165
- // .claude/ratchet.md (only if missing — don't clobber user-grown rules)
199
+ // ratchet.md (only if missing — don't clobber user-grown rules)
166
200
  if (!existsSync(rmPath)) {
167
201
  mkdirSync(dirname(rmPath), { recursive: true });
168
202
  writeFileSync(rmPath, harnessRatchetMdInitial());
@@ -181,10 +215,10 @@ export function harnessInit({ root = findProjectRoot(), force = false } = {}) {
181
215
  *
182
216
  * Returns { removed: [], backedUp: [], skipped: [] }.
183
217
  */
184
- export function harnessUninit({ root = findProjectRoot(), purgeRatchet = false } = {}) {
185
- const cmPath = claudeMdPath(root);
186
- const rmPath = ratchetMdPath(root);
187
- const result = { removed: [], backedUp: [], skipped: [], root };
218
+ export function harnessUninit({ root = findProjectRoot(), purgeRatchet = false, scope = 'project' } = {}) {
219
+ const cmPath = resolveClaudeMdPath(scope, root);
220
+ const rmPath = resolveRatchetPath(scope, root);
221
+ const result = { removed: [], backedUp: [], skipped: [], root, scope };
188
222
 
189
223
  if (existsSync(cmPath)) {
190
224
  const existing = readFileSync(cmPath, 'utf8');