claude-token-saver 3.4.1 → 3.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "3.4.1",
3
+ "version": "3.4.2",
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": {
@@ -32,6 +32,24 @@ function contentText(content) {
32
32
  const MUTATING_TOOLS = new Set(['Edit', 'Write', 'NotebookEdit', 'Bash']);
33
33
  const DELEGATION_TOOLS = new Set(['Task', 'Agent']);
34
34
 
35
+ // Permission rejections and harness policy denials arrive as is_error
36
+ // tool_results, but they encode the user's choice / the permission system's
37
+ // policy, not task difficulty — counting them poisons the rule-health error
38
+ // rate. Measured on a 14-day window: 6 user rejections + ~29 auto-mode
39
+ // classifier denials out of 142 is_error results (~25% of the numerator).
40
+ const REJECTION_RE = /doesn't want to proceed|tool use was rejected|doesn't want to take this action|denied by the claude code auto mode classifier|permission for this action was denied|requires approval/i;
41
+
42
+ function toolResultText(content) {
43
+ if (typeof content === 'string') return content;
44
+ if (!Array.isArray(content)) return '';
45
+ return content.map((b) => (b && typeof b.text === 'string' ? b.text : '')).join(' ');
46
+ }
47
+
48
+ function isRealToolError(block) {
49
+ return block && block.type === 'tool_result' && block.is_error &&
50
+ !REJECTION_RE.test(toolResultText(block.content));
51
+ }
52
+
35
53
  export async function collectSessionRecords(filePath, { includeContent = true } = {}) {
36
54
  const records = new Map();
37
55
  let depth = 0;
@@ -62,7 +80,7 @@ export async function collectSessionRecords(filePath, { includeContent = true }
62
80
  // follows the assistant call — attribute them to that call's record.
63
81
  if (lastRecord && Array.isArray(msg.content)) {
64
82
  for (const b of msg.content) {
65
- if (b && b.type === 'tool_result' && b.is_error) lastRecord.toolErrors += 1;
83
+ if (isRealToolError(b)) lastRecord.toolErrors += 1;
66
84
  }
67
85
  }
68
86
  continue;