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 +20 -4
- package/README.md +21 -5
- package/bin/cli.js +75 -16
- package/package.json +1 -1
- package/src/harness.js +16 -7
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
|
-
##
|
|
16
|
+
## Cost-savings report โ harness + ratchet adoption
|
|
17
17
|
|
|
18
|
-

|
|
19
19
|
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
16
|
+
## ์ค์ ํจ๊ณผ โ harness + ratchet ๋์
์ ํ ๋น์ฉ ์ ๊ฐ ๋ฆฌํฌํธ
|
|
17
17
|
|
|
18
|
-

|
|
19
19
|
|
|
20
|
-
์ ์ ๋ณธ์ธ์ Claude Code ์ฌ์ฉ
|
|
20
|
+
claude-token-saver์ ์ต๊ทผ ์ถ๊ฐ๋ **harness 5/5 + ratchet** ๊ธฐ๋ฅ์ ์ค์ ์ ์ฉํด๋ณด๊ณ ๋์
์ ํ๋ฅผ ๋น๊ตํ ๊ฒฐ๊ณผ์
๋๋ค (์ ์ ๋ณธ์ธ์ Claude Code ์ฌ์ฉ ๋ก๊ทธ, **์ฌ์ฉ์ ๋ฉ์์ง 1๊ฑด๋น**์ผ๋ก ์ ๊ทํ, 2026-05-02 ๊ธฐ์ค, Opus 4.7 ๊ฐ๊ฒฉ):
|
|
21
21
|
|
|
22
|
-
| ๋ฉํธ๋ฆญ |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
519
|
-
|
|
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
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
|
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
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 =
|
|
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 =
|
|
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
|
}
|