Refactor FormattingService and SearchManager for table-based output
- Updated FormattingService to format search results as tables, including methods for formatting observations, sessions, and user prompts. - Removed JSON format handling from SearchManager and streamlined result formatting to consistently use table format. - Enhanced readability and consistency in search tips and formatting logic. - Introduced token estimation for observations and improved time formatting.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* FormattingService - Handles all formatting logic for search results
|
* FormattingService - Handles all formatting logic for search results
|
||||||
* Extracted from mcp-server.ts to follow worker service organization pattern
|
* Uses table format matching context-generator style for visual consistency
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
|
import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
|
||||||
import { logger } from '../../utils/logger.js';
|
import { TYPE_ICON_MAP, TYPE_WORK_EMOJI_MAP } from '../../constants/observation-metadata.js';
|
||||||
|
|
||||||
export type FormatType = 'index' | 'full';
|
// Token estimation constant (matches context-generator)
|
||||||
|
const CHARS_PER_TOKEN_ESTIMATE = 4;
|
||||||
|
|
||||||
export class FormattingService {
|
export class FormattingService {
|
||||||
/**
|
/**
|
||||||
@@ -15,226 +16,89 @@ export class FormattingService {
|
|||||||
formatSearchTips(): string {
|
formatSearchTips(): string {
|
||||||
return `\n---
|
return `\n---
|
||||||
💡 Search Strategy:
|
💡 Search Strategy:
|
||||||
ALWAYS search with index format FIRST to get an overview and identify relevant results.
|
1. Search with index to see titles, dates, IDs
|
||||||
This is critical for token efficiency - index format uses ~10x fewer tokens than full format.
|
2. Use timeline to get context around interesting results
|
||||||
|
3. Batch fetch full details: get_batch_observations(ids=[...])
|
||||||
|
|
||||||
Search workflow:
|
Tips:
|
||||||
1. Initial search: Use default (index) format to see titles, dates, and sources
|
• Filter by type: obs_type="bugfix,feature"
|
||||||
2. Review results: Identify which items are most relevant to your needs
|
• Filter by date: dateStart="2025-01-01"
|
||||||
3. Deep dive: Only then use format: "full" on specific items of interest
|
• Sort: orderBy="date_desc" or "date_asc"`;
|
||||||
4. Narrow down: Use filters (type, dateStart/dateEnd, concepts, files) to refine results
|
|
||||||
|
|
||||||
Other tips:
|
|
||||||
• To search by concept: Use find_by_concept tool
|
|
||||||
• To browse by type: Use find_by_type with ["decision", "feature", etc.]
|
|
||||||
• To sort by date: Use orderBy: "date_desc" or "date_asc"`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format observation as index entry (title, date, ID only)
|
* Format time from epoch (matches context-generator formatTime)
|
||||||
*/
|
*/
|
||||||
formatObservationIndex(obs: ObservationSearchResult, index: number): string {
|
private formatTime(epoch: number): string {
|
||||||
const title = obs.title || `Observation #${obs.id}`;
|
return new Date(epoch).toLocaleString('en-US', {
|
||||||
const date = new Date(obs.created_at_epoch).toLocaleString();
|
hour: 'numeric',
|
||||||
const type = obs.type ? `[${obs.type}]` : '';
|
minute: '2-digit',
|
||||||
|
hour12: true
|
||||||
return ` ${index + 1}. ${type} ${title}\n Date: ${date}\n Source: claude-mem://observation/${obs.id}`;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format session summary as index entry (title, date, ID only)
|
* Estimate read tokens for an observation
|
||||||
*/
|
*/
|
||||||
formatSessionIndex(session: SessionSummarySearchResult, index: number): string {
|
private estimateReadTokens(obs: ObservationSearchResult): number {
|
||||||
const title = session.request || `Session ${session.sdk_session_id?.substring(0, 8) || 'unknown'}`;
|
const size = (obs.title?.length || 0) +
|
||||||
const date = new Date(session.created_at_epoch).toLocaleString();
|
(obs.subtitle?.length || 0) +
|
||||||
|
(obs.narrative?.length || 0) +
|
||||||
return ` ${index + 1}. ${title}\n Date: ${date}\n Source: claude-mem://session/${session.sdk_session_id}`;
|
(obs.facts?.length || 0);
|
||||||
|
return Math.ceil(size / CHARS_PER_TOKEN_ESTIMATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format user prompt as index entry (full text - don't truncate context!)
|
* Format observation as table row
|
||||||
|
* | ID | Time | T | Title | Read | Work |
|
||||||
*/
|
*/
|
||||||
formatUserPromptIndex(prompt: UserPromptSearchResult, index: number): string {
|
formatObservationIndex(obs: ObservationSearchResult, _index: number): string {
|
||||||
const date = new Date(prompt.created_at_epoch).toLocaleString();
|
const id = `#${obs.id}`;
|
||||||
|
const time = this.formatTime(obs.created_at_epoch);
|
||||||
|
const icon = TYPE_ICON_MAP[obs.type as keyof typeof TYPE_ICON_MAP] || '•';
|
||||||
|
const title = obs.title || 'Untitled';
|
||||||
|
const readTokens = this.estimateReadTokens(obs);
|
||||||
|
const workEmoji = TYPE_WORK_EMOJI_MAP[obs.type as keyof typeof TYPE_WORK_EMOJI_MAP] || '🔍';
|
||||||
|
const workTokens = obs.discovery_tokens || 0;
|
||||||
|
const workDisplay = workTokens > 0 ? `${workEmoji} ${workTokens}` : '-';
|
||||||
|
|
||||||
return ` ${index + 1}. "${prompt.prompt_text}"\n Date: ${date} | Prompt #${prompt.prompt_number}\n Source: claude-mem://user-prompt/${prompt.id}`;
|
return `| ${id} | ${time} | ${icon} | ${title} | ~${readTokens} | ${workDisplay} |`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format observation as text content with metadata
|
* Format session summary as table row
|
||||||
|
* | ID | Time | T | Title | - | - |
|
||||||
*/
|
*/
|
||||||
formatObservationResult(obs: ObservationSearchResult): string {
|
formatSessionIndex(session: SessionSummarySearchResult, _index: number): string {
|
||||||
const title = obs.title || `Observation #${obs.id}`;
|
const id = `#S${session.id}`;
|
||||||
|
const time = this.formatTime(session.created_at_epoch);
|
||||||
// Build content from available fields
|
const icon = '🎯';
|
||||||
const contentParts: string[] = [];
|
|
||||||
contentParts.push(`## ${title}`);
|
|
||||||
contentParts.push(`*Source: claude-mem://observation/${obs.id}*`);
|
|
||||||
contentParts.push('');
|
|
||||||
|
|
||||||
if (obs.subtitle) {
|
|
||||||
contentParts.push(`**${obs.subtitle}**`);
|
|
||||||
contentParts.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obs.narrative) {
|
|
||||||
contentParts.push(obs.narrative);
|
|
||||||
contentParts.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obs.text) {
|
|
||||||
contentParts.push(obs.text);
|
|
||||||
contentParts.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add metadata
|
|
||||||
const metadata: string[] = [];
|
|
||||||
metadata.push(`Type: ${obs.type}`);
|
|
||||||
|
|
||||||
if (obs.facts) {
|
|
||||||
try {
|
|
||||||
const facts = JSON.parse(obs.facts);
|
|
||||||
if (facts.length > 0) {
|
|
||||||
metadata.push(`Facts: ${facts.join('; ')}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('FORMAT', 'Invalid JSON in facts field', { obsId: obs.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obs.concepts) {
|
|
||||||
try {
|
|
||||||
const concepts = JSON.parse(obs.concepts);
|
|
||||||
if (concepts.length > 0) {
|
|
||||||
metadata.push(`Concepts: ${concepts.join(', ')}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('FORMAT', 'Invalid JSON in concepts field', { obsId: obs.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obs.files_read || obs.files_modified) {
|
|
||||||
const files: string[] = [];
|
|
||||||
if (obs.files_read) {
|
|
||||||
try {
|
|
||||||
files.push(...JSON.parse(obs.files_read));
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('FORMAT', 'Invalid JSON in files_read field', { obsId: obs.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (obs.files_modified) {
|
|
||||||
try {
|
|
||||||
files.push(...JSON.parse(obs.files_modified));
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('FORMAT', 'Invalid JSON in files_modified field', { obsId: obs.id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (files.length > 0) {
|
|
||||||
metadata.push(`Files: ${[...new Set(files)].join(', ')}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metadata.length > 0) {
|
|
||||||
contentParts.push('---');
|
|
||||||
contentParts.push(metadata.join(' | '));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add date
|
|
||||||
const date = new Date(obs.created_at_epoch).toLocaleString();
|
|
||||||
contentParts.push('');
|
|
||||||
contentParts.push(`---`);
|
|
||||||
contentParts.push(`Date: ${date}`);
|
|
||||||
|
|
||||||
return contentParts.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format session summary as text content with metadata
|
|
||||||
*/
|
|
||||||
formatSessionResult(session: SessionSummarySearchResult): string {
|
|
||||||
const title = session.request || `Session ${session.sdk_session_id?.substring(0, 8) || 'unknown'}`;
|
const title = session.request || `Session ${session.sdk_session_id?.substring(0, 8) || 'unknown'}`;
|
||||||
|
|
||||||
// Build content from available fields
|
return `| ${id} | ${time} | ${icon} | ${title} | - | - |`;
|
||||||
const contentParts: string[] = [];
|
|
||||||
contentParts.push(`## ${title}`);
|
|
||||||
contentParts.push(`*Source: claude-mem://session/${session.sdk_session_id}*`);
|
|
||||||
contentParts.push('');
|
|
||||||
|
|
||||||
if (session.completed) {
|
|
||||||
contentParts.push(`**Completed:** ${session.completed}`);
|
|
||||||
contentParts.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.learned) {
|
|
||||||
contentParts.push(`**Learned:** ${session.learned}`);
|
|
||||||
contentParts.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.investigated) {
|
|
||||||
contentParts.push(`**Investigated:** ${session.investigated}`);
|
|
||||||
contentParts.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.next_steps) {
|
|
||||||
contentParts.push(`**Next Steps:** ${session.next_steps}`);
|
|
||||||
contentParts.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.notes) {
|
|
||||||
contentParts.push(`**Notes:** ${session.notes}`);
|
|
||||||
contentParts.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add metadata
|
|
||||||
const metadata: string[] = [];
|
|
||||||
|
|
||||||
if (session.files_read || session.files_edited) {
|
|
||||||
const files: string[] = [];
|
|
||||||
if (session.files_read) {
|
|
||||||
try {
|
|
||||||
files.push(...JSON.parse(session.files_read));
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('FORMAT', 'Invalid JSON in session files_read field', { sessionId: session.sdk_session_id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (session.files_edited) {
|
|
||||||
try {
|
|
||||||
files.push(...JSON.parse(session.files_edited));
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('FORMAT', 'Invalid JSON in session files_edited field', { sessionId: session.sdk_session_id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (files.length > 0) {
|
|
||||||
metadata.push(`Files: ${[...new Set(files)].join(', ')}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const date = new Date(session.created_at_epoch).toLocaleDateString();
|
|
||||||
metadata.push(`Date: ${date}`);
|
|
||||||
|
|
||||||
if (metadata.length > 0) {
|
|
||||||
contentParts.push('---');
|
|
||||||
contentParts.push(metadata.join(' | '));
|
|
||||||
}
|
|
||||||
|
|
||||||
return contentParts.join('\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format user prompt as text content with metadata
|
* Format user prompt as table row
|
||||||
|
* | ID | Time | T | Title | - | - |
|
||||||
*/
|
*/
|
||||||
formatUserPromptResult(prompt: UserPromptSearchResult): string {
|
formatUserPromptIndex(prompt: UserPromptSearchResult, _index: number): string {
|
||||||
const contentParts: string[] = [];
|
const id = `#P${prompt.id}`;
|
||||||
contentParts.push(`## User Prompt #${prompt.prompt_number}`);
|
const time = this.formatTime(prompt.created_at_epoch);
|
||||||
contentParts.push(`*Source: claude-mem://user-prompt/${prompt.id}*`);
|
const icon = '💬';
|
||||||
contentParts.push('');
|
// Truncate long prompts for table display
|
||||||
contentParts.push(prompt.prompt_text);
|
const title = prompt.prompt_text.length > 60
|
||||||
contentParts.push('');
|
? prompt.prompt_text.substring(0, 57) + '...'
|
||||||
contentParts.push('---');
|
: prompt.prompt_text;
|
||||||
|
|
||||||
const date = new Date(prompt.created_at_epoch).toLocaleString();
|
return `| ${id} | ${time} | ${icon} | ${title} | - | - |`;
|
||||||
contentParts.push(`Date: ${date}`);
|
}
|
||||||
|
|
||||||
return contentParts.join('\n');
|
/**
|
||||||
|
* Generate table header for observations
|
||||||
|
*/
|
||||||
|
formatTableHeader(): string {
|
||||||
|
return `| ID | Time | T | Title | Read | Work |
|
||||||
|
|-----|------|---|-------|------|------|`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export class SearchManager {
|
|||||||
try {
|
try {
|
||||||
// Normalize URL-friendly params to internal format
|
// Normalize URL-friendly params to internal format
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { query, format = 'index', type, obs_type, concepts, files, ...options } = normalized;
|
const { query, type, obs_type, concepts, files, ...options } = normalized;
|
||||||
let observations: ObservationSearchResult[] = [];
|
let observations: ObservationSearchResult[] = [];
|
||||||
let sessions: SessionSummarySearchResult[] = [];
|
let sessions: SessionSummarySearchResult[] = [];
|
||||||
let prompts: UserPromptSearchResult[] = [];
|
let prompts: UserPromptSearchResult[] = [];
|
||||||
@@ -198,13 +198,6 @@ export class SearchManager {
|
|||||||
const totalResults = observations.length + sessions.length + prompts.length;
|
const totalResults = observations.length + sessions.length + prompts.length;
|
||||||
|
|
||||||
if (totalResults === 0) {
|
if (totalResults === 0) {
|
||||||
if (format === 'json') {
|
|
||||||
return {
|
|
||||||
observations: [],
|
|
||||||
sessions: [],
|
|
||||||
prompts: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
@@ -236,46 +229,22 @@ export class SearchManager {
|
|||||||
// Apply limit across all types
|
// Apply limit across all types
|
||||||
const limitedResults = allResults.slice(0, options.limit || 20);
|
const limitedResults = allResults.slice(0, options.limit || 20);
|
||||||
|
|
||||||
// Format based on requested format
|
// Format as table
|
||||||
if (format === 'json') {
|
const header = `Found ${totalResults} result(s) matching "${query}" (${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts)\n\n${this.formatter.formatTableHeader()}`;
|
||||||
// Raw JSON format for exports
|
const formattedResults = limitedResults.map((item, i) => {
|
||||||
return {
|
if (item.type === 'observation') {
|
||||||
observations,
|
return this.formatter.formatObservationIndex(item.data, i);
|
||||||
sessions,
|
} else if (item.type === 'session') {
|
||||||
prompts
|
return this.formatter.formatSessionIndex(item.data, i);
|
||||||
};
|
} else {
|
||||||
}
|
return this.formatter.formatUserPromptIndex(item.data, i);
|
||||||
|
}
|
||||||
let combinedText: string;
|
});
|
||||||
if (format === 'index') {
|
|
||||||
const header = ` ⎿ Found ${totalResults} result(s) matching "${query}" (${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts):\n\n`;
|
|
||||||
const formattedResults = limitedResults.map((item, i) => {
|
|
||||||
if (item.type === 'observation') {
|
|
||||||
return this.formatter.formatObservationIndex(item.data, i);
|
|
||||||
} else if (item.type === 'session') {
|
|
||||||
return this.formatter.formatSessionIndex(item.data, i);
|
|
||||||
} else {
|
|
||||||
return this.formatter.formatUserPromptIndex(item.data, i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults = limitedResults.map(item => {
|
|
||||||
if (item.type === 'observation') {
|
|
||||||
return this.formatter.formatObservationResult(item.data);
|
|
||||||
} else if (item.type === 'session') {
|
|
||||||
return this.formatter.formatSessionResult(item.data);
|
|
||||||
} else {
|
|
||||||
return this.formatter.formatUserPromptResult(item.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -629,7 +598,7 @@ export class SearchManager {
|
|||||||
async decisions(args: any): Promise<any> {
|
async decisions(args: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { query, format = 'index', ...filters } = normalized;
|
const { query, ...filters } = normalized;
|
||||||
let results: ObservationSearchResult[] = [];
|
let results: ObservationSearchResult[] = [];
|
||||||
|
|
||||||
// Search for decision-type observations
|
// Search for decision-type observations
|
||||||
@@ -686,20 +655,14 @@ export class SearchManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let combinedText: string;
|
// Format as table
|
||||||
if (format === 'index') {
|
const header = `Found ${results.length} decision(s)\n\n${this.formatter.formatTableHeader()}`;
|
||||||
const header = ` ⎿ Found ${results.length} decision(s):\n\n`;
|
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
||||||
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults = results.map((obs) => this.formatter.formatObservationResult(obs));
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -719,7 +682,7 @@ export class SearchManager {
|
|||||||
async changes(args: any): Promise<any> {
|
async changes(args: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { format = 'index', ...filters } = normalized;
|
const { ...filters } = normalized;
|
||||||
let results: ObservationSearchResult[] = [];
|
let results: ObservationSearchResult[] = [];
|
||||||
|
|
||||||
// Search for change-type observations and change-related concepts
|
// Search for change-type observations and change-related concepts
|
||||||
@@ -784,20 +747,14 @@ export class SearchManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let combinedText: string;
|
// Format as table
|
||||||
if (format === 'index') {
|
const header = `Found ${results.length} change-related observation(s)\n\n${this.formatter.formatTableHeader()}`;
|
||||||
const header = ` ⎿ Found ${results.length} change-related observation(s):\n\n`;
|
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
||||||
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults = results.map((obs) => this.formatter.formatObservationResult(obs));
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -817,7 +774,7 @@ export class SearchManager {
|
|||||||
async howItWorks(args: any): Promise<any> {
|
async howItWorks(args: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { format = 'index', ...filters } = normalized;
|
const { ...filters } = normalized;
|
||||||
let results: ObservationSearchResult[] = [];
|
let results: ObservationSearchResult[] = [];
|
||||||
|
|
||||||
// Search for how-it-works concept observations
|
// Search for how-it-works concept observations
|
||||||
@@ -860,20 +817,14 @@ export class SearchManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let combinedText: string;
|
// Format as table
|
||||||
if (format === 'index') {
|
const header = `Found ${results.length} "how it works" observation(s)\n\n${this.formatter.formatTableHeader()}`;
|
||||||
const header = ` ⎿ Found ${results.length} "how it works" observation(s):\n\n`;
|
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
||||||
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults = results.map((obs) => this.formatter.formatObservationResult(obs));
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -893,7 +844,7 @@ export class SearchManager {
|
|||||||
async searchObservations(args: any): Promise<any> {
|
async searchObservations(args: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { query, format = 'index', ...options } = normalized;
|
const { query, ...options } = normalized;
|
||||||
let results: ObservationSearchResult[] = [];
|
let results: ObservationSearchResult[] = [];
|
||||||
|
|
||||||
// Vector-first search via ChromaDB
|
// Vector-first search via ChromaDB
|
||||||
@@ -936,21 +887,14 @@ export class SearchManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format based on requested format
|
// Format as table
|
||||||
let combinedText: string;
|
const header = `Found ${results.length} observation(s) matching "${query}"\n\n${this.formatter.formatTableHeader()}`;
|
||||||
if (format === 'index') {
|
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
||||||
const header = ` ⎿ Found ${results.length} observation(s) matching "${query}":\n\n`;
|
|
||||||
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults = results.map((obs) => this.formatter.formatObservationResult(obs));
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -970,7 +914,7 @@ export class SearchManager {
|
|||||||
async searchSessions(args: any): Promise<any> {
|
async searchSessions(args: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { query, format = 'index', ...options } = normalized;
|
const { query, ...options } = normalized;
|
||||||
let results: SessionSummarySearchResult[] = [];
|
let results: SessionSummarySearchResult[] = [];
|
||||||
|
|
||||||
// Vector-first search via ChromaDB
|
// Vector-first search via ChromaDB
|
||||||
@@ -1013,21 +957,14 @@ export class SearchManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format based on requested format
|
// Format as table
|
||||||
let combinedText: string;
|
const header = `Found ${results.length} session(s) matching "${query}"\n\n${this.formatter.formatTableHeader()}`;
|
||||||
if (format === 'index') {
|
const formattedResults = results.map((session, i) => this.formatter.formatSessionIndex(session, i));
|
||||||
const header = ` ⎿ Found ${results.length} session(s) matching "${query}":\n\n`;
|
|
||||||
const formattedResults = results.map((session, i) => this.formatter.formatSessionIndex(session, i));
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults = results.map((session) => this.formatter.formatSessionResult(session));
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -1047,7 +984,7 @@ export class SearchManager {
|
|||||||
async searchUserPrompts(args: any): Promise<any> {
|
async searchUserPrompts(args: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { query, format = 'index', ...options } = normalized;
|
const { query, ...options } = normalized;
|
||||||
let results: UserPromptSearchResult[] = [];
|
let results: UserPromptSearchResult[] = [];
|
||||||
|
|
||||||
// Vector-first search via ChromaDB
|
// Vector-first search via ChromaDB
|
||||||
@@ -1090,21 +1027,14 @@ export class SearchManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format based on requested format
|
// Format as table
|
||||||
let combinedText: string;
|
const header = `Found ${results.length} user prompt(s) matching "${query}"\n\n${this.formatter.formatTableHeader()}`;
|
||||||
if (format === 'index') {
|
const formattedResults = results.map((prompt, i) => this.formatter.formatUserPromptIndex(prompt, i));
|
||||||
const header = ` ⎿ Found ${results.length} user prompt(s) matching "${query}":\n\n`;
|
|
||||||
const formattedResults = results.map((prompt, i) => this.formatter.formatUserPromptIndex(prompt, i));
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults = results.map((prompt) => this.formatter.formatUserPromptResult(prompt));
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -1124,7 +1054,7 @@ export class SearchManager {
|
|||||||
async findByConcept(args: any): Promise<any> {
|
async findByConcept(args: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { concepts: concept, format = 'index', ...filters } = normalized;
|
const { concepts: concept, ...filters } = normalized;
|
||||||
let results: ObservationSearchResult[] = [];
|
let results: ObservationSearchResult[] = [];
|
||||||
|
|
||||||
// Metadata-first, semantic-enhanced search
|
// Metadata-first, semantic-enhanced search
|
||||||
@@ -1179,21 +1109,14 @@ export class SearchManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format based on requested format
|
// Format as table
|
||||||
let combinedText: string;
|
const header = `Found ${results.length} observation(s) with concept "${concept}"\n\n${this.formatter.formatTableHeader()}`;
|
||||||
if (format === 'index') {
|
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
||||||
const header = ` ⎿ Found ${results.length} observation(s) with concept "${concept}":\n\n`;
|
|
||||||
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults = results.map((obs) => this.formatter.formatObservationResult(obs));
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -1213,7 +1136,7 @@ export class SearchManager {
|
|||||||
async findByFile(args: any): Promise<any> {
|
async findByFile(args: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { files: filePath, format = 'index', ...filters } = normalized;
|
const { files: filePath, ...filters } = normalized;
|
||||||
let observations: ObservationSearchResult[] = [];
|
let observations: ObservationSearchResult[] = [];
|
||||||
let sessions: SessionSummarySearchResult[] = [];
|
let sessions: SessionSummarySearchResult[] = [];
|
||||||
|
|
||||||
@@ -1277,42 +1200,24 @@ export class SearchManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let combinedText: string;
|
// Format as table
|
||||||
if (format === 'index') {
|
const header = `Found ${totalResults} result(s) for file "${filePath}"\n\n${this.formatter.formatTableHeader()}`;
|
||||||
const header = ` ⎿ Found ${totalResults} result(s) for file "${filePath}":\n\n`;
|
const formattedResults: string[] = [];
|
||||||
const formattedResults: string[] = [];
|
|
||||||
|
|
||||||
// Add observations
|
// Add observations
|
||||||
observations.forEach((obs, i) => {
|
observations.forEach((obs, i) => {
|
||||||
formattedResults.push(this.formatter.formatObservationIndex(obs, i));
|
formattedResults.push(this.formatter.formatObservationIndex(obs, i));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add sessions
|
// Add sessions
|
||||||
sessions.forEach((session, i) => {
|
sessions.forEach((session, i) => {
|
||||||
formattedResults.push(this.formatter.formatSessionIndex(session, i + observations.length));
|
formattedResults.push(this.formatter.formatSessionIndex(session, i + observations.length));
|
||||||
});
|
});
|
||||||
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults: string[] = [];
|
|
||||||
|
|
||||||
// Add observations
|
|
||||||
observations.forEach((obs) => {
|
|
||||||
formattedResults.push(this.formatter.formatObservationResult(obs));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add sessions
|
|
||||||
sessions.forEach((session) => {
|
|
||||||
formattedResults.push(this.formatter.formatSessionResult(session));
|
|
||||||
});
|
|
||||||
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -1332,7 +1237,7 @@ export class SearchManager {
|
|||||||
async findByType(args: any): Promise<any> {
|
async findByType(args: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeParams(args);
|
const normalized = this.normalizeParams(args);
|
||||||
const { type, format = 'index', ...filters } = normalized;
|
const { type, ...filters } = normalized;
|
||||||
const typeStr = Array.isArray(type) ? type.join(', ') : type;
|
const typeStr = Array.isArray(type) ? type.join(', ') : type;
|
||||||
let results: ObservationSearchResult[] = [];
|
let results: ObservationSearchResult[] = [];
|
||||||
|
|
||||||
@@ -1388,21 +1293,14 @@ export class SearchManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format based on requested format
|
// Format as table
|
||||||
let combinedText: string;
|
const header = `Found ${results.length} observation(s) with type "${typeStr}"\n\n${this.formatter.formatTableHeader()}`;
|
||||||
if (format === 'index') {
|
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
||||||
const header = ` ⎿ Found ${results.length} observation(s) with type "${typeStr}":\n\n`;
|
|
||||||
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
|
||||||
combinedText = header + formattedResults.join('\n\n');
|
|
||||||
} else {
|
|
||||||
const formattedResults = results.map((obs) => this.formatter.formatObservationResult(obs));
|
|
||||||
combinedText = formattedResults.join('\n\n---\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: combinedText
|
text: header + '\n' + formattedResults.join('\n')
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
Reference in New Issue
Block a user