claude-self-reflect 3.3.0 → 3.3.1
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/.claude/agents/claude-self-reflect-test.md +426 -11
- package/installer/cli.js +16 -0
- package/installer/postinstall.js +14 -0
- package/installer/statusline-setup.js +289 -0
- package/mcp-server/run-mcp.sh +28 -4
- package/mcp-server/src/parallel_search.py +16 -82
- package/mcp-server/src/reflection_tools.py +13 -8
- package/mcp-server/src/search_tools.py +90 -42
- package/mcp-server/src/temporal_tools.py +10 -3
- package/package.json +6 -1
- package/scripts/ast_grep_final_analyzer.py +325 -0
- package/scripts/ast_grep_unified_registry.py +556 -0
- package/scripts/csr-status +366 -0
- package/scripts/import-conversations-unified.py +104 -23
- package/scripts/session_quality_tracker.py +481 -0
- package/scripts/streaming-watcher.py +140 -5
- package/scripts/update_patterns.py +334 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code Statusline Integration Setup
|
|
4
|
+
* Automatically configures CC statusline to show CSR metrics
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
|
|
16
|
+
class StatuslineSetup {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.homeDir = os.homedir();
|
|
19
|
+
this.claudeDir = path.join(this.homeDir, '.claude');
|
|
20
|
+
this.csrScript = path.join(path.dirname(__dirname), 'scripts', 'csr-status');
|
|
21
|
+
this.globalBin = '/usr/local/bin/csr-status';
|
|
22
|
+
this.statuslineWrapper = path.join(this.claudeDir, 'statusline-wrapper.sh');
|
|
23
|
+
this.statuslineBackup = path.join(this.claudeDir, 'statusline-wrapper.sh.backup');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
log(message, type = 'info') {
|
|
27
|
+
const colors = {
|
|
28
|
+
info: '\x1b[36m',
|
|
29
|
+
success: '\x1b[32m',
|
|
30
|
+
warning: '\x1b[33m',
|
|
31
|
+
error: '\x1b[31m'
|
|
32
|
+
};
|
|
33
|
+
console.log(`${colors[type]}${message}\x1b[0m`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
checkPrerequisites() {
|
|
37
|
+
// Check if Claude Code directory exists
|
|
38
|
+
if (!fs.existsSync(this.claudeDir)) {
|
|
39
|
+
this.log('Claude Code directory not found. Please ensure Claude Code is installed.', 'warning');
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if csr-status script exists
|
|
44
|
+
if (!fs.existsSync(this.csrScript)) {
|
|
45
|
+
this.log('CSR status script not found. Please ensure the package is installed correctly.', 'error');
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
installGlobalCommand() {
|
|
53
|
+
try {
|
|
54
|
+
// Check if we need sudo
|
|
55
|
+
const needsSudo = !this.canWriteTo('/usr/local/bin');
|
|
56
|
+
|
|
57
|
+
if (fs.existsSync(this.globalBin)) {
|
|
58
|
+
// Check if it's already pointing to our script
|
|
59
|
+
try {
|
|
60
|
+
const target = fs.readlinkSync(this.globalBin);
|
|
61
|
+
if (target === this.csrScript) {
|
|
62
|
+
this.log('Global csr-status command already installed', 'success');
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
// Not a symlink or can't read, will replace
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Create symlink
|
|
71
|
+
const cmd = needsSudo
|
|
72
|
+
? `sudo ln -sf "${this.csrScript}" "${this.globalBin}"`
|
|
73
|
+
: `ln -sf "${this.csrScript}" "${this.globalBin}"`;
|
|
74
|
+
|
|
75
|
+
if (needsSudo) {
|
|
76
|
+
this.log('Installing global csr-status command (may require password)...', 'info');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
80
|
+
|
|
81
|
+
// Make executable
|
|
82
|
+
const chmodCmd = needsSudo
|
|
83
|
+
? `sudo chmod +x "${this.globalBin}"`
|
|
84
|
+
: `chmod +x "${this.globalBin}"`;
|
|
85
|
+
execSync(chmodCmd);
|
|
86
|
+
|
|
87
|
+
this.log('Global csr-status command installed successfully', 'success');
|
|
88
|
+
return true;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
this.log(`Failed to install global command: ${error.message}`, 'warning');
|
|
91
|
+
this.log('You can manually install by running:', 'info');
|
|
92
|
+
this.log(` sudo ln -sf "${this.csrScript}" "${this.globalBin}"`, 'info');
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
canWriteTo(dir) {
|
|
98
|
+
try {
|
|
99
|
+
fs.accessSync(dir, fs.constants.W_OK);
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
patchStatuslineWrapper() {
|
|
107
|
+
if (!fs.existsSync(this.statuslineWrapper)) {
|
|
108
|
+
this.log('Claude Code statusline wrapper not found. Statusline integration skipped.', 'warning');
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// Read current wrapper
|
|
114
|
+
let content = fs.readFileSync(this.statuslineWrapper, 'utf8');
|
|
115
|
+
|
|
116
|
+
// Check if already patched with new approach
|
|
117
|
+
if (content.includes('CSR compact status instead of MCP bar')) {
|
|
118
|
+
this.log('Statusline wrapper already patched', 'success');
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Create backup
|
|
123
|
+
if (!fs.existsSync(this.statuslineBackup)) {
|
|
124
|
+
fs.copyFileSync(this.statuslineWrapper, this.statuslineBackup);
|
|
125
|
+
this.log(`Backup created: ${this.statuslineBackup}`, 'info');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Find and replace the MCP bar generation section
|
|
129
|
+
const barPattern = /# Create mini progress bar[\s\S]*?MCP_COLOR="\\033\[1;90m" # Gray/;
|
|
130
|
+
|
|
131
|
+
if (content.match(barPattern)) {
|
|
132
|
+
// Replace the entire bar generation section
|
|
133
|
+
const replacement = `# Use CSR compact status instead of MCP bar
|
|
134
|
+
# This shows both import percentage and code quality in format: [100% <time>][🟢:A+]
|
|
135
|
+
CSR_COMPACT=$(csr-status --compact 2>/dev/null || echo "")
|
|
136
|
+
|
|
137
|
+
if [[ -n "$CSR_COMPACT" ]]; then
|
|
138
|
+
MCP_STATUS="$CSR_COMPACT"
|
|
139
|
+
|
|
140
|
+
# Color based on content
|
|
141
|
+
if [[ "$CSR_COMPACT" == *"100%"* ]]; then
|
|
142
|
+
MCP_COLOR="\\033[1;32m" # Green for complete
|
|
143
|
+
elif [[ "$CSR_COMPACT" == *"[🟢:"* ]]; then
|
|
144
|
+
MCP_COLOR="\\033[1;32m" # Green for good quality
|
|
145
|
+
elif [[ "$CSR_COMPACT" == *"[🟡:"* ]]; then
|
|
146
|
+
MCP_COLOR="\\033[1;33m" # Yellow for medium quality
|
|
147
|
+
elif [[ "$CSR_COMPACT" == *"[🔴:"* ]]; then
|
|
148
|
+
MCP_COLOR="\\033[1;31m" # Red for poor quality
|
|
149
|
+
else
|
|
150
|
+
MCP_COLOR="\\033[1;36m" # Cyan default
|
|
151
|
+
fi
|
|
152
|
+
else
|
|
153
|
+
MCP_STATUS=""
|
|
154
|
+
MCP_COLOR="\\033[1;90m" # Gray`;
|
|
155
|
+
|
|
156
|
+
content = content.replace(barPattern, replacement);
|
|
157
|
+
|
|
158
|
+
// Write back
|
|
159
|
+
fs.writeFileSync(this.statuslineWrapper, content);
|
|
160
|
+
this.log('Statusline wrapper patched successfully (replaced MCP bar)', 'success');
|
|
161
|
+
return true;
|
|
162
|
+
} else {
|
|
163
|
+
// Fallback: Look for the PERCENTAGE check
|
|
164
|
+
const altPattern = /if \[\[ "\$PERCENTAGE" != "null"[\s\S]*?MCP_COLOR="\\033\[1;90m" # Gray/;
|
|
165
|
+
|
|
166
|
+
if (content.match(altPattern)) {
|
|
167
|
+
const replacement = `# Use CSR compact status instead of MCP bar
|
|
168
|
+
# This shows both import percentage and code quality in format: [100% <time>][🟢:A+]
|
|
169
|
+
CSR_COMPACT=$(csr-status --compact 2>/dev/null || echo "")
|
|
170
|
+
|
|
171
|
+
if [[ -n "$CSR_COMPACT" ]]; then
|
|
172
|
+
MCP_STATUS="$CSR_COMPACT"
|
|
173
|
+
|
|
174
|
+
# Color based on content
|
|
175
|
+
if [[ "$CSR_COMPACT" == *"100%"* ]]; then
|
|
176
|
+
MCP_COLOR="\\033[1;32m" # Green for complete
|
|
177
|
+
elif [[ "$CSR_COMPACT" == *"[🟢:"* ]]; then
|
|
178
|
+
MCP_COLOR="\\033[1;32m" # Green for good quality
|
|
179
|
+
elif [[ "$CSR_COMPACT" == *"[🟡:"* ]]; then
|
|
180
|
+
MCP_COLOR="\\033[1;33m" # Yellow for medium quality
|
|
181
|
+
elif [[ "$CSR_COMPACT" == *"[🔴:"* ]]; then
|
|
182
|
+
MCP_COLOR="\\033[1;31m" # Red for poor quality
|
|
183
|
+
else
|
|
184
|
+
MCP_COLOR="\\033[1;36m" # Cyan default
|
|
185
|
+
fi
|
|
186
|
+
else
|
|
187
|
+
MCP_STATUS=""
|
|
188
|
+
MCP_COLOR="\\033[1;90m" # Gray`;
|
|
189
|
+
|
|
190
|
+
content = content.replace(altPattern, replacement);
|
|
191
|
+
|
|
192
|
+
// Write back
|
|
193
|
+
fs.writeFileSync(this.statuslineWrapper, content);
|
|
194
|
+
this.log('Statusline wrapper patched successfully (replaced PERCENTAGE section)', 'success');
|
|
195
|
+
return true;
|
|
196
|
+
} else {
|
|
197
|
+
this.log('Could not find MCP bar section to replace', 'warning');
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
this.log(`Failed to patch statusline wrapper: ${error.message}`, 'error');
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
validateIntegration() {
|
|
208
|
+
try {
|
|
209
|
+
// Test csr-status command
|
|
210
|
+
const output = execSync('csr-status --compact', { encoding: 'utf8' });
|
|
211
|
+
if (output) {
|
|
212
|
+
this.log(`CSR status output: ${output.trim()}`, 'success');
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
this.log('CSR status command not working properly', 'warning');
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async run() {
|
|
223
|
+
this.log('🚀 Setting up Claude Code Statusline Integration...', 'info');
|
|
224
|
+
|
|
225
|
+
if (!this.checkPrerequisites()) {
|
|
226
|
+
this.log('Prerequisites check failed', 'error');
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const steps = [
|
|
231
|
+
{ name: 'Install global command', fn: () => this.installGlobalCommand() },
|
|
232
|
+
{ name: 'Patch statusline wrapper', fn: () => this.patchStatuslineWrapper() },
|
|
233
|
+
{ name: 'Validate integration', fn: () => this.validateIntegration() }
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
let success = true;
|
|
237
|
+
for (const step of steps) {
|
|
238
|
+
this.log(`\n📋 ${step.name}...`, 'info');
|
|
239
|
+
if (!step.fn()) {
|
|
240
|
+
success = false;
|
|
241
|
+
this.log(`❌ ${step.name} failed`, 'error');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (success) {
|
|
246
|
+
this.log('\n✅ Statusline integration completed successfully!', 'success');
|
|
247
|
+
this.log('The CSR status will now appear in your Claude Code statusline.', 'info');
|
|
248
|
+
this.log('Format: [import%][🟢:grade] for compact quality metrics', 'info');
|
|
249
|
+
} else {
|
|
250
|
+
this.log('\n⚠️ Statusline integration completed with warnings', 'warning');
|
|
251
|
+
this.log('Some features may need manual configuration.', 'warning');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return success;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Restore original statusline if needed
|
|
258
|
+
restore() {
|
|
259
|
+
if (fs.existsSync(this.statuslineBackup)) {
|
|
260
|
+
try {
|
|
261
|
+
fs.copyFileSync(this.statuslineBackup, this.statuslineWrapper);
|
|
262
|
+
this.log('Statusline wrapper restored from backup', 'success');
|
|
263
|
+
return true;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
this.log(`Failed to restore: ${error.message}`, 'error');
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
this.log('No backup found to restore', 'warning');
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Run if called directly
|
|
276
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
277
|
+
const setup = new StatuslineSetup();
|
|
278
|
+
|
|
279
|
+
if (process.argv[2] === '--restore') {
|
|
280
|
+
setup.restore();
|
|
281
|
+
} else {
|
|
282
|
+
setup.run().catch(error => {
|
|
283
|
+
console.error('Setup failed:', error);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export default StatuslineSetup;
|
package/mcp-server/run-mcp.sh
CHANGED
|
@@ -11,8 +11,17 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
|
11
11
|
# Navigate to the mcp-server directory
|
|
12
12
|
cd "$SCRIPT_DIR"
|
|
13
13
|
|
|
14
|
-
# CRITICAL:
|
|
15
|
-
#
|
|
14
|
+
# CRITICAL: Environment variables priority:
|
|
15
|
+
# 1. Command-line args from Claude Code (already in environment)
|
|
16
|
+
# 2. .env file (only for missing values)
|
|
17
|
+
# 3. Defaults (as fallback)
|
|
18
|
+
|
|
19
|
+
# Store any command-line provided values BEFORE loading .env
|
|
20
|
+
CMDLINE_VOYAGE_KEY="${VOYAGE_KEY:-}"
|
|
21
|
+
CMDLINE_PREFER_LOCAL="${PREFER_LOCAL_EMBEDDINGS:-}"
|
|
22
|
+
CMDLINE_QDRANT_URL="${QDRANT_URL:-}"
|
|
23
|
+
|
|
24
|
+
# Load .env file for any missing values
|
|
16
25
|
if [ -f "../.env" ]; then
|
|
17
26
|
echo "[DEBUG] Loading .env file from project root" >&2
|
|
18
27
|
set -a # Export all variables
|
|
@@ -22,8 +31,23 @@ else
|
|
|
22
31
|
echo "[DEBUG] No .env file found, using defaults" >&2
|
|
23
32
|
fi
|
|
24
33
|
|
|
25
|
-
#
|
|
26
|
-
|
|
34
|
+
# Restore command-line values (they take precedence)
|
|
35
|
+
if [ ! -z "$CMDLINE_VOYAGE_KEY" ]; then
|
|
36
|
+
export VOYAGE_KEY="$CMDLINE_VOYAGE_KEY"
|
|
37
|
+
echo "[DEBUG] Using command-line VOYAGE_KEY" >&2
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ ! -z "$CMDLINE_PREFER_LOCAL" ]; then
|
|
41
|
+
export PREFER_LOCAL_EMBEDDINGS="$CMDLINE_PREFER_LOCAL"
|
|
42
|
+
echo "[DEBUG] Using command-line PREFER_LOCAL_EMBEDDINGS: $PREFER_LOCAL_EMBEDDINGS" >&2
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if [ ! -z "$CMDLINE_QDRANT_URL" ]; then
|
|
46
|
+
export QDRANT_URL="$CMDLINE_QDRANT_URL"
|
|
47
|
+
echo "[DEBUG] Using command-line QDRANT_URL: $QDRANT_URL" >&2
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Set smart defaults ONLY if still not set
|
|
27
51
|
if [ -z "$QDRANT_URL" ]; then
|
|
28
52
|
export QDRANT_URL="http://localhost:6333"
|
|
29
53
|
echo "[DEBUG] Using default QDRANT_URL: $QDRANT_URL" >&2
|
|
@@ -64,84 +64,11 @@ async def search_single_collection(
|
|
|
64
64
|
DECAY_SCALE_DAYS = constants.get('DECAY_SCALE_DAYS', 90)
|
|
65
65
|
DECAY_WEIGHT = constants.get('DECAY_WEIGHT', 0.3)
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# Build query using Qdrant's Fusion and RankFusion
|
|
74
|
-
fusion_query = models.Fusion(
|
|
75
|
-
fusion=models.RankFusion.RRF,
|
|
76
|
-
queries=[
|
|
77
|
-
# Semantic similarity query
|
|
78
|
-
models.NearestQuery(
|
|
79
|
-
nearest=query_embedding,
|
|
80
|
-
score_threshold=min_score
|
|
81
|
-
),
|
|
82
|
-
# Time decay query using context pair
|
|
83
|
-
models.ContextQuery(
|
|
84
|
-
context=[
|
|
85
|
-
models.ContextPair(
|
|
86
|
-
positive=models.DiscoverQuery(
|
|
87
|
-
target=query_embedding,
|
|
88
|
-
context=[
|
|
89
|
-
models.ContextPair(
|
|
90
|
-
positive=models.DatetimeRange(
|
|
91
|
-
gt=datetime.now().isoformat(),
|
|
92
|
-
lt=(datetime.now().timestamp() + half_life_seconds)
|
|
93
|
-
)
|
|
94
|
-
)
|
|
95
|
-
]
|
|
96
|
-
)
|
|
97
|
-
)
|
|
98
|
-
]
|
|
99
|
-
)
|
|
100
|
-
]
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
# Execute search with native decay
|
|
104
|
-
search_results = await qdrant_client.query_points(
|
|
105
|
-
collection_name=collection_name,
|
|
106
|
-
query=fusion_query,
|
|
107
|
-
limit=limit,
|
|
108
|
-
with_payload=True
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
# Process results
|
|
112
|
-
for point in search_results.points:
|
|
113
|
-
# Process each point and add to results
|
|
114
|
-
raw_timestamp = point.payload.get('timestamp', datetime.now().isoformat())
|
|
115
|
-
clean_timestamp = raw_timestamp.replace('Z', '+00:00') if raw_timestamp.endswith('Z') else raw_timestamp
|
|
116
|
-
|
|
117
|
-
point_project = point.payload.get('project', collection_name.replace('conv_', '').replace('_voyage', '').replace('_local', ''))
|
|
118
|
-
|
|
119
|
-
# Apply project filtering
|
|
120
|
-
if target_project != 'all' and not is_reflection_collection:
|
|
121
|
-
if point_project != target_project:
|
|
122
|
-
normalized_target = target_project.replace('-', '_')
|
|
123
|
-
normalized_point = point_project.replace('-', '_')
|
|
124
|
-
if not (normalized_point == normalized_target or
|
|
125
|
-
point_project.endswith(f"/{target_project}") or
|
|
126
|
-
point_project.endswith(f"-{target_project}") or
|
|
127
|
-
normalized_point.endswith(f"_{normalized_target}") or
|
|
128
|
-
normalized_point.endswith(f"/{normalized_target}")):
|
|
129
|
-
continue
|
|
130
|
-
|
|
131
|
-
# Create SearchResult
|
|
132
|
-
search_result = {
|
|
133
|
-
'id': str(point.id),
|
|
134
|
-
'score': point.score,
|
|
135
|
-
'timestamp': clean_timestamp,
|
|
136
|
-
'role': point.payload.get('start_role', point.payload.get('role', 'unknown')),
|
|
137
|
-
'excerpt': (point.payload.get('text', '')[:350] + '...'
|
|
138
|
-
if len(point.payload.get('text', '')) > 350
|
|
139
|
-
else point.payload.get('text', '')),
|
|
140
|
-
'project_name': point_project,
|
|
141
|
-
'payload': point.payload
|
|
142
|
-
}
|
|
143
|
-
results.append(search_result)
|
|
144
|
-
|
|
67
|
+
# NOTE: Native decay API is not available in current Qdrant, fall back to client-side
|
|
68
|
+
# The Fusion/RankFusion API was experimental and removed, always use client-side decay
|
|
69
|
+
if should_use_decay and False: # Disabled until Qdrant provides stable decay API
|
|
70
|
+
# This code path is intentionally disabled
|
|
71
|
+
pass
|
|
145
72
|
else:
|
|
146
73
|
# Standard search without native decay or client-side decay
|
|
147
74
|
search_results = await qdrant_client.search(
|
|
@@ -220,17 +147,24 @@ async def search_single_collection(
|
|
|
220
147
|
continue
|
|
221
148
|
logger.debug(f"Keeping point: project '{point_project}' matches target '{target_project}'")
|
|
222
149
|
|
|
223
|
-
# Create SearchResult
|
|
150
|
+
# Create SearchResult with consistent structure
|
|
224
151
|
search_result = {
|
|
225
152
|
'id': str(point.id),
|
|
226
153
|
'score': adjusted_score,
|
|
227
154
|
'timestamp': clean_timestamp,
|
|
228
155
|
'role': point.payload.get('start_role', point.payload.get('role', 'unknown')),
|
|
229
|
-
'excerpt': (point.payload.get('text', '')[:350] + '...'
|
|
230
|
-
if len(point.payload.get('text', '')) > 350
|
|
156
|
+
'excerpt': (point.payload.get('text', '')[:350] + '...'
|
|
157
|
+
if len(point.payload.get('text', '')) > 350
|
|
231
158
|
else point.payload.get('text', '')),
|
|
232
159
|
'project_name': point_project,
|
|
233
|
-
'
|
|
160
|
+
'conversation_id': point.payload.get('conversation_id'),
|
|
161
|
+
'base_conversation_id': point.payload.get('base_conversation_id'),
|
|
162
|
+
'collection_name': collection_name,
|
|
163
|
+
'raw_payload': point.payload, # Renamed from 'payload' for consistency
|
|
164
|
+
'code_patterns': point.payload.get('code_patterns'),
|
|
165
|
+
'files_analyzed': point.payload.get('files_analyzed'),
|
|
166
|
+
'tools_used': list(point.payload.get('tools_used', [])) if isinstance(point.payload.get('tools_used'), set) else point.payload.get('tools_used'),
|
|
167
|
+
'concepts': point.payload.get('concepts')
|
|
234
168
|
}
|
|
235
169
|
results.append(search_result)
|
|
236
170
|
else:
|
|
@@ -44,9 +44,10 @@ class ReflectionTools:
|
|
|
44
44
|
await ctx.debug(f"Storing reflection with {len(tags)} tags")
|
|
45
45
|
|
|
46
46
|
try:
|
|
47
|
-
# Determine collection name based on
|
|
47
|
+
# Determine collection name based on active model type, not prefer_local
|
|
48
48
|
embedding_manager = self.get_embedding_manager()
|
|
49
|
-
|
|
49
|
+
# Use actual model_type to ensure consistency
|
|
50
|
+
embedding_type = embedding_manager.model_type or ("voyage" if embedding_manager.voyage_client else "local")
|
|
50
51
|
collection_name = f"reflections_{embedding_type}"
|
|
51
52
|
|
|
52
53
|
# Ensure reflections collection exists
|
|
@@ -57,8 +58,8 @@ class ReflectionTools:
|
|
|
57
58
|
# Collection doesn't exist, create it
|
|
58
59
|
await ctx.debug(f"Creating {collection_name} collection")
|
|
59
60
|
|
|
60
|
-
#
|
|
61
|
-
embedding_dim = embedding_manager.get_vector_dimension()
|
|
61
|
+
# Get embedding dimensions for the specific type
|
|
62
|
+
embedding_dim = embedding_manager.get_vector_dimension(force_type=embedding_type)
|
|
62
63
|
|
|
63
64
|
await self.qdrant_client.create_collection(
|
|
64
65
|
collection_name=collection_name,
|
|
@@ -67,10 +68,14 @@ class ReflectionTools:
|
|
|
67
68
|
distance=Distance.COSINE
|
|
68
69
|
)
|
|
69
70
|
)
|
|
70
|
-
|
|
71
|
-
# Generate embedding
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
|
|
72
|
+
# Generate embedding with the same forced type for consistency
|
|
73
|
+
embedding = await embedding_manager.generate_embedding(content, force_type=embedding_type)
|
|
74
|
+
|
|
75
|
+
# Guard against failed embeddings
|
|
76
|
+
if not embedding:
|
|
77
|
+
await ctx.debug("Failed to generate embedding for reflection")
|
|
78
|
+
return "Failed to store reflection: embedding generation failed"
|
|
74
79
|
|
|
75
80
|
# Create unique ID
|
|
76
81
|
reflection_id = hashlib.md5(f"{content}{datetime.now().isoformat()}".encode()).hexdigest()
|