claude-self-reflect 6.0.0 → 6.0.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/README.md +0 -3
- package/docker-compose.yaml +0 -10
- package/installer/cli.js +88 -4
- package/installer/fastembed-fallback.js +261 -0
- package/installer/postinstall.js +17 -12
- package/installer/statusline-setup.js +79 -8
- package/installer/update-manager.js +318 -0
- package/package.json +1 -1
- package/shared/ast_grep_utils.py +89 -0
package/README.md
CHANGED
|
@@ -175,9 +175,6 @@ Your code quality displayed live as you work:
|
|
|
175
175
|
3. **Quality Scoring**: Weighted scoring normalized by lines of code
|
|
176
176
|
4. **Statusline Display**: Real-time feedback as you code
|
|
177
177
|
|
|
178
|
-
> [!TIP]
|
|
179
|
-
> Run `python scripts/session_quality_tracker.py` to analyze your current session quality!
|
|
180
|
-
|
|
181
178
|
</details>
|
|
182
179
|
|
|
183
180
|
## Key Features
|
package/docker-compose.yaml
CHANGED
|
@@ -40,8 +40,6 @@ services:
|
|
|
40
40
|
volumes:
|
|
41
41
|
- ${CLAUDE_LOGS_PATH:-~/.claude/projects}:/logs:ro
|
|
42
42
|
- ${CONFIG_PATH:-~/.claude-self-reflect/config}:/config
|
|
43
|
-
- ./src:/app/src:ro
|
|
44
|
-
- ./shared:/app/shared:ro
|
|
45
43
|
environment:
|
|
46
44
|
- QDRANT_URL=http://qdrant:6333
|
|
47
45
|
- STATE_FILE=/config/imported-files.json
|
|
@@ -71,8 +69,6 @@ services:
|
|
|
71
69
|
volumes:
|
|
72
70
|
- ${CLAUDE_LOGS_PATH:-~/.claude/projects}:/logs:ro
|
|
73
71
|
- ${CONFIG_PATH:-~/.claude-self-reflect/config}:/config
|
|
74
|
-
- ./src:/app/src:ro
|
|
75
|
-
- ./shared:/app/shared:ro
|
|
76
72
|
- /tmp:/tmp
|
|
77
73
|
environment:
|
|
78
74
|
- QDRANT_URL=http://qdrant:6333
|
|
@@ -103,8 +99,6 @@ services:
|
|
|
103
99
|
volumes:
|
|
104
100
|
- ${CLAUDE_LOGS_PATH:-~/.claude/projects}:/logs:ro
|
|
105
101
|
- ${CONFIG_PATH:-~/.claude-self-reflect/config}:/config
|
|
106
|
-
- ./src:/app/src:ro
|
|
107
|
-
- ./shared:/app/shared:ro
|
|
108
102
|
environment:
|
|
109
103
|
- QDRANT_URL=http://qdrant:6333
|
|
110
104
|
- STATE_FILE=/config/streaming-state.json # FIXED: Use streaming-specific state file
|
|
@@ -144,8 +138,6 @@ services:
|
|
|
144
138
|
volumes:
|
|
145
139
|
- ${CLAUDE_LOGS_PATH:-~/.claude/projects}:/logs:ro
|
|
146
140
|
- ${CONFIG_PATH:-~/.claude-self-reflect/config}:/config
|
|
147
|
-
- ./src:/app/src:ro
|
|
148
|
-
- ./shared:/app/shared:ro
|
|
149
141
|
environment:
|
|
150
142
|
- QDRANT_URL=http://qdrant:6333
|
|
151
143
|
- STATE_FILE=/config/imported-files.json
|
|
@@ -181,8 +173,6 @@ services:
|
|
|
181
173
|
volumes:
|
|
182
174
|
- ${CLAUDE_LOGS_PATH:-~/.claude/projects}:/logs:ro
|
|
183
175
|
- ${CONFIG_PATH:-~/.claude-self-reflect/config}:/config
|
|
184
|
-
- ./src:/app/src:ro
|
|
185
|
-
- ./shared:/app/shared:ro
|
|
186
176
|
environment:
|
|
187
177
|
- QDRANT_URL=http://qdrant:6333
|
|
188
178
|
- STATE_FILE=/config/csr-watcher.json
|
package/installer/cli.js
CHANGED
|
@@ -10,9 +10,12 @@ const __dirname = dirname(__filename);
|
|
|
10
10
|
|
|
11
11
|
const commands = {
|
|
12
12
|
setup: 'Run the setup wizard to configure Claude Self-Reflect',
|
|
13
|
+
update: 'Check for and install missing features or updates',
|
|
13
14
|
status: 'Get indexing status as JSON (overall + per-project breakdown)',
|
|
14
15
|
statusline: 'Configure Claude Code statusline integration',
|
|
15
16
|
doctor: 'Check your installation and diagnose issues',
|
|
17
|
+
version: 'Show version and check for updates',
|
|
18
|
+
uninstall: 'Uninstall Claude Self-Reflect',
|
|
16
19
|
help: 'Show this help message'
|
|
17
20
|
};
|
|
18
21
|
|
|
@@ -185,12 +188,13 @@ function help() {
|
|
|
185
188
|
console.log('\nExamples:');
|
|
186
189
|
console.log(' claude-self-reflect setup --voyage-key=pa-1234567890');
|
|
187
190
|
console.log(' claude-self-reflect setup --local');
|
|
188
|
-
console.log(' claude-self-reflect
|
|
189
|
-
console.log(' claude-self-reflect status # Get indexing status
|
|
190
|
-
|
|
191
|
+
console.log(' claude-self-reflect update # Fix missing features');
|
|
192
|
+
console.log(' claude-self-reflect status # Get indexing status');
|
|
193
|
+
console.log(' claude-self-reflect doctor # Diagnose issues');
|
|
194
|
+
|
|
191
195
|
console.log('\nFor more information:');
|
|
192
196
|
console.log(' Documentation: https://github.com/ramakay/claude-self-reflect');
|
|
193
|
-
console.log('
|
|
197
|
+
console.log(' Issues: https://github.com/ramakay/claude-self-reflect/issues');
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
async function statusline() {
|
|
@@ -205,6 +209,77 @@ async function statusline() {
|
|
|
205
209
|
}
|
|
206
210
|
}
|
|
207
211
|
|
|
212
|
+
// Helper function to run bash scripts with validation
|
|
213
|
+
async function runBashScript(scriptName, errorMessage) {
|
|
214
|
+
const scriptPath = join(__dirname, '..', 'scripts', 'setup', scriptName);
|
|
215
|
+
|
|
216
|
+
// Check bash is available
|
|
217
|
+
try {
|
|
218
|
+
execSync('bash --version', { stdio: 'ignore' });
|
|
219
|
+
} catch {
|
|
220
|
+
console.error('❌ Bash is required but not found');
|
|
221
|
+
console.error(' Please ensure bash is installed');
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check script file exists
|
|
226
|
+
try {
|
|
227
|
+
await fs.access(scriptPath);
|
|
228
|
+
} catch {
|
|
229
|
+
console.error(`❌ Script not found: ${scriptPath}`);
|
|
230
|
+
console.error(' Please reinstall claude-self-reflect');
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Run script
|
|
235
|
+
try {
|
|
236
|
+
const child = spawn('bash', [scriptPath], { stdio: 'inherit' });
|
|
237
|
+
child.on('exit', (code) => {
|
|
238
|
+
process.exit(code || 0);
|
|
239
|
+
});
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error(`${errorMessage} ${error.message}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function update() {
|
|
247
|
+
console.log('🔄 Checking for missing features and updates...\n');
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const UpdateManager = (await import('./update-manager.js')).default;
|
|
251
|
+
const manager = new UpdateManager();
|
|
252
|
+
await manager.run();
|
|
253
|
+
} catch (error) {
|
|
254
|
+
// Check if update-manager.js itself is missing (not a nested dependency)
|
|
255
|
+
// Node uses file:// URLs in error messages, not relative paths
|
|
256
|
+
const isUpdateManagerMissing =
|
|
257
|
+
(error.code === 'ERR_MODULE_NOT_FOUND' &&
|
|
258
|
+
(error.message.includes('update-manager.js') || error.message.includes('update-manager'))) ||
|
|
259
|
+
(error.message && error.message.includes('Cannot find module') && error.message.includes('update-manager'));
|
|
260
|
+
|
|
261
|
+
if (isUpdateManagerMissing) {
|
|
262
|
+
console.error('❌ Update manager not found');
|
|
263
|
+
console.error(' Please reinstall: npm install -g claude-self-reflect');
|
|
264
|
+
} else {
|
|
265
|
+
console.error('❌ Update check failed:', error.message);
|
|
266
|
+
console.error(' Run with DEBUG=1 for more details');
|
|
267
|
+
if (process.env.DEBUG) {
|
|
268
|
+
console.error('\nStack trace:', error.stack);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function version() {
|
|
276
|
+
await runBashScript('version.sh', 'Failed to check version:');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function uninstall() {
|
|
280
|
+
await runBashScript('uninstall.sh', 'Failed to run uninstall:');
|
|
281
|
+
}
|
|
282
|
+
|
|
208
283
|
// Main
|
|
209
284
|
const command = process.argv[2] || 'help';
|
|
210
285
|
|
|
@@ -212,6 +287,9 @@ switch (command) {
|
|
|
212
287
|
case 'setup':
|
|
213
288
|
setup();
|
|
214
289
|
break;
|
|
290
|
+
case 'update':
|
|
291
|
+
update();
|
|
292
|
+
break;
|
|
215
293
|
case 'status':
|
|
216
294
|
status();
|
|
217
295
|
break;
|
|
@@ -221,6 +299,12 @@ switch (command) {
|
|
|
221
299
|
case 'doctor':
|
|
222
300
|
doctor();
|
|
223
301
|
break;
|
|
302
|
+
case 'version':
|
|
303
|
+
version();
|
|
304
|
+
break;
|
|
305
|
+
case 'uninstall':
|
|
306
|
+
uninstall();
|
|
307
|
+
break;
|
|
224
308
|
case 'help':
|
|
225
309
|
default:
|
|
226
310
|
help();
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* FastEmbed Fallback Installer
|
|
4
|
+
* Automatically detects SSL/proxy issues and downloads model from Google Cloud Storage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
import https from 'https';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
|
|
13
|
+
class FastEmbedFallback {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.homeDir = os.homedir();
|
|
16
|
+
this.cacheDir = path.join(this.homeDir, '.cache', 'fastembed');
|
|
17
|
+
this.modelName = 'sentence-transformers-all-MiniLM-L6-v2';
|
|
18
|
+
this.modelFile = `${this.modelName}.tar.gz`;
|
|
19
|
+
this.gcsUrl = `https://storage.googleapis.com/qdrant-fastembed/${this.modelFile}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
log(message, type = 'info') {
|
|
23
|
+
const colors = {
|
|
24
|
+
info: '\x1b[36m',
|
|
25
|
+
success: '\x1b[32m',
|
|
26
|
+
warning: '\x1b[33m',
|
|
27
|
+
error: '\x1b[31m'
|
|
28
|
+
};
|
|
29
|
+
const prefix = {
|
|
30
|
+
info: 'ℹ',
|
|
31
|
+
success: '✓',
|
|
32
|
+
warning: '⚠',
|
|
33
|
+
error: '✗'
|
|
34
|
+
};
|
|
35
|
+
console.log(`${colors[type]}${prefix[type]} ${message}\x1b[0m`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
checkModelExists() {
|
|
39
|
+
const modelPath = path.join(this.cacheDir, this.modelName);
|
|
40
|
+
return fs.existsSync(modelPath);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async testHuggingFace() {
|
|
44
|
+
this.log('Testing HuggingFace connectivity...', 'info');
|
|
45
|
+
try {
|
|
46
|
+
execSync('curl -s -m 5 https://huggingface.co > /dev/null 2>&1', { timeout: 5000 });
|
|
47
|
+
this.log('HuggingFace is accessible', 'success');
|
|
48
|
+
return true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.log('HuggingFace blocked by proxy/firewall', 'warning');
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
downloadFromGCS() {
|
|
56
|
+
this.log('Downloading FastEmbed model from Google Cloud Storage...', 'info');
|
|
57
|
+
this.log('(Using GCS mirror to bypass corporate proxies)', 'info');
|
|
58
|
+
|
|
59
|
+
// Create cache directory
|
|
60
|
+
if (!fs.existsSync(this.cacheDir)) {
|
|
61
|
+
fs.mkdirSync(this.cacheDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const tarPath = path.join(this.cacheDir, this.modelFile);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Download with curl (handles proxies better than Node's https)
|
|
68
|
+
this.log(`Downloading ${this.modelFile} (79MB)...`, 'info');
|
|
69
|
+
execSync(`curl -L -o "${tarPath}" "${this.gcsUrl}"`, {
|
|
70
|
+
stdio: 'inherit',
|
|
71
|
+
timeout: 300000 // 5 minute timeout
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
this.log('Download complete. Extracting...', 'success');
|
|
75
|
+
|
|
76
|
+
// Extract (with 2 minute timeout to prevent hanging)
|
|
77
|
+
execSync(`tar -xzf "${tarPath}" -C "${this.cacheDir}"`, {
|
|
78
|
+
stdio: 'inherit',
|
|
79
|
+
timeout: 120000 // 2 minute timeout
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Verify extraction
|
|
83
|
+
if (this.checkModelExists()) {
|
|
84
|
+
this.log('FastEmbed model installed successfully!', 'success');
|
|
85
|
+
|
|
86
|
+
// Clean up tar file
|
|
87
|
+
fs.unlinkSync(tarPath);
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
} else {
|
|
91
|
+
this.log('Model extraction failed', 'error');
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
this.log(`Download failed: ${error.message}`, 'error');
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
configureDockerCompose() {
|
|
101
|
+
this.log('Configuring docker-compose for offline model...', 'info');
|
|
102
|
+
|
|
103
|
+
const dockerComposePath = path.join(process.cwd(), 'docker-compose.yaml');
|
|
104
|
+
|
|
105
|
+
if (!fs.existsSync(dockerComposePath)) {
|
|
106
|
+
this.log('docker-compose.yaml not found, skipping configuration', 'warning');
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const content = fs.readFileSync(dockerComposePath, 'utf8');
|
|
112
|
+
|
|
113
|
+
// Check if already configured
|
|
114
|
+
if (content.includes('HF_HUB_OFFLINE')) {
|
|
115
|
+
this.log('docker-compose already configured for offline mode', 'success');
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Line-by-line processing is more reliable than regex for YAML
|
|
120
|
+
const lines = content.split('\n');
|
|
121
|
+
const services = ['importer', 'watcher', 'streaming-importer', 'async-importer', 'safe-watcher', 'mcp-server'];
|
|
122
|
+
let currentService = null;
|
|
123
|
+
let inEnvironment = false;
|
|
124
|
+
let environmentIndent = '';
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < lines.length; i++) {
|
|
127
|
+
const line = lines[i];
|
|
128
|
+
const trimmed = line.trim();
|
|
129
|
+
|
|
130
|
+
// Track which service we're in
|
|
131
|
+
for (const service of services) {
|
|
132
|
+
if (trimmed === `${service}:`) {
|
|
133
|
+
currentService = service;
|
|
134
|
+
inEnvironment = false;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Detect environment section
|
|
140
|
+
if (currentService && trimmed === 'environment:') {
|
|
141
|
+
inEnvironment = true;
|
|
142
|
+
environmentIndent = line.match(/^(\s+)/)?.[1] || ' ';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Add HF_HUB_OFFLINE after first environment entry
|
|
146
|
+
if (inEnvironment && trimmed.startsWith('-') && !content.includes('HF_HUB_OFFLINE')) {
|
|
147
|
+
lines.splice(i + 1, 0, `${environmentIndent} - HF_HUB_OFFLINE=1`);
|
|
148
|
+
inEnvironment = false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Add volume mount after volumes section
|
|
152
|
+
if (currentService && trimmed === 'volumes:' && !content.includes('.cache/fastembed')) {
|
|
153
|
+
const volumeIndent = line.match(/^(\s+)/)?.[1] || ' ';
|
|
154
|
+
// Find next line with volume entry
|
|
155
|
+
if (lines[i + 1] && lines[i + 1].trim().startsWith('-')) {
|
|
156
|
+
lines.splice(i + 2, 0, `${volumeIndent} - ~/.cache/fastembed:/root/.cache/fastembed:ro`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Reset when we exit a service (next service or same-level key)
|
|
161
|
+
if (currentService && line.match(/^\s{0,2}\w+:/) && !trimmed.startsWith(`${currentService}:`)) {
|
|
162
|
+
currentService = null;
|
|
163
|
+
inEnvironment = false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Write back
|
|
168
|
+
fs.writeFileSync(dockerComposePath, lines.join('\n'));
|
|
169
|
+
this.log('docker-compose.yaml updated for offline mode', 'success');
|
|
170
|
+
return true;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this.log(`Failed to update docker-compose: ${error.message}`, 'error');
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
configureMCPServer() {
|
|
178
|
+
this.log('Configuring MCP server for offline model...', 'info');
|
|
179
|
+
|
|
180
|
+
const mcpRunScript = path.join(process.cwd(), 'mcp-server', 'run-mcp.sh');
|
|
181
|
+
|
|
182
|
+
if (!fs.existsSync(mcpRunScript)) {
|
|
183
|
+
this.log('run-mcp.sh not found, skipping configuration', 'warning');
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
let content = fs.readFileSync(mcpRunScript, 'utf8');
|
|
189
|
+
|
|
190
|
+
// Check if already configured
|
|
191
|
+
if (content.includes('HF_HUB_OFFLINE')) {
|
|
192
|
+
this.log('MCP server already configured for offline mode', 'success');
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Add exports at the beginning
|
|
197
|
+
const exports = `
|
|
198
|
+
# Offline FastEmbed configuration (auto-added by installer)
|
|
199
|
+
export HF_HUB_OFFLINE=1
|
|
200
|
+
export FASTEMBED_CACHE_PATH="$HOME/.cache/fastembed"
|
|
201
|
+
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
// Insert after shebang
|
|
205
|
+
content = content.replace(/(#!\/bin\/bash\n)/, `$1${exports}`);
|
|
206
|
+
|
|
207
|
+
fs.writeFileSync(mcpRunScript, content);
|
|
208
|
+
this.log('MCP server configured for offline mode', 'success');
|
|
209
|
+
return true;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
this.log(`Failed to update run-mcp.sh: ${error.message}`, 'error');
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async run() {
|
|
217
|
+
this.log('🔍 Checking FastEmbed model availability...', 'info');
|
|
218
|
+
|
|
219
|
+
// Check if model already exists
|
|
220
|
+
if (this.checkModelExists()) {
|
|
221
|
+
this.log('FastEmbed model already installed ✓', 'success');
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Test HuggingFace connectivity
|
|
226
|
+
const hfAccessible = await this.testHuggingFace();
|
|
227
|
+
|
|
228
|
+
if (!hfAccessible) {
|
|
229
|
+
this.log('Corporate proxy detected - using Google Cloud Storage mirror', 'warning');
|
|
230
|
+
|
|
231
|
+
// Download from GCS
|
|
232
|
+
if (!this.downloadFromGCS()) {
|
|
233
|
+
this.log('Failed to download model from GCS', 'error');
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Configure for offline use
|
|
238
|
+
this.configureDockerCompose();
|
|
239
|
+
this.configureMCPServer();
|
|
240
|
+
|
|
241
|
+
this.log('✅ FastEmbed configured for offline use', 'success');
|
|
242
|
+
this.log('Your installation will now work behind corporate proxies', 'info');
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// HuggingFace is accessible, let Python handle the download
|
|
247
|
+
this.log('HuggingFace accessible - standard installation will work', 'info');
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Run if called directly
|
|
253
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
254
|
+
const fallback = new FastEmbedFallback();
|
|
255
|
+
fallback.run().catch(error => {
|
|
256
|
+
console.error('FastEmbed fallback failed:', error);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export default FastEmbedFallback;
|
package/installer/postinstall.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname, join } from 'path';
|
|
5
|
-
import StatuslineSetup from './statusline-setup.js';
|
|
6
5
|
|
|
7
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
7
|
const __dirname = dirname(__filename);
|
|
@@ -10,18 +9,24 @@ const __dirname = dirname(__filename);
|
|
|
10
9
|
// Only show message if not in development
|
|
11
10
|
if (!process.cwd().includes('claude-self-reflect')) {
|
|
12
11
|
console.log('\n🎉 Claude Self-Reflect installed!\n');
|
|
13
|
-
console.log('
|
|
12
|
+
console.log('🔍 Checking installation...\n');
|
|
14
13
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
console.log('✅
|
|
21
|
-
|
|
22
|
-
console.log('
|
|
23
|
-
|
|
14
|
+
// Import and run update manager for comprehensive setup
|
|
15
|
+
import('./update-manager.js').then(module => {
|
|
16
|
+
const UpdateManager = module.default;
|
|
17
|
+
const manager = new UpdateManager();
|
|
18
|
+
manager.run().then(() => {
|
|
19
|
+
console.log('\n✅ Installation complete!');
|
|
20
|
+
console.log('\n📋 Next steps:');
|
|
21
|
+
console.log(' 1. Run: claude-self-reflect setup');
|
|
22
|
+
console.log(' 2. Configure your embedding preferences');
|
|
23
|
+
console.log(' 3. Start using Claude with perfect memory!\n');
|
|
24
|
+
}).catch(error => {
|
|
25
|
+
console.log('\n⚠️ Setup encountered issues:', error.message);
|
|
26
|
+
console.log(' Run "claude-self-reflect update" to fix any problems\n');
|
|
27
|
+
});
|
|
24
28
|
}).catch(error => {
|
|
25
|
-
console.log('⚠️
|
|
29
|
+
console.log('⚠️ Could not run automatic setup');
|
|
30
|
+
console.log(' Run "claude-self-reflect setup" to configure manually\n');
|
|
26
31
|
});
|
|
27
32
|
}
|
|
@@ -23,6 +23,34 @@ class StatuslineSetup {
|
|
|
23
23
|
this.statuslineBackup = path.join(this.claudeDir, 'statusline-wrapper.sh.backup');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
checkCCStatusline() {
|
|
27
|
+
try {
|
|
28
|
+
execSync('npm list -g cc-statusline', { stdio: 'ignore' });
|
|
29
|
+
this.log('cc-statusline is installed', 'success');
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
this.log('cc-statusline not found', 'warning');
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
installCCStatusline() {
|
|
38
|
+
if (this.checkCCStatusline()) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.log('Installing cc-statusline...', 'info');
|
|
43
|
+
try {
|
|
44
|
+
execSync('npm install -g cc-statusline', { stdio: 'inherit' });
|
|
45
|
+
this.log('cc-statusline installed successfully', 'success');
|
|
46
|
+
return true;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
this.log(`Failed to install cc-statusline: ${error.message}`, 'error');
|
|
49
|
+
this.log('Statusline features will not be available', 'warning');
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
26
54
|
log(message, type = 'info') {
|
|
27
55
|
const colors = {
|
|
28
56
|
info: '\x1b[36m',
|
|
@@ -34,6 +62,15 @@ class StatuslineSetup {
|
|
|
34
62
|
}
|
|
35
63
|
|
|
36
64
|
checkPrerequisites() {
|
|
65
|
+
// Check npm is available
|
|
66
|
+
try {
|
|
67
|
+
execSync('npm --version', { stdio: 'ignore' });
|
|
68
|
+
} catch {
|
|
69
|
+
this.log('npm is required but not found', 'error');
|
|
70
|
+
this.log('Please install Node.js and npm from nodejs.org', 'error');
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
37
74
|
// Check if Claude Code directory exists
|
|
38
75
|
if (!fs.existsSync(this.claudeDir)) {
|
|
39
76
|
this.log('Claude Code directory not found. Please ensure Claude Code is installed.', 'warning');
|
|
@@ -67,15 +104,48 @@ class StatuslineSetup {
|
|
|
67
104
|
}
|
|
68
105
|
}
|
|
69
106
|
|
|
107
|
+
// Try user-local installation first (no sudo needed)
|
|
108
|
+
const userBin = path.join(this.homeDir, 'bin');
|
|
109
|
+
const userLocalBin = path.join(userBin, 'csr-status');
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(userBin)) {
|
|
112
|
+
fs.mkdirSync(userBin, { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Create symlink in ~/bin
|
|
116
|
+
try {
|
|
117
|
+
if (fs.existsSync(userLocalBin)) {
|
|
118
|
+
fs.unlinkSync(userLocalBin);
|
|
119
|
+
}
|
|
120
|
+
fs.symlinkSync(this.csrScript, userLocalBin);
|
|
121
|
+
// Note: Don't chmod symlink - ensure source script is executable instead
|
|
122
|
+
|
|
123
|
+
this.log('csr-status installed to ~/bin (no sudo required)', 'success');
|
|
124
|
+
this.log('Add ~/bin to PATH: export PATH="$HOME/bin:$PATH"', 'info');
|
|
125
|
+
|
|
126
|
+
// Check if ~/bin is in PATH
|
|
127
|
+
const pathDirs = process.env.PATH.split(':');
|
|
128
|
+
if (!pathDirs.includes(userBin) && !pathDirs.includes('~/bin')) {
|
|
129
|
+
this.log('⚠️ Add this to ~/.bashrc or ~/.zshrc:', 'warning');
|
|
130
|
+
this.log(' export PATH="$HOME/bin:$PATH"', 'info');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return true;
|
|
134
|
+
} catch (userError) {
|
|
135
|
+
this.log(`User-local install failed: ${userError.message}`, 'warning');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Fallback to global install if user has sudo access
|
|
139
|
+
if (needsSudo) {
|
|
140
|
+
this.log('Attempting global install (requires sudo)...', 'info');
|
|
141
|
+
this.log('Corporate machines may not allow this - using user-local is fine', 'info');
|
|
142
|
+
}
|
|
143
|
+
|
|
70
144
|
// Create symlink
|
|
71
145
|
const cmd = needsSudo
|
|
72
146
|
? `sudo ln -sf "${this.csrScript}" "${this.globalBin}"`
|
|
73
147
|
: `ln -sf "${this.csrScript}" "${this.globalBin}"`;
|
|
74
148
|
|
|
75
|
-
if (needsSudo) {
|
|
76
|
-
this.log('Installing global csr-status command (may require password)...', 'info');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
149
|
execSync(cmd, { stdio: 'inherit' });
|
|
80
150
|
|
|
81
151
|
// Make executable
|
|
@@ -87,10 +157,10 @@ class StatuslineSetup {
|
|
|
87
157
|
this.log('Global csr-status command installed successfully', 'success');
|
|
88
158
|
return true;
|
|
89
159
|
} catch (error) {
|
|
90
|
-
this.log(
|
|
91
|
-
this.log('
|
|
92
|
-
this.log(
|
|
93
|
-
return false;
|
|
160
|
+
this.log('Statusline installation skipped (no sudo access)', 'info');
|
|
161
|
+
this.log('This is normal on corporate machines', 'info');
|
|
162
|
+
this.log('✅ Core MCP search works fine without statusline!', 'success');
|
|
163
|
+
return false; // Return false but don't treat as critical error
|
|
94
164
|
}
|
|
95
165
|
}
|
|
96
166
|
|
|
@@ -228,6 +298,7 @@ class StatuslineSetup {
|
|
|
228
298
|
}
|
|
229
299
|
|
|
230
300
|
const steps = [
|
|
301
|
+
{ name: 'Install cc-statusline', fn: () => this.installCCStatusline() },
|
|
231
302
|
{ name: 'Install global command', fn: () => this.installGlobalCommand() },
|
|
232
303
|
{ name: 'Patch statusline wrapper', fn: () => this.patchStatuslineWrapper() },
|
|
233
304
|
{ name: 'Validate integration', fn: () => this.validateIntegration() }
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Update Manager
|
|
4
|
+
* Detects and fixes missing features, updates configurations
|
|
5
|
+
* Ensures user's installation matches the package capabilities
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import StatuslineSetup from './statusline-setup.js';
|
|
14
|
+
import FastEmbedFallback from './fastembed-fallback.js';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
|
|
19
|
+
class UpdateManager {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.homeDir = os.homedir();
|
|
22
|
+
this.packageRoot = path.dirname(__dirname);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
log(message, type = 'info') {
|
|
26
|
+
const colors = {
|
|
27
|
+
info: '\x1b[36m',
|
|
28
|
+
success: '\x1b[32m',
|
|
29
|
+
warning: '\x1b[33m',
|
|
30
|
+
error: '\x1b[31m'
|
|
31
|
+
};
|
|
32
|
+
const icons = {
|
|
33
|
+
info: 'ℹ',
|
|
34
|
+
success: '✓',
|
|
35
|
+
warning: '⚠',
|
|
36
|
+
error: '✗'
|
|
37
|
+
};
|
|
38
|
+
console.log(`${colors[type]}${icons[type]} ${message}\x1b[0m`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Feature checks
|
|
42
|
+
async checkCCStatusline() {
|
|
43
|
+
try {
|
|
44
|
+
execSync('npm list -g cc-statusline', { stdio: 'ignore' });
|
|
45
|
+
return { installed: true, name: 'cc-statusline', critical: false };
|
|
46
|
+
} catch {
|
|
47
|
+
return { installed: false, name: 'cc-statusline', critical: false, fix: () => this.installCCStatusline() };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async checkCSRStatusScript() {
|
|
52
|
+
const userBin = path.join(this.homeDir, 'bin', 'csr-status');
|
|
53
|
+
const globalBin = '/usr/local/bin/csr-status';
|
|
54
|
+
|
|
55
|
+
const exists = fs.existsSync(userBin) || fs.existsSync(globalBin);
|
|
56
|
+
return {
|
|
57
|
+
installed: exists,
|
|
58
|
+
name: 'csr-status command',
|
|
59
|
+
critical: false,
|
|
60
|
+
fix: () => this.installCSRStatus()
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async checkFastEmbedModel() {
|
|
65
|
+
const modelPath = path.join(this.homeDir, '.cache', 'fastembed', 'sentence-transformers-all-MiniLM-L6-v2');
|
|
66
|
+
const exists = fs.existsSync(modelPath);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
installed: exists,
|
|
70
|
+
name: 'FastEmbed model',
|
|
71
|
+
critical: true,
|
|
72
|
+
fix: () => this.installFastEmbed()
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async checkASTGrep() {
|
|
77
|
+
// Check for both 'ast-grep' (brew) and 'sg' (npm) binaries
|
|
78
|
+
try {
|
|
79
|
+
execSync('ast-grep --version', { stdio: 'ignore' });
|
|
80
|
+
return { installed: true, name: 'AST-Grep', critical: false };
|
|
81
|
+
} catch {
|
|
82
|
+
// Try 'sg' binary (npm install -g @ast-grep/cli)
|
|
83
|
+
try {
|
|
84
|
+
execSync('sg --version', { stdio: 'ignore' });
|
|
85
|
+
return { installed: true, name: 'AST-Grep (sg)', critical: false };
|
|
86
|
+
} catch {
|
|
87
|
+
return {
|
|
88
|
+
installed: false,
|
|
89
|
+
name: 'AST-Grep (optional)',
|
|
90
|
+
critical: false,
|
|
91
|
+
fix: () => this.suggestASTGrep()
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async checkDocker() {
|
|
98
|
+
try {
|
|
99
|
+
execSync('docker info', { stdio: 'ignore' });
|
|
100
|
+
return { installed: true, name: 'Docker', critical: true };
|
|
101
|
+
} catch {
|
|
102
|
+
return {
|
|
103
|
+
installed: false,
|
|
104
|
+
name: 'Docker',
|
|
105
|
+
critical: true,
|
|
106
|
+
fix: null,
|
|
107
|
+
error: 'Docker is required. Install Docker Desktop from docker.com'
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async checkQdrant() {
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch('http://localhost:6333');
|
|
115
|
+
if (response.ok) {
|
|
116
|
+
return { installed: true, name: 'Qdrant', critical: true };
|
|
117
|
+
}
|
|
118
|
+
} catch {}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
installed: false,
|
|
122
|
+
name: 'Qdrant',
|
|
123
|
+
critical: true,
|
|
124
|
+
fix: () => this.startQdrant()
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async checkDockerComposeConfig() {
|
|
129
|
+
const composePath = path.join(this.packageRoot, 'docker-compose.yaml');
|
|
130
|
+
|
|
131
|
+
if (!fs.existsSync(composePath)) {
|
|
132
|
+
return { installed: false, name: 'docker-compose.yaml', critical: true, fix: null };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const content = fs.readFileSync(composePath, 'utf8');
|
|
136
|
+
|
|
137
|
+
// Check for offline FastEmbed configuration
|
|
138
|
+
const hasOfflineConfig = content.includes('HF_HUB_OFFLINE') && content.includes('fastembed:ro');
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
installed: hasOfflineConfig,
|
|
142
|
+
name: 'Docker offline FastEmbed config',
|
|
143
|
+
critical: false,
|
|
144
|
+
fix: () => this.fixDockerConfig()
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Fix functions
|
|
149
|
+
async installCCStatusline() {
|
|
150
|
+
// Check npm is available
|
|
151
|
+
try {
|
|
152
|
+
execSync('npm --version', { stdio: 'ignore' });
|
|
153
|
+
} catch {
|
|
154
|
+
this.log('npm is required but not found', 'error');
|
|
155
|
+
this.log('Please install Node.js and npm from nodejs.org', 'error');
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.log('Installing cc-statusline...', 'info');
|
|
160
|
+
try {
|
|
161
|
+
execSync('npm install -g cc-statusline', { stdio: 'inherit' });
|
|
162
|
+
this.log('cc-statusline installed', 'success');
|
|
163
|
+
return true;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// Check for permission errors
|
|
166
|
+
const isPermissionError = error.code === 'EACCES' ||
|
|
167
|
+
error.code === 'EPERM' ||
|
|
168
|
+
(error.stderr && error.stderr.toString().includes('EACCES')) ||
|
|
169
|
+
(error.message && error.message.includes('permission'));
|
|
170
|
+
|
|
171
|
+
if (isPermissionError) {
|
|
172
|
+
this.log('Failed to install cc-statusline: Permission denied', 'error');
|
|
173
|
+
this.log('Try one of:', 'info');
|
|
174
|
+
this.log(' 1. Run with elevated privileges: sudo npm install -g cc-statusline', 'info');
|
|
175
|
+
this.log(' 2. Use a node version manager like nvm', 'info');
|
|
176
|
+
this.log(' 3. Configure npm for user-local installs', 'info');
|
|
177
|
+
} else {
|
|
178
|
+
this.log(`Failed to install cc-statusline: ${error.message}`, 'error');
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async installCSRStatus() {
|
|
185
|
+
this.log('Setting up csr-status command...', 'info');
|
|
186
|
+
const statuslineSetup = new StatuslineSetup();
|
|
187
|
+
return await statuslineSetup.installGlobalCommand();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async installFastEmbed() {
|
|
191
|
+
this.log('Setting up FastEmbed model...', 'info');
|
|
192
|
+
const fallback = new FastEmbedFallback();
|
|
193
|
+
return await fallback.run();
|
|
194
|
+
}
|
|
195
|
+
|
|
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
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async startQdrant() {
|
|
204
|
+
this.log('Starting Qdrant...', 'info');
|
|
205
|
+
try {
|
|
206
|
+
execSync('docker compose up -d qdrant', {
|
|
207
|
+
cwd: this.packageRoot,
|
|
208
|
+
stdio: 'inherit'
|
|
209
|
+
});
|
|
210
|
+
this.log('Qdrant started', 'success');
|
|
211
|
+
return true;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
this.log(`Failed to start Qdrant: ${error.message}`, 'error');
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async fixDockerConfig() {
|
|
219
|
+
this.log('Updating Docker Compose configuration...', 'info');
|
|
220
|
+
const fallback = new FastEmbedFallback();
|
|
221
|
+
return await fallback.configureDockerCompose();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async run() {
|
|
225
|
+
this.log('Analyzing installation...', 'info');
|
|
226
|
+
console.log();
|
|
227
|
+
|
|
228
|
+
// Run all checks
|
|
229
|
+
const checks = [
|
|
230
|
+
this.checkDocker(),
|
|
231
|
+
this.checkQdrant(),
|
|
232
|
+
this.checkFastEmbedModel(),
|
|
233
|
+
this.checkDockerComposeConfig(),
|
|
234
|
+
this.checkCCStatusline(),
|
|
235
|
+
this.checkCSRStatusScript(),
|
|
236
|
+
this.checkASTGrep()
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
const results = await Promise.all(checks);
|
|
240
|
+
|
|
241
|
+
// Categorize results
|
|
242
|
+
const missing = results.filter(r => !r.installed);
|
|
243
|
+
const critical = missing.filter(r => r.critical);
|
|
244
|
+
const optional = missing.filter(r => !r.critical);
|
|
245
|
+
|
|
246
|
+
// Display status
|
|
247
|
+
console.log('📊 Installation Status:\n');
|
|
248
|
+
for (const result of results) {
|
|
249
|
+
const icon = result.installed ? '✅' : (result.critical ? '❌' : '⚠️ ');
|
|
250
|
+
const status = result.installed ? 'Installed' : 'Missing';
|
|
251
|
+
console.log(`${icon} ${result.name}: ${status}`);
|
|
252
|
+
}
|
|
253
|
+
console.log();
|
|
254
|
+
|
|
255
|
+
// Handle critical issues
|
|
256
|
+
const unresolvedCritical = [];
|
|
257
|
+
if (critical.length > 0) {
|
|
258
|
+
this.log(`Found ${critical.length} critical issue(s) that need fixing`, 'error');
|
|
259
|
+
console.log();
|
|
260
|
+
|
|
261
|
+
for (const issue of critical) {
|
|
262
|
+
if (issue.error) {
|
|
263
|
+
this.log(issue.error, 'error');
|
|
264
|
+
unresolvedCritical.push(issue);
|
|
265
|
+
} else if (issue.fix) {
|
|
266
|
+
this.log(`Fixing: ${issue.name}...`, 'info');
|
|
267
|
+
const success = await issue.fix();
|
|
268
|
+
if (!success) {
|
|
269
|
+
this.log(`Failed to fix: ${issue.name}`, 'error');
|
|
270
|
+
unresolvedCritical.push(issue);
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
// Issue has no fix and no error message - track as unresolved
|
|
274
|
+
this.log(`${issue.name} is missing (no automatic fix available)`, 'error');
|
|
275
|
+
unresolvedCritical.push(issue);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Handle optional issues
|
|
281
|
+
if (optional.length > 0) {
|
|
282
|
+
this.log(`Found ${optional.length} optional feature(s) to install`, 'warning');
|
|
283
|
+
console.log();
|
|
284
|
+
|
|
285
|
+
for (const issue of optional) {
|
|
286
|
+
if (issue.fix) {
|
|
287
|
+
this.log(`Installing: ${issue.name}...`, 'info');
|
|
288
|
+
await issue.fix();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Final status
|
|
294
|
+
console.log();
|
|
295
|
+
if (unresolvedCritical.length === 0 && optional.length === 0) {
|
|
296
|
+
this.log('All features are up to date! ✨', 'success');
|
|
297
|
+
} else if (unresolvedCritical.length === 0) {
|
|
298
|
+
this.log('Core features are working. Optional features installed.', 'success');
|
|
299
|
+
} else {
|
|
300
|
+
this.log('Please address critical issues before using Claude Self-Reflect', 'error');
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
console.log();
|
|
305
|
+
this.log('Run "claude-self-reflect doctor" for detailed diagnostics', 'info');
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Run if called directly
|
|
310
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
311
|
+
const manager = new UpdateManager();
|
|
312
|
+
manager.run().catch(error => {
|
|
313
|
+
console.error('Update failed:', error);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export default UpdateManager;
|
package/package.json
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST-Grep Utilities
|
|
3
|
+
Gracefully handles AST-Grep availability for code quality features
|
|
4
|
+
"""
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
def is_ast_grep_installed() -> bool:
|
|
11
|
+
"""Check if ast-grep or sg (npm install) is available in PATH"""
|
|
12
|
+
return shutil.which('ast-grep') is not None or shutil.which('sg') is not None
|
|
13
|
+
|
|
14
|
+
def get_ast_grep_command() -> Optional[str]:
|
|
15
|
+
"""Get the available AST-Grep command (ast-grep or sg)"""
|
|
16
|
+
if shutil.which('ast-grep'):
|
|
17
|
+
return 'ast-grep'
|
|
18
|
+
elif shutil.which('sg'):
|
|
19
|
+
return 'sg'
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
def get_ast_grep_version() -> Optional[str]:
|
|
23
|
+
"""Get installed AST-Grep version"""
|
|
24
|
+
cmd = get_ast_grep_command()
|
|
25
|
+
if not cmd:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
result = subprocess.run(
|
|
30
|
+
[cmd, '--version'],
|
|
31
|
+
capture_output=True,
|
|
32
|
+
text=True,
|
|
33
|
+
timeout=5
|
|
34
|
+
)
|
|
35
|
+
if result.returncode == 0:
|
|
36
|
+
return result.stdout.strip()
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
def check_ast_grep_or_warn(feature_name: str = "Quality analysis") -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Check if AST-Grep is installed, warn if not
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
True if installed, False otherwise
|
|
48
|
+
"""
|
|
49
|
+
if is_ast_grep_installed():
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
print(f"⚠️ {feature_name} requires AST-Grep")
|
|
53
|
+
print(" This is an optional feature for advanced code quality analysis")
|
|
54
|
+
print()
|
|
55
|
+
print(" To install:")
|
|
56
|
+
print(" • macOS: brew install ast-grep")
|
|
57
|
+
print(" • Linux: npm install -g @ast-grep/cli")
|
|
58
|
+
print(" • Or: Download from https://github.com/ast-grep/ast-grep/releases")
|
|
59
|
+
print()
|
|
60
|
+
print(" ✅ Core MCP functionality works fine without it!")
|
|
61
|
+
print()
|
|
62
|
+
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
def ast_grep_required(func):
|
|
66
|
+
"""Decorator to mark functions that require AST-Grep"""
|
|
67
|
+
@wraps(func)
|
|
68
|
+
def wrapper(*args, **kwargs):
|
|
69
|
+
if not check_ast_grep_or_warn(func.__name__):
|
|
70
|
+
return None
|
|
71
|
+
return func(*args, **kwargs)
|
|
72
|
+
|
|
73
|
+
return wrapper
|
|
74
|
+
|
|
75
|
+
# Example usage in scripts:
|
|
76
|
+
# from shared.ast_grep_utils import ast_grep_required, check_ast_grep_or_warn
|
|
77
|
+
#
|
|
78
|
+
# @ast_grep_required
|
|
79
|
+
# def run_quality_analysis():
|
|
80
|
+
# # AST-Grep code here
|
|
81
|
+
# pass
|
|
82
|
+
#
|
|
83
|
+
# if __name__ == "__main__":
|
|
84
|
+
# if check_ast_grep_or_warn("Code Quality Scanner"):
|
|
85
|
+
# # Run AST-Grep features
|
|
86
|
+
# pass
|
|
87
|
+
# else:
|
|
88
|
+
# # Fallback to basic analysis
|
|
89
|
+
# print("Running basic quality checks without AST-Grep...")
|