cc-dev-template 0.1.92 → 0.1.93
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/bin/install.js +21 -2
- package/package.json +1 -1
- package/src/scripts/read-guard-hook.json +0 -15
- package/src/scripts/read-guard.js +0 -146
package/bin/install.js
CHANGED
|
@@ -252,7 +252,6 @@ const settingsFile = path.join(CLAUDE_DIR, 'settings.json');
|
|
|
252
252
|
|
|
253
253
|
if (fs.existsSync(mergeSettingsPath)) {
|
|
254
254
|
const configs = [
|
|
255
|
-
{ file: 'read-guard-hook.json', name: 'Context guard for large reads' },
|
|
256
255
|
{ file: 'task-output-guard-hook.json', name: 'TaskOutput context guard' },
|
|
257
256
|
{ file: 'statusline-config.json', name: 'Custom status line' },
|
|
258
257
|
// Spinner verbs - choose one (Star Trek or Factorio)
|
|
@@ -344,7 +343,9 @@ const deprecatedFiles = [
|
|
|
344
343
|
path.join(CLAUDE_DIR, 'hooks', 'bash-overflow-guard.sh'),
|
|
345
344
|
path.join(CLAUDE_DIR, 'scripts', 'bash-overflow-hook.json'),
|
|
346
345
|
path.join(CLAUDE_DIR, 'scripts', 'env-config.json'),
|
|
347
|
-
path.join(CLAUDE_DIR, 'scripts', 'spinner-verbs-helldivers.json')
|
|
346
|
+
path.join(CLAUDE_DIR, 'scripts', 'spinner-verbs-helldivers.json'),
|
|
347
|
+
path.join(CLAUDE_DIR, 'scripts', 'read-guard.js'),
|
|
348
|
+
path.join(CLAUDE_DIR, 'scripts', 'read-guard-hook.json')
|
|
348
349
|
];
|
|
349
350
|
|
|
350
351
|
deprecatedFiles.forEach(file => {
|
|
@@ -388,6 +389,24 @@ if (fs.existsSync(settingsFile)) {
|
|
|
388
389
|
});
|
|
389
390
|
}
|
|
390
391
|
|
|
392
|
+
// Remove read-guard hooks from settings
|
|
393
|
+
if (settings.hooks) {
|
|
394
|
+
['PreToolUse'].forEach(hookType => {
|
|
395
|
+
if (settings.hooks[hookType] && Array.isArray(settings.hooks[hookType])) {
|
|
396
|
+
const originalLength = settings.hooks[hookType].length;
|
|
397
|
+
settings.hooks[hookType] = settings.hooks[hookType].filter(hook => {
|
|
398
|
+
const command = hook.hooks?.[0]?.command || '';
|
|
399
|
+
return !command.includes('read-guard');
|
|
400
|
+
});
|
|
401
|
+
if (settings.hooks[hookType].length < originalLength) {
|
|
402
|
+
console.log(`✓ Removed deprecated read-guard hook from ${hookType}`);
|
|
403
|
+
settingsModified = true;
|
|
404
|
+
cleanupPerformed = true;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
391
410
|
// Remove bash-overflow-guard hooks from settings
|
|
392
411
|
if (settings.hooks) {
|
|
393
412
|
['PostToolUse'].forEach(hookType => {
|
package/package.json
CHANGED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* read-guard.js - Block large file reads to preserve context
|
|
5
|
-
*
|
|
6
|
-
* This script intercepts Read tool calls and blocks reads of large files that would
|
|
7
|
-
* consume excessive context. It forces Claude to use more targeted approaches like
|
|
8
|
-
* Grep to find relevant sections, then Read with offset/limit parameters.
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* echo '{"tool_input":{"file_path":"/path/to/file.js"}}' | node read-guard.js
|
|
12
|
-
*
|
|
13
|
-
* Input format (JSON via stdin):
|
|
14
|
-
* {
|
|
15
|
-
* "tool_input": {
|
|
16
|
-
* "file_path": "/absolute/path/to/file.js",
|
|
17
|
-
* "limit": 100, // optional - if present, allow the read
|
|
18
|
-
* "offset": 0 // optional
|
|
19
|
-
* }
|
|
20
|
-
* }
|
|
21
|
-
*
|
|
22
|
-
* Exit codes:
|
|
23
|
-
* 0 - Allow the read (outputs nothing or JSON to allow/deny)
|
|
24
|
-
*
|
|
25
|
-
* Environment variables:
|
|
26
|
-
* CONTEXT_GUARD_TOKEN_LIMIT - Token threshold (default: 5000)
|
|
27
|
-
*
|
|
28
|
-
* Token estimation:
|
|
29
|
-
* Rough heuristic: file_size_bytes / 4 ≈ tokens
|
|
30
|
-
* This catches obvious context bloat without needing a tokenizer.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
const fs = require('fs');
|
|
34
|
-
|
|
35
|
-
const TOKEN_THRESHOLD = parseInt(process.env.CONTEXT_GUARD_TOKEN_LIMIT || '5000', 10);
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Read JSON from stdin
|
|
39
|
-
* @returns {Promise<object | null>} - Parsed JSON or null if invalid
|
|
40
|
-
*/
|
|
41
|
-
async function readStdin() {
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
let data = '';
|
|
44
|
-
|
|
45
|
-
process.stdin.setEncoding('utf8');
|
|
46
|
-
process.stdin.on('data', chunk => {
|
|
47
|
-
data += chunk;
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
process.stdin.on('end', () => {
|
|
51
|
-
try {
|
|
52
|
-
resolve(JSON.parse(data));
|
|
53
|
-
} catch {
|
|
54
|
-
resolve(null);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
process.stdin.on('error', () => {
|
|
59
|
-
resolve(null);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Format file size for display
|
|
66
|
-
* @param {number} bytes - File size in bytes
|
|
67
|
-
* @returns {string} - Human-readable size
|
|
68
|
-
*/
|
|
69
|
-
function formatSize(bytes) {
|
|
70
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
71
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
72
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Main execution
|
|
77
|
-
*/
|
|
78
|
-
async function main() {
|
|
79
|
-
const inputData = await readStdin();
|
|
80
|
-
|
|
81
|
-
// If we can't parse input, allow the read
|
|
82
|
-
if (!inputData) {
|
|
83
|
-
process.exit(0);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const toolInput = inputData.tool_input || {};
|
|
87
|
-
const filePath = toolInput.file_path;
|
|
88
|
-
const limit = toolInput.limit;
|
|
89
|
-
|
|
90
|
-
// If limit is already specified, Claude is being intentional - allow it
|
|
91
|
-
if (limit !== undefined) {
|
|
92
|
-
process.exit(0);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Image files: token consumption based on dimensions, not file size
|
|
96
|
-
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.ico', '.tiff', '.tif'];
|
|
97
|
-
const ext = filePath.toLowerCase().slice(filePath.lastIndexOf('.'));
|
|
98
|
-
if (imageExtensions.includes(ext)) {
|
|
99
|
-
process.exit(0);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// If file doesn't exist or no path, let Read tool handle the error
|
|
103
|
-
if (!filePath || !fs.existsSync(filePath)) {
|
|
104
|
-
process.exit(0);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Get file stats
|
|
108
|
-
let stats;
|
|
109
|
-
try {
|
|
110
|
-
stats = fs.statSync(filePath);
|
|
111
|
-
} catch {
|
|
112
|
-
// Can't stat file, let Read tool handle it
|
|
113
|
-
process.exit(0);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Skip directories
|
|
117
|
-
if (stats.isDirectory()) {
|
|
118
|
-
process.exit(0);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Estimate tokens from file size
|
|
122
|
-
const fileSizeBytes = stats.size;
|
|
123
|
-
const estimatedTokens = Math.ceil(fileSizeBytes / 4);
|
|
124
|
-
|
|
125
|
-
// If under threshold, allow the read
|
|
126
|
-
if (estimatedTokens <= TOKEN_THRESHOLD) {
|
|
127
|
-
process.exit(0);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Block the read with guidance
|
|
131
|
-
const output = {
|
|
132
|
-
hookSpecificOutput: {
|
|
133
|
-
hookEventName: "PreToolUse",
|
|
134
|
-
permissionDecision: "deny",
|
|
135
|
-
permissionDecisionReason: `This file (~${estimatedTokens.toLocaleString()} tokens, ${formatSize(fileSizeBytes)}) would consume significant context. Use Grep to find relevant sections first, then Read with offset/limit parameters to read specific portions. If you need the full file, use multiple parallel Read calls with offset/limit to read it in chunks.`
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
console.log(JSON.stringify(output));
|
|
140
|
-
process.exit(0);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
main().catch(() => {
|
|
144
|
-
// If anything goes wrong, allow the read to avoid disrupting workflow
|
|
145
|
-
process.exit(0);
|
|
146
|
-
});
|