claude-self-reflect 6.0.2 → 6.0.4

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.
@@ -6,7 +6,7 @@
6
6
 
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
- import { execSync } from 'child_process';
9
+ import { execSync, spawnSync } from 'child_process';
10
10
  import https from 'https';
11
11
  import os from 'os';
12
12
 
@@ -65,20 +65,30 @@ class FastEmbedFallback {
65
65
 
66
66
  try {
67
67
  // Download with curl (handles proxies better than Node's https)
68
+ // SECURITY: Use array-based arguments to prevent shell injection
68
69
  this.log(`Downloading ${this.modelFile} (79MB)...`, 'info');
69
- execSync(`curl -L -o "${tarPath}" "${this.gcsUrl}"`, {
70
+ const curlResult = spawnSync('curl', ['-L', '-o', tarPath, this.gcsUrl], {
70
71
  stdio: 'inherit',
71
72
  timeout: 300000 // 5 minute timeout
72
73
  });
73
74
 
75
+ if (curlResult.error || curlResult.status !== 0) {
76
+ throw new Error(`curl failed: ${curlResult.error?.message || `exit code ${curlResult.status}`}`);
77
+ }
78
+
74
79
  this.log('Download complete. Extracting...', 'success');
75
80
 
76
81
  // Extract (with 2 minute timeout to prevent hanging)
77
- execSync(`tar -xzf "${tarPath}" -C "${this.cacheDir}"`, {
82
+ // SECURITY: Use array-based arguments to prevent shell injection
83
+ const tarResult = spawnSync('tar', ['-xzf', tarPath, '-C', this.cacheDir], {
78
84
  stdio: 'inherit',
79
85
  timeout: 120000 // 2 minute timeout
80
86
  });
81
87
 
88
+ if (tarResult.error || tarResult.status !== 0) {
89
+ throw new Error(`tar extraction failed: ${tarResult.error?.message || `exit code ${tarResult.status}`}`);
90
+ }
91
+
82
92
  // Verify extraction
83
93
  if (this.checkModelExists()) {
84
94
  this.log('FastEmbed model installed successfully!', 'success');
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { fileURLToPath } from 'url';
4
- import { dirname, join } from 'path';
4
+ import { dirname } from 'path';
5
5
 
6
6
  const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = dirname(__filename);
@@ -118,7 +118,7 @@ class StatuslineSetup {
118
118
  fs.unlinkSync(userLocalBin);
119
119
  }
120
120
  fs.symlinkSync(this.csrScript, userLocalBin);
121
- // Note: Don't chmod symlink - ensure source script is executable instead
121
+ // Note: Symlink permissions don't matter - source script permissions are used
122
122
 
123
123
  this.log('csr-status installed to ~/bin (no sudo required)', 'success');
124
124
  this.log('Add ~/bin to PATH: export PATH="$HOME/bin:$PATH"', 'info');
@@ -42,9 +42,9 @@ class UpdateManager {
42
42
  async checkCCStatusline() {
43
43
  try {
44
44
  execSync('npm list -g cc-statusline', { stdio: 'ignore' });
45
- return { installed: true, name: 'cc-statusline', critical: false };
45
+ return { installed: true, name: 'cc-statusline', critical: true };
46
46
  } catch {
47
- return { installed: false, name: 'cc-statusline', critical: false, fix: () => this.installCCStatusline() };
47
+ return { installed: false, name: 'cc-statusline', critical: true, fix: () => this.installCCStatusline() };
48
48
  }
49
49
  }
50
50
 
@@ -77,18 +77,18 @@ class UpdateManager {
77
77
  // Check for both 'ast-grep' (brew) and 'sg' (npm) binaries
78
78
  try {
79
79
  execSync('ast-grep --version', { stdio: 'ignore' });
80
- return { installed: true, name: 'AST-Grep', critical: false };
80
+ return { installed: true, name: 'AST-Grep', critical: true };
81
81
  } catch {
82
82
  // Try 'sg' binary (npm install -g @ast-grep/cli)
83
83
  try {
84
84
  execSync('sg --version', { stdio: 'ignore' });
85
- return { installed: true, name: 'AST-Grep (sg)', critical: false };
85
+ return { installed: true, name: 'AST-Grep (sg)', critical: true };
86
86
  } catch {
87
87
  return {
88
88
  installed: false,
89
- name: 'AST-Grep (optional)',
90
- critical: false,
91
- fix: () => this.suggestASTGrep()
89
+ name: 'AST-Grep',
90
+ critical: true,
91
+ fix: () => this.installASTGrep()
92
92
  };
93
93
  }
94
94
  }
@@ -110,12 +110,45 @@ class UpdateManager {
110
110
  }
111
111
 
112
112
  async checkQdrant() {
113
+ // Check if fetch is available (Node.js 18+)
114
+ if (typeof fetch === 'undefined') {
115
+ this.log('fetch not available, skipping Qdrant health check', 'warning');
116
+ return {
117
+ installed: false,
118
+ name: 'Qdrant',
119
+ critical: true,
120
+ fix: () => this.startQdrant(),
121
+ error: 'Cannot verify Qdrant (fetch not available)'
122
+ };
123
+ }
124
+
125
+ const controller = new AbortController();
126
+ const timeout = setTimeout(() => controller.abort(), 3000); // 3 second timeout
127
+
113
128
  try {
114
- const response = await fetch('http://localhost:6333');
129
+ const response = await fetch('http://localhost:6333', {
130
+ signal: controller.signal
131
+ });
132
+
133
+ // Clear timeout immediately after successful fetch
134
+ clearTimeout(timeout);
135
+
115
136
  if (response.ok) {
137
+ // Timeout already cleared above, safe to return
116
138
  return { installed: true, name: 'Qdrant', critical: true };
139
+ } else {
140
+ // Timeout already cleared above, log and fall through
141
+ this.log(`Qdrant responded with status: ${response.status}`, 'warning');
142
+ }
143
+ } catch (error) {
144
+ clearTimeout(timeout);
145
+
146
+ if (error.name === 'AbortError') {
147
+ this.log('Qdrant health check timed out after 3 seconds', 'warning');
148
+ } else {
149
+ this.log(`Qdrant health check failed: ${error.message}`, 'warning');
117
150
  }
118
- } catch {}
151
+ }
119
152
 
120
153
  return {
121
154
  installed: false,
@@ -193,25 +226,71 @@ class UpdateManager {
193
226
  return await fallback.run();
194
227
  }
195
228
 
196
- async suggestASTGrep() {
197
- this.log('AST-Grep is optional but recommended for code quality features', 'info');
198
- this.log('Install with: brew install ast-grep (macOS)', 'info');
199
- this.log(' or: npm install -g @ast-grep/cli', 'info');
200
- return true; // Not critical
229
+ async installASTGrep() {
230
+ // Try npm installation first (works on all platforms)
231
+ this.log('Installing AST-Grep via npm...', 'info');
232
+ try {
233
+ execSync('npm install -g @ast-grep/cli', { stdio: 'inherit' });
234
+ this.log('AST-Grep installed successfully', 'success');
235
+ return true;
236
+ } catch (npmError) {
237
+ // Check for permission errors
238
+ const isPermissionError = npmError.code === 'EACCES' ||
239
+ npmError.code === 'EPERM' ||
240
+ (npmError.stderr && npmError.stderr.toString().includes('EACCES')) ||
241
+ (npmError.message && npmError.message.includes('permission'));
242
+
243
+ if (isPermissionError) {
244
+ this.log('Failed to install AST-Grep via npm: Permission denied', 'error');
245
+ this.log('Alternative installation methods:', 'info');
246
+ this.log(' 1. With sudo: sudo npm install -g @ast-grep/cli', 'info');
247
+ this.log(' 2. With brew: brew install ast-grep (macOS/Linux)', 'info');
248
+ this.log(' 3. Use nvm for user-local npm installs', 'info');
249
+ return false;
250
+ }
251
+
252
+ // If npm fails for other reasons, try suggesting brew on macOS
253
+ if (process.platform === 'darwin') {
254
+ this.log('npm installation failed. Checking for Homebrew...', 'warning');
255
+ try {
256
+ execSync('brew --version', { stdio: 'ignore' });
257
+ this.log('Install AST-Grep with: brew install ast-grep', 'info');
258
+ } catch {
259
+ this.log('Homebrew not found. Install from: https://brew.sh', 'info');
260
+ }
261
+ }
262
+
263
+ this.log(`AST-Grep installation failed: ${npmError.message}`, 'error');
264
+ return false;
265
+ }
201
266
  }
202
267
 
203
268
  async startQdrant() {
204
269
  this.log('Starting Qdrant...', 'info');
270
+
271
+ // Try Docker Compose v2 first (docker compose)
205
272
  try {
206
273
  execSync('docker compose up -d qdrant', {
207
274
  cwd: this.packageRoot,
208
275
  stdio: 'inherit'
209
276
  });
210
- this.log('Qdrant started', 'success');
277
+ this.log('Qdrant started (Docker Compose v2)', 'success');
211
278
  return true;
212
- } catch (error) {
213
- this.log(`Failed to start Qdrant: ${error.message}`, 'error');
214
- return false;
279
+ } catch (v2Error) {
280
+ this.log('Docker Compose v2 failed, trying v1...', 'warning');
281
+
282
+ // Fallback to Docker Compose v1 (docker-compose)
283
+ try {
284
+ execSync('docker-compose up -d qdrant', {
285
+ cwd: this.packageRoot,
286
+ stdio: 'inherit'
287
+ });
288
+ this.log('Qdrant started (Docker Compose v1)', 'success');
289
+ return true;
290
+ } catch (v1Error) {
291
+ this.log(`Failed to start Qdrant with both v1 and v2: ${v1Error.message}`, 'error');
292
+ return false;
293
+ }
215
294
  }
216
295
  }
217
296
 
@@ -225,18 +304,38 @@ class UpdateManager {
225
304
  this.log('Analyzing installation...', 'info');
226
305
  console.log();
227
306
 
228
- // Run all checks
307
+ // Run all checks with Promise.allSettled to prevent throw on first failure
308
+ // Store checks as objects with name property for maintainability
229
309
  const checks = [
230
- this.checkDocker(),
231
- this.checkQdrant(),
232
- this.checkFastEmbedModel(),
233
- this.checkDockerComposeConfig(),
234
- this.checkCCStatusline(),
235
- this.checkCSRStatusScript(),
236
- this.checkASTGrep()
310
+ { name: 'Docker', fn: () => this.checkDocker() },
311
+ { name: 'Qdrant', fn: () => this.checkQdrant() },
312
+ { name: 'FastEmbed', fn: () => this.checkFastEmbedModel() },
313
+ { name: 'Docker Config', fn: () => this.checkDockerComposeConfig() },
314
+ { name: 'cc-statusline', fn: () => this.checkCCStatusline() },
315
+ { name: 'csr-status', fn: () => this.checkCSRStatusScript() },
316
+ { name: 'AST-Grep', fn: () => this.checkASTGrep() }
237
317
  ];
238
318
 
239
- const results = await Promise.all(checks);
319
+ const settledResults = await Promise.allSettled(checks.map(c => c.fn()));
320
+
321
+ // Convert settled results to standard format, treating rejections as failures
322
+ const results = settledResults.map((result, index) => {
323
+ if (result.status === 'fulfilled') {
324
+ return result.value;
325
+ } else {
326
+ // Rejected check - treat as critical failure
327
+ this.log(
328
+ `Check failed: ${checks[index].name} - ${result.reason?.message || result.reason}`,
329
+ 'error'
330
+ );
331
+ return {
332
+ installed: false,
333
+ critical: true,
334
+ name: checks[index].name,
335
+ error: `Check threw error: ${result.reason?.message || result.reason}`
336
+ };
337
+ }
338
+ });
240
339
 
241
340
  // Categorize results
242
341
  const missing = results.filter(r => !r.installed);
@@ -268,6 +367,40 @@ class UpdateManager {
268
367
  if (!success) {
269
368
  this.log(`Failed to fix: ${issue.name}`, 'error');
270
369
  unresolvedCritical.push(issue);
370
+ } else {
371
+ // Re-verify the fix worked by re-running the check
372
+ this.log(`Verifying fix for ${issue.name}...`, 'info');
373
+ const recheckName = issue.name.toLowerCase();
374
+ let recheckResult;
375
+
376
+ if (recheckName.includes('docker') && recheckName.includes('config')) {
377
+ // Docker config specific check
378
+ recheckResult = await this.checkDockerComposeConfig();
379
+ } else if (recheckName.includes('docker') && !recheckName.includes('config')) {
380
+ recheckResult = await this.checkDocker();
381
+ } else if (recheckName.includes('qdrant')) {
382
+ // Give Qdrant a moment to come online before rechecking
383
+ this.log('Waiting 5 seconds for Qdrant to start...', 'info');
384
+ await new Promise(resolve => setTimeout(resolve, 5000));
385
+ recheckResult = await this.checkQdrant();
386
+ } else if (recheckName.includes('fastembed')) {
387
+ recheckResult = await this.checkFastEmbedModel();
388
+ } else if (recheckName.includes('cc-statusline')) {
389
+ recheckResult = await this.checkCCStatusline();
390
+ } else if (recheckName.includes('csr-status')) {
391
+ recheckResult = await this.checkCSRStatusScript();
392
+ } else if (recheckName.includes('ast-grep')) {
393
+ recheckResult = await this.checkASTGrep();
394
+ }
395
+
396
+ // Guard against undefined recheckResult (no matching verifier)
397
+ if (recheckResult === undefined) {
398
+ this.log(`No verifier found for ${issue.name} - cannot verify fix`, 'error');
399
+ unresolvedCritical.push(issue);
400
+ } else if (!recheckResult.installed) {
401
+ this.log(`Fix verification failed for ${issue.name}`, 'error');
402
+ unresolvedCritical.push(issue);
403
+ }
271
404
  }
272
405
  } else {
273
406
  // Issue has no fix and no error message - track as unresolved
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-self-reflect",
3
- "version": "6.0.2",
3
+ "version": "6.0.4",
4
4
  "description": "Give Claude perfect memory of all your conversations - Installation wizard for Python MCP server",
5
5
  "keywords": [
6
6
  "claude",
@@ -31,7 +31,8 @@
31
31
  "author": "Claude-Self-Reflect Contributors",
32
32
  "type": "module",
33
33
  "bin": {
34
- "claude-self-reflect": "installer/cli.js"
34
+ "claude-self-reflect": "installer/cli.js",
35
+ "csr-status": "scripts/csr-status"
35
36
  },
36
37
  "files": [
37
38
  "installer/**/*.js",
@@ -43,8 +43,10 @@ class LocalEmbeddingProvider(EmbeddingProvider):
43
43
  """Initialize the FastEmbed model."""
44
44
  try:
45
45
  from fastembed import TextEmbedding
46
- self.model = TextEmbedding(model_name="BAAI/bge-small-en-v1.5")
47
- logger.info("Initialized local FastEmbed model (384 dimensions)")
46
+ # CRITICAL: Use the correct model that matches the rest of the system
47
+ # This must be sentence-transformers/all-MiniLM-L6-v2 (384 dimensions)
48
+ self.model = TextEmbedding(model_name="sentence-transformers/all-MiniLM-L6-v2")
49
+ logger.info("Initialized local FastEmbed model: sentence-transformers/all-MiniLM-L6-v2 (384 dimensions)")
48
50
  except ImportError as e:
49
51
  logger.error("FastEmbed not installed. Install with: pip install fastembed")
50
52
  raise