claude-self-reflect 6.0.3 → 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');
@@ -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,
@@ -234,16 +267,30 @@ class UpdateManager {
234
267
 
235
268
  async startQdrant() {
236
269
  this.log('Starting Qdrant...', 'info');
270
+
271
+ // Try Docker Compose v2 first (docker compose)
237
272
  try {
238
273
  execSync('docker compose up -d qdrant', {
239
274
  cwd: this.packageRoot,
240
275
  stdio: 'inherit'
241
276
  });
242
- this.log('Qdrant started', 'success');
277
+ this.log('Qdrant started (Docker Compose v2)', 'success');
243
278
  return true;
244
- } catch (error) {
245
- this.log(`Failed to start Qdrant: ${error.message}`, 'error');
246
- 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
+ }
247
294
  }
248
295
  }
249
296
 
@@ -257,18 +304,38 @@ class UpdateManager {
257
304
  this.log('Analyzing installation...', 'info');
258
305
  console.log();
259
306
 
260
- // 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
261
309
  const checks = [
262
- this.checkDocker(),
263
- this.checkQdrant(),
264
- this.checkFastEmbedModel(),
265
- this.checkDockerComposeConfig(),
266
- this.checkCCStatusline(),
267
- this.checkCSRStatusScript(),
268
- 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() }
269
317
  ];
270
318
 
271
- 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
+ });
272
339
 
273
340
  // Categorize results
274
341
  const missing = results.filter(r => !r.installed);
@@ -300,6 +367,40 @@ class UpdateManager {
300
367
  if (!success) {
301
368
  this.log(`Failed to fix: ${issue.name}`, 'error');
302
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
+ }
303
404
  }
304
405
  } else {
305
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.3",
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",