@torka/claude-qol 0.2.1 → 0.3.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.
Files changed (2) hide show
  1. package/install.js +87 -20
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -13,11 +13,17 @@ const colors = {
13
13
  green: '\x1b[32m',
14
14
  yellow: '\x1b[33m',
15
15
  blue: '\x1b[34m',
16
+ cyan: '\x1b[36m',
16
17
  red: '\x1b[31m',
17
18
  reset: '\x1b[0m',
18
19
  bold: '\x1b[1m',
19
20
  };
20
21
 
22
+ // Files that should not be overwritten if user has customized them
23
+ const PROTECTED_FILES = [
24
+ 'auto_approve_safe.rules.json'
25
+ ];
26
+
21
27
  function log(message, color = 'reset') {
22
28
  console.log(`${colors[color]}${message}${colors.reset}`);
23
29
  }
@@ -34,21 +40,16 @@ function logError(message) {
34
40
  log(` ✗ ${message}`, 'red');
35
41
  }
36
42
 
37
- function logUpdated(message) {
43
+ function logUpdate(message) {
38
44
  log(` ↻ ${message}`, 'blue');
39
45
  }
40
46
 
41
- /**
42
- * Check if two files have identical content
43
- */
44
- function filesAreIdentical(file1, file2) {
45
- try {
46
- const content1 = fs.readFileSync(file1);
47
- const content2 = fs.readFileSync(file2);
48
- return content1.equals(content2);
49
- } catch {
50
- return false;
51
- }
47
+ function logBackup(message) {
48
+ log(` ⤷ ${message}`, 'cyan');
49
+ }
50
+
51
+ function logPreserve(message) {
52
+ log(` ★ ${message}`, 'yellow');
52
53
  }
53
54
 
54
55
  /**
@@ -87,6 +88,32 @@ function getTargetBase() {
87
88
  return path.join(process.env.INIT_CWD || process.cwd(), '.claude');
88
89
  }
89
90
 
91
+ /**
92
+ * Ensure entries exist in a .gitignore file (append if missing)
93
+ */
94
+ function ensureGitignoreEntries(gitignorePath, entries, header) {
95
+ let existingContent = '';
96
+ if (fs.existsSync(gitignorePath)) {
97
+ existingContent = fs.readFileSync(gitignorePath, 'utf8');
98
+ }
99
+
100
+ const existingLines = new Set(
101
+ existingContent.split('\n').map(line => line.trim()).filter(Boolean)
102
+ );
103
+
104
+ const missingEntries = entries.filter(entry => !existingLines.has(entry));
105
+
106
+ if (missingEntries.length > 0) {
107
+ const newContent = existingContent.trimEnd() +
108
+ (existingContent ? '\n\n' : '') +
109
+ `# ${header}\n` +
110
+ missingEntries.join('\n') + '\n';
111
+ fs.writeFileSync(gitignorePath, newContent);
112
+ return missingEntries.length;
113
+ }
114
+ return 0;
115
+ }
116
+
90
117
  /**
91
118
  * Recursively copy directory contents
92
119
  */
@@ -110,17 +137,31 @@ function copyDirRecursive(src, dest, stats) {
110
137
  copyDirRecursive(srcPath, destPath, stats);
111
138
  } else {
112
139
  if (fs.existsSync(destPath)) {
113
- // Check if files are identical
114
- if (filesAreIdentical(srcPath, destPath)) {
115
- stats.skipped.push(destPath);
140
+ // Check if this is a protected user config file
141
+ if (PROTECTED_FILES.includes(entry.name)) {
142
+ stats.preserved.push(destPath);
143
+ logPreserve(`Preserved (user config): ${path.relative(stats.targetBase, destPath)}`);
144
+ continue;
145
+ }
146
+
147
+ // Check if files are identical (inline comparison)
148
+ const srcContent = fs.readFileSync(srcPath);
149
+ const destContent = fs.readFileSync(destPath);
150
+ const filesAreIdentical = srcContent.equals(destContent);
151
+
152
+ if (filesAreIdentical) {
153
+ stats.unchanged.push(destPath);
116
154
  logSkip(`Unchanged: ${path.relative(stats.targetBase, destPath)}`);
117
155
  } else {
118
156
  // Backup existing file, then replace
119
- const backupPath = destPath + '.bak';
157
+ const backupPath = destPath + '.backup';
120
158
  fs.copyFileSync(destPath, backupPath);
159
+ stats.backups.push(backupPath);
160
+ logBackup(`Backup: ${path.relative(stats.targetBase, backupPath)}`);
161
+
121
162
  fs.copyFileSync(srcPath, destPath);
122
163
  stats.updated.push(destPath);
123
- logUpdated(`Updated (backup: .bak): ${path.relative(stats.targetBase, destPath)}`);
164
+ logUpdate(`Updated: ${path.relative(stats.targetBase, destPath)}`);
124
165
  }
125
166
  } else {
126
167
  fs.copyFileSync(srcPath, destPath);
@@ -148,10 +189,34 @@ function install() {
148
189
  fs.mkdirSync(targetBase, { recursive: true });
149
190
  }
150
191
 
192
+ // Ensure gitignore entries for package-installed files
193
+ const gitignorePath = path.join(targetBase, '.gitignore');
194
+ const gitignoreEntries = [
195
+ 'scripts/auto_approve_safe.py',
196
+ 'scripts/auto_approve_safe.rules.json',
197
+ 'scripts/context-monitor.py',
198
+ 'scripts/__pycache__/',
199
+ 'commands/optimize-auto-approve-hook.md',
200
+ 'skills/nash/',
201
+ 'auto_approve_safe.decisions.jsonl',
202
+ 'auto_approve_safe.decisions.archived.jsonl',
203
+ '*.backup',
204
+ ];
205
+ const addedCount = ensureGitignoreEntries(
206
+ gitignorePath,
207
+ gitignoreEntries,
208
+ 'Installed by @torka/claude-qol'
209
+ );
210
+ if (addedCount > 0) {
211
+ log(` Updated .gitignore (added ${addedCount} entries)`, 'green');
212
+ }
213
+
151
214
  const stats = {
152
215
  copied: [],
153
216
  updated: [],
154
- skipped: [],
217
+ unchanged: [],
218
+ preserved: [],
219
+ backups: [],
155
220
  targetBase,
156
221
  };
157
222
 
@@ -176,8 +241,10 @@ function install() {
176
241
  // Summary
177
242
  log('\n' + colors.bold + '📊 Installation Summary' + colors.reset);
178
243
  log(` Files copied: ${stats.copied.length}`, 'green');
179
- log(` Files updated (backups created): ${stats.updated.length}`, 'blue');
180
- log(` Files unchanged: ${stats.skipped.length}`, 'yellow');
244
+ log(` Files updated: ${stats.updated.length}`, 'blue');
245
+ log(` Backups created: ${stats.backups.length}`, 'cyan');
246
+ log(` Files unchanged: ${stats.unchanged.length}`, 'yellow');
247
+ log(` Files preserved (user config): ${stats.preserved.length}`, 'yellow');
181
248
 
182
249
  // Post-install instructions
183
250
  log('\n' + colors.bold + '📝 Configuration Required' + colors.reset);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torka/claude-qol",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Claude Code quality-of-life improvements: auto-approve hooks, context monitoring, and status line enhancements",
5
5
  "keywords": [
6
6
  "claude-code",