claude-self-reflect 4.0.3 → 5.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.
- package/docker-compose.yaml +3 -1
- package/installer/setup-wizard-docker.js +64 -9
- package/mcp-server/src/code_reload_tool.py +207 -98
- package/mcp-server/src/parallel_search.py +7 -6
- package/mcp-server/src/rich_formatting.py +10 -6
- package/mcp-server/src/safe_getters.py +217 -0
- package/mcp-server/src/search_tools.py +30 -8
- package/package.json +1 -1
- package/scripts/ast_grep_final_analyzer.py +16 -6
- package/scripts/csr-status +120 -17
- package/scripts/debug-august-parsing.py +5 -1
- package/scripts/debug-project-resolver.py +3 -3
- package/scripts/import-conversations-unified.py +292 -821
- package/scripts/session_quality_tracker.py +10 -0
- package/scripts/unified_state_manager.py +7 -4
- package/mcp-server/src/test_quality.py +0 -153
package/docker-compose.yaml
CHANGED
|
@@ -18,7 +18,9 @@ services:
|
|
|
18
18
|
- "${QDRANT_PORT:-6333}:6333"
|
|
19
19
|
volumes:
|
|
20
20
|
- qdrant_data:/qdrant/storage
|
|
21
|
-
|
|
21
|
+
# Note: Using CONFIG_PATH variable to support global npm installs (fixes #71)
|
|
22
|
+
# macOS Docker Desktop restricts mounts to /Users, /Volumes, /private, /tmp
|
|
23
|
+
- ${CONFIG_PATH:-~/.claude-self-reflect/config}/qdrant-config.yaml:/qdrant/config/config.yaml:ro
|
|
22
24
|
environment:
|
|
23
25
|
- QDRANT__LOG_LEVEL=INFO
|
|
24
26
|
- QDRANT__SERVICE__HTTP_PORT=6333
|
|
@@ -71,23 +71,64 @@ async function checkDocker() {
|
|
|
71
71
|
try {
|
|
72
72
|
safeExec('docker', ['info'], { stdio: 'ignore' });
|
|
73
73
|
console.log('✅ Docker is installed and running');
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
// Check docker compose
|
|
76
76
|
try {
|
|
77
77
|
safeExec('docker', ['compose', 'version'], { stdio: 'ignore' });
|
|
78
|
-
console.log('✅ Docker Compose
|
|
78
|
+
console.log('✅ Docker Compose is available');
|
|
79
79
|
return true;
|
|
80
80
|
} catch {
|
|
81
|
-
console.log('❌ Docker Compose
|
|
82
|
-
console.log(' Please update Docker Desktop to
|
|
81
|
+
console.log('❌ Docker Compose not found');
|
|
82
|
+
console.log(' Please update Docker Desktop to include Compose v2');
|
|
83
83
|
return false;
|
|
84
84
|
}
|
|
85
85
|
} catch {
|
|
86
|
-
console.log('❌ Docker is not running or not installed');
|
|
87
|
-
console.log('
|
|
88
|
-
console.log('
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
console.log('❌ Docker is not running or not installed\n');
|
|
87
|
+
console.log('📋 Claude Self-Reflect requires Docker Desktop');
|
|
88
|
+
console.log(' (Includes Docker Engine + Compose - everything you need)\n');
|
|
89
|
+
|
|
90
|
+
const platform = process.platform;
|
|
91
|
+
const arch = process.arch;
|
|
92
|
+
|
|
93
|
+
if (platform === 'darwin') {
|
|
94
|
+
const archType = arch === 'arm64' ? 'Apple Silicon (M1/M2/M3/M4)' : 'Intel';
|
|
95
|
+
console.log(`🍎 macOS (${archType}) Installation:\n`);
|
|
96
|
+
|
|
97
|
+
if (arch === 'arm64') {
|
|
98
|
+
console.log(' Download: https://desktop.docker.com/mac/main/arm64/Docker.dmg');
|
|
99
|
+
} else {
|
|
100
|
+
console.log(' Download: https://desktop.docker.com/mac/main/amd64/Docker.dmg');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(' 1. Open the downloaded Docker.dmg');
|
|
104
|
+
console.log(' 2. Drag Docker.app to Applications folder');
|
|
105
|
+
console.log(' 3. Launch Docker Desktop from Applications');
|
|
106
|
+
console.log(' 4. Wait for Docker to start (whale icon in menu bar)');
|
|
107
|
+
console.log(' 5. Re-run: claude-self-reflect setup\n');
|
|
108
|
+
|
|
109
|
+
} else if (platform === 'win32') {
|
|
110
|
+
console.log('🪟 Windows Installation:\n');
|
|
111
|
+
console.log(' Download: https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe');
|
|
112
|
+
console.log(' 1. Run the installer');
|
|
113
|
+
console.log(' 2. Follow installation prompts');
|
|
114
|
+
console.log(' 3. Restart computer if prompted');
|
|
115
|
+
console.log(' 4. Launch Docker Desktop');
|
|
116
|
+
console.log(' 5. Re-run: claude-self-reflect setup\n');
|
|
117
|
+
|
|
118
|
+
} else {
|
|
119
|
+
console.log('🐧 Linux Installation:\n');
|
|
120
|
+
console.log(' Install Docker Engine (includes Compose):');
|
|
121
|
+
console.log(' • Ubuntu/Debian: https://docs.docker.com/engine/install/ubuntu/');
|
|
122
|
+
console.log(' • Fedora: https://docs.docker.com/engine/install/fedora/');
|
|
123
|
+
console.log(' • Arch: https://wiki.archlinux.org/title/docker');
|
|
124
|
+
console.log(' • CentOS: https://docs.docker.com/engine/install/centos/\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log('ℹ️ Docker Desktop is free for:');
|
|
128
|
+
console.log(' • Personal use');
|
|
129
|
+
console.log(' • Small businesses (<250 employees, <$10M revenue)');
|
|
130
|
+
console.log(' • Education and open source projects\n');
|
|
131
|
+
|
|
91
132
|
return false;
|
|
92
133
|
}
|
|
93
134
|
}
|
|
@@ -123,6 +164,20 @@ async function configureEnvironment() {
|
|
|
123
164
|
} catch {
|
|
124
165
|
// No old config directory, nothing to migrate
|
|
125
166
|
}
|
|
167
|
+
|
|
168
|
+
// Copy qdrant-config.yaml from npm package to user config directory
|
|
169
|
+
// This is critical for global npm installs where Docker cannot mount from /opt/homebrew
|
|
170
|
+
const sourceQdrantConfig = join(projectRoot, 'config', 'qdrant-config.yaml');
|
|
171
|
+
const targetQdrantConfig = join(userConfigDir, 'qdrant-config.yaml');
|
|
172
|
+
try {
|
|
173
|
+
await fs.copyFile(sourceQdrantConfig, targetQdrantConfig);
|
|
174
|
+
console.log('✅ Qdrant config copied to user directory');
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (err.code !== 'ENOENT') {
|
|
177
|
+
console.log('⚠️ Could not copy qdrant-config.yaml:', err.message);
|
|
178
|
+
console.log(' Docker may have issues starting Qdrant service');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
126
181
|
} catch (error) {
|
|
127
182
|
console.log(`❌ Could not create config directory: ${error.message}`);
|
|
128
183
|
console.log(' This may cause Docker mount issues. Please check permissions.');
|
|
@@ -5,11 +5,22 @@ import sys
|
|
|
5
5
|
import importlib
|
|
6
6
|
import logging
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Dict, List, Optional
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
9
|
from fastmcp import Context
|
|
10
10
|
from pydantic import Field
|
|
11
11
|
import hashlib
|
|
12
12
|
import json
|
|
13
|
+
import asyncio
|
|
14
|
+
|
|
15
|
+
# Import security module - handle both relative and absolute imports
|
|
16
|
+
try:
|
|
17
|
+
from .security_patches import ModuleWhitelist
|
|
18
|
+
except ImportError:
|
|
19
|
+
try:
|
|
20
|
+
from security_patches import ModuleWhitelist
|
|
21
|
+
except ImportError:
|
|
22
|
+
# Security module is required - fail closed, not open
|
|
23
|
+
raise RuntimeError("Security module 'security_patches' is required for code reload functionality")
|
|
13
24
|
|
|
14
25
|
logger = logging.getLogger(__name__)
|
|
15
26
|
|
|
@@ -19,20 +30,36 @@ class CodeReloader:
|
|
|
19
30
|
|
|
20
31
|
def __init__(self):
|
|
21
32
|
"""Initialize the code reloader."""
|
|
22
|
-
self.module_hashes: Dict[str, str] = {}
|
|
23
|
-
self.reload_history: List[Dict] = []
|
|
24
33
|
self.cache_dir = Path.home() / '.claude-self-reflect' / 'reload_cache'
|
|
25
34
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
self.hash_file = self.cache_dir / 'module_hashes.json'
|
|
36
|
+
self._lock = asyncio.Lock() # Thread safety for async operations
|
|
37
|
+
|
|
38
|
+
# Load persisted hashes from disk with error handling
|
|
39
|
+
if self.hash_file.exists():
|
|
40
|
+
try:
|
|
41
|
+
with open(self.hash_file, 'r') as f:
|
|
42
|
+
self.module_hashes: Dict[str, str] = json.load(f)
|
|
43
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
44
|
+
logger.error(f"Failed to load module hashes: {e}. Starting fresh.")
|
|
45
|
+
self.module_hashes: Dict[str, str] = {}
|
|
46
|
+
else:
|
|
47
|
+
self.module_hashes: Dict[str, str] = {}
|
|
48
|
+
|
|
49
|
+
self.reload_history: List[Dict] = []
|
|
50
|
+
logger.info(f"CodeReloader initialized with {len(self.module_hashes)} cached hashes")
|
|
28
51
|
|
|
29
52
|
def _get_file_hash(self, filepath: Path) -> str:
|
|
30
53
|
"""Get SHA256 hash of a file."""
|
|
31
54
|
with open(filepath, 'rb') as f:
|
|
32
55
|
return hashlib.sha256(f.read()).hexdigest()
|
|
33
56
|
|
|
34
|
-
def
|
|
35
|
-
"""Detect which modules have changed since last check.
|
|
57
|
+
def _detect_changed_modules(self) -> List[str]:
|
|
58
|
+
"""Detect which modules have changed since last check.
|
|
59
|
+
|
|
60
|
+
This method ONLY detects changes, it does NOT update the stored hashes.
|
|
61
|
+
Use _update_module_hashes() to update hashes after successful reload.
|
|
62
|
+
"""
|
|
36
63
|
changed = []
|
|
37
64
|
src_dir = Path(__file__).parent
|
|
38
65
|
|
|
@@ -43,13 +70,61 @@ class CodeReloader:
|
|
|
43
70
|
module_name = f"src.{py_file.stem}"
|
|
44
71
|
current_hash = self._get_file_hash(py_file)
|
|
45
72
|
|
|
73
|
+
# Only detect changes, DO NOT update hashes here
|
|
46
74
|
if module_name in self.module_hashes:
|
|
47
75
|
if self.module_hashes[module_name] != current_hash:
|
|
48
76
|
changed.append(module_name)
|
|
77
|
+
logger.debug(f"Change detected in {module_name}: {self.module_hashes[module_name][:8]} -> {current_hash[:8]}")
|
|
78
|
+
else:
|
|
79
|
+
# New module not seen before
|
|
80
|
+
changed.append(module_name)
|
|
81
|
+
logger.debug(f"New module detected: {module_name}")
|
|
82
|
+
|
|
83
|
+
return changed
|
|
84
|
+
|
|
85
|
+
def _update_module_hashes(self, modules: Optional[List[str]] = None) -> None:
|
|
86
|
+
"""Update the stored hashes for specified modules or all modules.
|
|
87
|
+
|
|
88
|
+
This should be called AFTER successful reload to mark modules as up-to-date.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
modules: List of module names to update. If None, updates all modules.
|
|
92
|
+
"""
|
|
93
|
+
src_dir = Path(__file__).parent
|
|
94
|
+
updated = []
|
|
95
|
+
|
|
96
|
+
for py_file in src_dir.glob("*.py"):
|
|
97
|
+
if py_file.name == "__pycache__":
|
|
98
|
+
continue
|
|
49
99
|
|
|
100
|
+
module_name = f"src.{py_file.stem}"
|
|
101
|
+
|
|
102
|
+
# If specific modules provided, only update those
|
|
103
|
+
if modules is not None and module_name not in modules:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
current_hash = self._get_file_hash(py_file)
|
|
107
|
+
old_hash = self.module_hashes.get(module_name, "new")
|
|
50
108
|
self.module_hashes[module_name] = current_hash
|
|
109
|
+
|
|
110
|
+
if old_hash != current_hash:
|
|
111
|
+
updated.append(module_name)
|
|
112
|
+
logger.debug(f"Updated hash for {module_name}: {old_hash[:8] if old_hash != 'new' else 'new'} -> {current_hash[:8]}")
|
|
51
113
|
|
|
52
|
-
|
|
114
|
+
# Persist the updated hashes to disk using atomic write
|
|
115
|
+
temp_file = Path(str(self.hash_file) + '.tmp')
|
|
116
|
+
try:
|
|
117
|
+
with open(temp_file, 'w') as f:
|
|
118
|
+
json.dump(self.module_hashes, f, indent=2)
|
|
119
|
+
# Atomic rename on POSIX systems
|
|
120
|
+
temp_file.replace(self.hash_file)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(f"Failed to persist module hashes: {e}")
|
|
123
|
+
if temp_file.exists():
|
|
124
|
+
temp_file.unlink() # Clean up temp file on failure
|
|
125
|
+
|
|
126
|
+
if updated:
|
|
127
|
+
logger.info(f"Updated hashes for {len(updated)} modules: {', '.join(updated)}")
|
|
53
128
|
|
|
54
129
|
async def reload_modules(
|
|
55
130
|
self,
|
|
@@ -61,93 +136,98 @@ class CodeReloader:
|
|
|
61
136
|
|
|
62
137
|
await ctx.debug("Starting code reload process...")
|
|
63
138
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
response +=
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
139
|
+
async with self._lock: # Ensure thread safety for reload operations
|
|
140
|
+
try:
|
|
141
|
+
# Track what we're reloading
|
|
142
|
+
reload_targets = []
|
|
143
|
+
|
|
144
|
+
if auto_detect:
|
|
145
|
+
# Detect changed modules (without updating hashes)
|
|
146
|
+
changed = self._detect_changed_modules()
|
|
147
|
+
if changed:
|
|
148
|
+
reload_targets.extend(changed)
|
|
149
|
+
await ctx.debug(f"Auto-detected changes in: {changed}")
|
|
150
|
+
|
|
151
|
+
if modules:
|
|
152
|
+
# Add explicitly requested modules
|
|
153
|
+
reload_targets.extend(modules)
|
|
154
|
+
|
|
155
|
+
if not reload_targets:
|
|
156
|
+
return "📊 No modules to reload. All code is up to date!"
|
|
157
|
+
|
|
158
|
+
# Perform the reload
|
|
159
|
+
reloaded = []
|
|
160
|
+
failed = []
|
|
161
|
+
|
|
162
|
+
for module_name in reload_targets:
|
|
163
|
+
try:
|
|
164
|
+
# SECURITY FIX: Validate module is in whitelist
|
|
165
|
+
if not ModuleWhitelist.is_allowed_module(module_name):
|
|
166
|
+
logger.warning(f"Module not in whitelist, skipping: {module_name}")
|
|
167
|
+
failed.append((module_name, "Module not in whitelist"))
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
if module_name in sys.modules:
|
|
171
|
+
# Store old module reference for rollback
|
|
172
|
+
old_module = sys.modules[module_name]
|
|
173
|
+
|
|
174
|
+
# Reload the module
|
|
175
|
+
logger.info(f"Reloading module: {module_name}")
|
|
176
|
+
reloaded_module = importlib.reload(sys.modules[module_name])
|
|
177
|
+
|
|
178
|
+
# Update any global references if needed
|
|
179
|
+
self._update_global_references(module_name, reloaded_module)
|
|
180
|
+
|
|
181
|
+
reloaded.append(module_name)
|
|
182
|
+
await ctx.debug(f"✅ Reloaded: {module_name}")
|
|
183
|
+
else:
|
|
184
|
+
# Module not loaded yet, import it
|
|
185
|
+
importlib.import_module(module_name)
|
|
186
|
+
reloaded.append(module_name)
|
|
187
|
+
await ctx.debug(f"✅ Imported: {module_name}")
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.error(f"Failed to reload {module_name}: {e}", exc_info=True)
|
|
191
|
+
failed.append((module_name, str(e)))
|
|
192
|
+
await ctx.debug(f"❌ Failed: {module_name} - {e}")
|
|
193
|
+
|
|
194
|
+
# Update hashes ONLY for successfully reloaded modules
|
|
195
|
+
if reloaded:
|
|
196
|
+
self._update_module_hashes(reloaded)
|
|
197
|
+
await ctx.debug(f"Updated hashes for {len(reloaded)} successfully reloaded modules")
|
|
198
|
+
|
|
199
|
+
# Record reload history
|
|
200
|
+
self.reload_history.append({
|
|
201
|
+
"timestamp": os.environ.get('MCP_REQUEST_ID', 'unknown'),
|
|
202
|
+
"reloaded": reloaded,
|
|
203
|
+
"failed": failed
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
# Build response
|
|
207
|
+
response = "🔄 **Code Reload Results**\n\n"
|
|
208
|
+
|
|
209
|
+
if reloaded:
|
|
210
|
+
response += f"**Successfully Reloaded ({len(reloaded)}):**\n"
|
|
211
|
+
for module in reloaded:
|
|
212
|
+
response += f"- ✅ {module}\n"
|
|
213
|
+
response += "\n"
|
|
214
|
+
|
|
215
|
+
if failed:
|
|
216
|
+
response += f"**Failed to Reload ({len(failed)}):**\n"
|
|
217
|
+
for module, error in failed:
|
|
218
|
+
response += f"- ❌ {module}: {error}\n"
|
|
219
|
+
response += "\n"
|
|
220
|
+
|
|
221
|
+
response += "**Important Notes:**\n"
|
|
222
|
+
response += "- Class instances created before reload keep old code\n"
|
|
223
|
+
response += "- New requests will use the reloaded code\n"
|
|
224
|
+
response += "- Some changes may require full restart (e.g., new tools)\n"
|
|
225
|
+
|
|
226
|
+
return response
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
logger.error(f"Code reload failed: {e}", exc_info=True)
|
|
230
|
+
return f"❌ Code reload failed: {str(e)}"
|
|
151
231
|
|
|
152
232
|
def _update_global_references(self, module_name: str, new_module):
|
|
153
233
|
"""Update global references after module reload."""
|
|
@@ -171,8 +251,8 @@ class CodeReloader:
|
|
|
171
251
|
"""Get the current reload status and history."""
|
|
172
252
|
|
|
173
253
|
try:
|
|
174
|
-
# Check for changed files
|
|
175
|
-
changed = self.
|
|
254
|
+
# Check for changed files (WITHOUT updating hashes)
|
|
255
|
+
changed = self._detect_changed_modules()
|
|
176
256
|
|
|
177
257
|
response = "📊 **Code Reload Status**\n\n"
|
|
178
258
|
|
|
@@ -224,6 +304,24 @@ class CodeReloader:
|
|
|
224
304
|
logger.error(f"Failed to clear cache: {e}", exc_info=True)
|
|
225
305
|
return f"❌ Failed to clear cache: {str(e)}"
|
|
226
306
|
|
|
307
|
+
async def force_update_hashes(self, ctx: Context) -> str:
|
|
308
|
+
"""Force update all module hashes to current state.
|
|
309
|
+
|
|
310
|
+
This is useful when you want to mark all current code as 'baseline'
|
|
311
|
+
without actually reloading anything.
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
await ctx.debug("Force updating all module hashes...")
|
|
315
|
+
|
|
316
|
+
# Update all module hashes
|
|
317
|
+
self._update_module_hashes(modules=None)
|
|
318
|
+
|
|
319
|
+
return f"✅ Force updated hashes for all {len(self.module_hashes)} tracked modules"
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
logger.error(f"Failed to force update hashes: {e}", exc_info=True)
|
|
323
|
+
return f"❌ Failed to force update hashes: {str(e)}"
|
|
324
|
+
|
|
227
325
|
|
|
228
326
|
def register_code_reload_tool(mcp, get_embedding_manager):
|
|
229
327
|
"""Register the code reloading tool with the MCP server."""
|
|
@@ -257,6 +355,8 @@ def register_code_reload_tool(mcp, get_embedding_manager):
|
|
|
257
355
|
|
|
258
356
|
Shows which files have been modified since last reload and
|
|
259
357
|
the history of recent reload operations.
|
|
358
|
+
|
|
359
|
+
Note: This only checks for changes, it does not update the stored hashes.
|
|
260
360
|
"""
|
|
261
361
|
return await reloader.get_reload_status(ctx)
|
|
262
362
|
|
|
@@ -267,5 +367,14 @@ def register_code_reload_tool(mcp, get_embedding_manager):
|
|
|
267
367
|
Useful when reload isn't working due to cached bytecode.
|
|
268
368
|
"""
|
|
269
369
|
return await reloader.clear_python_cache(ctx)
|
|
370
|
+
|
|
371
|
+
@mcp.tool()
|
|
372
|
+
async def force_update_module_hashes(ctx: Context) -> str:
|
|
373
|
+
"""Force update all module hashes to mark current code as baseline.
|
|
374
|
+
|
|
375
|
+
Use this when you want to ignore current changes and treat
|
|
376
|
+
the current state as the new baseline without reloading.
|
|
377
|
+
"""
|
|
378
|
+
return await reloader.force_update_hashes(ctx)
|
|
270
379
|
|
|
271
|
-
logger.info("Code reload tools registered successfully")
|
|
380
|
+
logger.info("Code reload tools registered successfully")
|
|
@@ -8,6 +8,7 @@ import time
|
|
|
8
8
|
from typing import List, Dict, Any, Optional, Tuple
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
import logging
|
|
11
|
+
from .safe_getters import safe_get_list, safe_get_str
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
@@ -176,9 +177,9 @@ async def search_single_collection(
|
|
|
176
177
|
'collection_name': collection_name,
|
|
177
178
|
'raw_payload': point.payload, # Renamed from 'payload' for consistency
|
|
178
179
|
'code_patterns': point.payload.get('code_patterns'),
|
|
179
|
-
'files_analyzed': point.payload
|
|
180
|
-
'tools_used':
|
|
181
|
-
'concepts': point.payload
|
|
180
|
+
'files_analyzed': safe_get_list(point.payload, 'files_analyzed'),
|
|
181
|
+
'tools_used': safe_get_list(point.payload, 'tools_used'),
|
|
182
|
+
'concepts': safe_get_list(point.payload, 'concepts')
|
|
182
183
|
}
|
|
183
184
|
results.append(search_result)
|
|
184
185
|
else:
|
|
@@ -219,9 +220,9 @@ async def search_single_collection(
|
|
|
219
220
|
'collection_name': collection_name,
|
|
220
221
|
'raw_payload': point.payload,
|
|
221
222
|
'code_patterns': point.payload.get('code_patterns'),
|
|
222
|
-
'files_analyzed': point.payload
|
|
223
|
-
'tools_used':
|
|
224
|
-
'concepts': point.payload
|
|
223
|
+
'files_analyzed': safe_get_list(point.payload, 'files_analyzed'),
|
|
224
|
+
'tools_used': safe_get_list(point.payload, 'tools_used'),
|
|
225
|
+
'concepts': safe_get_list(point.payload, 'concepts')
|
|
225
226
|
}
|
|
226
227
|
results.append(search_result)
|
|
227
228
|
|
|
@@ -5,6 +5,7 @@ import time
|
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
6
|
from typing import List, Dict, Any, Optional
|
|
7
7
|
import logging
|
|
8
|
+
from .safe_getters import safe_get_list, safe_get_str
|
|
8
9
|
|
|
9
10
|
logger = logging.getLogger(__name__)
|
|
10
11
|
|
|
@@ -114,16 +115,19 @@ def format_search_results_rich(
|
|
|
114
115
|
concept_frequency = {}
|
|
115
116
|
|
|
116
117
|
for result in results:
|
|
117
|
-
# Count file modifications
|
|
118
|
-
|
|
118
|
+
# Count file modifications - using safe_get_list for consistency
|
|
119
|
+
files = safe_get_list(result, 'files_analyzed')
|
|
120
|
+
for file in files:
|
|
119
121
|
file_frequency[file] = file_frequency.get(file, 0) + 1
|
|
120
122
|
|
|
121
|
-
# Count tool usage
|
|
122
|
-
|
|
123
|
+
# Count tool usage - using safe_get_list for consistency
|
|
124
|
+
tools = safe_get_list(result, 'tools_used')
|
|
125
|
+
for tool in tools:
|
|
123
126
|
tool_frequency[tool] = tool_frequency.get(tool, 0) + 1
|
|
124
127
|
|
|
125
|
-
# Count concepts
|
|
126
|
-
|
|
128
|
+
# Count concepts - using safe_get_list for consistency
|
|
129
|
+
concepts = safe_get_list(result, 'concepts')
|
|
130
|
+
for concept in concepts:
|
|
127
131
|
concept_frequency[concept] = concept_frequency.get(concept, 0) + 1
|
|
128
132
|
|
|
129
133
|
# Show most frequently modified files
|