claude-token-saver 2.10.2 → 2.11.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.md CHANGED
@@ -134,6 +134,31 @@ claude-token-saver mode # 현재 설정 확인
134
134
 
135
135
  전체 옵션은 `--help` 또는 [영문 README](./README.en.md#options).
136
136
 
137
+ ## 🅷 Harness 모드
138
+
139
+ 다섯 가지 원칙(Ratchet, Evidence, PEV, Structured Task, Default Safe Path)을 한 줄 명령으로 `CLAUDE.md`에 셋업하고, statusline에 `🅷 5/5`로 점수화합니다. 같은 에러가 반복되면 `🅷⚠ ratchet?`로 알림이 뜹니다.
140
+
141
+ ```bash
142
+ claude-token-saver harness init # CLAUDE.md(5섹션) + .claude/ratchet.md
143
+ claude-token-saver harness check # 현재 점수
144
+ claude-token-saver harness promote <N> # statusline 경고 #N → ratchet에 한 줄 등록
145
+ claude-token-saver harness list # 등록된 ratchet 룰 번호 매겨 보기
146
+ claude-token-saver harness rm <N> # 룰 삭제 (자동 .bak 백업)
147
+ claude-token-saver harness uninit # harness 블록 제거 (CLAUDE.md 다른 내용은 보존)
148
+ claude-token-saver harness off | on # statusline 🅷 표시 토글
149
+ ```
150
+
151
+ ### ⚠️ 주의 — `harness rm`은 신중하게
152
+
153
+ ratchet의 가치는 **"한 방향 누적"**에 있습니다. 룰을 가볍게 지우기 시작하면 같은 실수가 다시 새기 시작합니다. **지우기 전에 다음을 확인하세요**:
154
+
155
+ - **룰이 너무 광범위해서 정상 케이스도 막나?** → ❌ 삭제 ✅ **조건을 좁혀서 다듬기**
156
+ - 예: `"하드코딩 금지"` → `"테스트 외 코드에서 하드코딩 금지"`
157
+ - **룰이 너무 좁아 거의 발동 안 되나?** → ❌ 삭제 ✅ **그냥 두기** (비용 0)
158
+ - **정말 잘못된 룰이라 확신?** → ✅ **그때만 삭제**
159
+
160
+ 대부분의 "과도한 ratchet" 문제는 **룰의 표현이 좁지 못해서** 생깁니다. 삭제는 마지막 수단으로 두고, 먼저 `.claude/ratchet.md`를 직접 열어 조건을 다듬는 쪽을 우선하세요. 삭제 시 자동 `.bak`이 남지만, **세션 컨텍스트(왜 그 룰이 박혔는지)는 백업으로 복원되지 않습니다**.
161
+
137
162
  ## 토큰 급증 원인 코드
138
163
 
139
164
  | 코드 | 의미 |
@@ -169,6 +194,9 @@ Node.js ≥ 18 · macOS / Linux / Windows / WSL · 의존성 0.
169
194
 
170
195
  ## 릴리스 노트
171
196
 
197
+ ### v2.11.0 (2026-05-02)
198
+ - `harness list` / `harness rm <N>` 추가. 등록된 ratchet 룰을 번호로 보고 개별 삭제 가능 (자동 `.bak` 백업). 삭제 전 "조건을 좁혀서 다듬기" 우선 검토 안내가 CLI에 표시됩니다. README의 [⚠️ 주의 — `harness rm`은 신중하게](#️-주의--harness-rm은-신중하게) 항목 참고.
199
+
172
200
  ### v2.9.4 (2026-04-27)
173
201
  - README에 Node.js 사전 설치 안내 추가 (macOS/Windows/Linux별). GitHub에서 처음 본 사용자가 npm 명령부터 막히는 일을 방지. sudo 글로벌 설치 시 postinstall이 root 홈에 SKILL을 만드는 함정도 함께 안내.
174
202
 
package/bin/cli.js CHANGED
@@ -452,7 +452,7 @@ 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
- const { harnessInit, harnessUninit, harnessStatus, harnessPromote, findProjectRoot } =
455
+ const { harnessInit, harnessUninit, harnessStatus, harnessPromote, harnessListRules, harnessRmRule, findProjectRoot } =
456
456
  await import('../src/harness.js');
457
457
  const { HARNESS_SECTIONS } = await import('../src/harness-templates.js');
458
458
  const { loadConfig, saveConfig } = await import('../src/config.js');
@@ -569,6 +569,50 @@ async function main() {
569
569
  return;
570
570
  }
571
571
 
572
+ 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>');
581
+ return;
582
+ }
583
+
584
+ if (sub === 'rm') {
585
+ const raw = (args[2] || '').trim();
586
+ if (!/^\d+$/.test(raw)) {
587
+ console.error('Usage: claude-token-saver harness rm <N> # N from `harness list`');
588
+ process.exit(1);
589
+ }
590
+ const n = parseInt(raw, 10);
591
+ // ⚠️ Heads-up before deletion. Ratchet's value is one-way accumulation —
592
+ // dropping a rule is sometimes right, but more often the rule is just
593
+ // too narrow. Surface the alternative loudly here.
594
+ console.log('⚠️ 주의: ratchet 룰 삭제는 신중하게.');
595
+ console.log(' 같은 실수가 또 발생할 가능성이 큽니다. 보통은 "조건이 너무 좁아서"');
596
+ console.log(' 문제가 되는 경우가 많아요. 지우기 전에 한 번 더 검토하세요:');
597
+ console.log(' - 룰이 너무 광범위해서 정상 케이스도 막나? → 조건을 좁혀서 다듬기');
598
+ console.log(' - 룰이 너무 좁아서 거의 발동 안 되나? → 그냥 두기 (비용 0)');
599
+ console.log(' - 정말 잘못된 룰이라 확신? → 그때만 삭제');
600
+ console.log('');
601
+ const r = harnessRmRule(n);
602
+ if (!r.ok) {
603
+ console.error(`❌ ${r.error}`);
604
+ if (r.rules) {
605
+ console.error('Available:');
606
+ for (const x of r.rules) console.error(` #${x.index} ${x.text}`);
607
+ }
608
+ process.exit(1);
609
+ }
610
+ console.log(`✅ Removed #${n}: ${r.removed.text}`);
611
+ console.log(` Backup: ${r.backup}`);
612
+ console.log(` 복구: cp "${r.backup}" "${r.path}"`);
613
+ return;
614
+ }
615
+
572
616
  if (sub === 'off' || sub === 'on') {
573
617
  const cfg = loadConfig();
574
618
  cfg.harness = cfg.harness || {};
@@ -579,7 +623,7 @@ async function main() {
579
623
  }
580
624
 
581
625
  console.error(`Unknown harness subcommand: ${sub}`);
582
- console.error('Usage: claude-token-saver harness [check|init|uninit [--purge-ratchet]|promote "<rule>"|off|on]');
626
+ console.error('Usage: claude-token-saver harness [check|init|uninit [--purge-ratchet]|promote "<rule>"|list|rm <N>|off|on]');
583
627
  process.exit(1);
584
628
  }
585
629
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "2.10.2",
3
+ "version": "2.11.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
@@ -220,6 +220,52 @@ export function harnessPromote(ruleText, { root = findProjectRoot() } = {}) {
220
220
  return { path: rmPath, root };
221
221
  }
222
222
 
223
+ /**
224
+ * harness list — return numbered ratchet rules from .claude/ratchet.md.
225
+ * Numbering is 1-based and matches `harness rm <N>`.
226
+ */
227
+ export function harnessListRules({ root = findProjectRoot() } = {}) {
228
+ const rmPath = ratchetMdPath(root);
229
+ if (!existsSync(rmPath)) return { path: rmPath, rules: [] };
230
+ const lines = readFileSync(rmPath, 'utf8').split('\n');
231
+ const rules = [];
232
+ for (let i = 0; i < lines.length; i++) {
233
+ const line = lines[i];
234
+ // A "rule line" starts with "- " (markdown bullet). Header lines, blanks,
235
+ // and the "## Rules" anchor are ignored.
236
+ if (/^\s*-\s+/.test(line)) {
237
+ rules.push({ index: rules.length + 1, lineNo: i, text: line.replace(/^\s*-\s+/, '') });
238
+ }
239
+ }
240
+ return { path: rmPath, rules };
241
+ }
242
+
243
+ /**
244
+ * harness rm — remove a ratchet rule by its 1-based index. Writes a `.bak`
245
+ * before mutating so the user can recover. Returns the removed rule for the
246
+ * CLI to echo back.
247
+ *
248
+ * NOTE: Removal is intentionally a separate verb from `promote`. Ratchet's
249
+ * value is one-way accumulation; deleting should feel deliberate. The CLI
250
+ * surfaces a "narrow the condition instead" reminder around this call.
251
+ */
252
+ export function harnessRmRule(n, { root = findProjectRoot() } = {}) {
253
+ const { path: rmPath, rules } = harnessListRules({ root });
254
+ if (!existsSync(rmPath)) {
255
+ return { ok: false, error: `ratchet.md not found at ${rmPath}` };
256
+ }
257
+ const target = rules.find((r) => r.index === n);
258
+ if (!target) {
259
+ return { ok: false, error: `No rule #${n} (have ${rules.length})`, rules };
260
+ }
261
+ const content = readFileSync(rmPath, 'utf8');
262
+ writeFileSync(rmPath + '.bak', content);
263
+ const lines = content.split('\n');
264
+ lines.splice(target.lineNo, 1);
265
+ writeFileSync(rmPath, lines.join('\n'));
266
+ return { ok: true, path: rmPath, backup: rmPath + '.bak', removed: target };
267
+ }
268
+
223
269
  /**
224
270
  * Statusline segment shape for the 🅷 indicator. Returns null when the user
225
271
  * has explicitly disabled harness display, or when there's no CLAUDE.md and