Refactor mem-search documentation and optimize API tool definitions
- Updated SKILL.md to emphasize batch fetching for observations, clarifying usage and efficiency. - Removed deprecated tools from mcp-server.ts and streamlined tool definitions for clarity. - Enhanced formatting in FormattingService.ts for better output readability. - Adjusted SearchManager.ts to improve result headers and removed unnecessary search tips from combined text.
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -87,13 +87,7 @@ Review the index results (and timeline if used). Identify which IDs are actually
|
||||
|
||||
For each relevant ID, fetch full details using MCP tools:
|
||||
|
||||
**Fetch single observation:**
|
||||
|
||||
```
|
||||
get_observation(id=11131)
|
||||
```
|
||||
|
||||
**Fetch multiple observations (recommended for 2+ IDs):**
|
||||
**Fetch multiple observations (ALWAYS use for 2+ IDs):**
|
||||
|
||||
```
|
||||
get_batch_observations(ids=[11131, 10942, 10855])
|
||||
@@ -105,10 +99,17 @@ get_batch_observations(ids=[11131, 10942, 10855])
|
||||
get_batch_observations(
|
||||
ids=[11131, 10942, 10855],
|
||||
orderBy="date_desc",
|
||||
limit=10
|
||||
limit=10,
|
||||
project="my-project"
|
||||
)
|
||||
```
|
||||
|
||||
**Fetch single observation (only when fetching exactly 1):**
|
||||
|
||||
```
|
||||
get_observation(id=11131)
|
||||
```
|
||||
|
||||
**Fetch session:**
|
||||
|
||||
```
|
||||
@@ -127,18 +128,20 @@ get_prompt(id=5421)
|
||||
- Sessions: Just the number (2005) from "S2005"
|
||||
- Prompts: Just the number (5421)
|
||||
|
||||
**When to use batch:**
|
||||
**Batch optimization:**
|
||||
|
||||
- Always use `get_batch_observations` when fetching 2+ observations
|
||||
- More efficient: one request vs multiple
|
||||
- Returns all observations in a single response
|
||||
- **ALWAYS use `get_batch_observations` for 2+ observations**
|
||||
- 10-100x more efficient than individual fetches
|
||||
- Single HTTP request vs N requests
|
||||
- Returns all results in one response
|
||||
- Supports ordering and filtering
|
||||
|
||||
## Search Parameters
|
||||
|
||||
**Basic:**
|
||||
|
||||
- `query` - What to search for (required)
|
||||
- `format` - "index" or "full" (always use "index" first)
|
||||
- `format` - "index" (NEVER USE FULL)
|
||||
- `limit` - How many results (default 30)
|
||||
- `project` - Filter by project name (required)
|
||||
|
||||
@@ -188,18 +191,30 @@ progressive_ix(topic="all") # Get complete guide
|
||||
|
||||
## Why This Workflow?
|
||||
|
||||
**Token efficiency:**
|
||||
**Massive performance gains:**
|
||||
|
||||
- Index format: ~50-100 tokens per result
|
||||
- Full format: ~500-1000 tokens per result
|
||||
- **10x difference** - only fetch full when you know it's relevant
|
||||
- **Index format:** ~50-100 tokens per result
|
||||
- **Full format:** ~500-1000 tokens per result
|
||||
- **10x token savings** - only fetch full when you know it's relevant
|
||||
|
||||
**Batch fetching optimization:**
|
||||
|
||||
- **Fetching 10 observations individually:** 10 HTTP requests, ~5-10s latency
|
||||
- **Batch fetch:** 1 HTTP request, ~0.5-1s latency
|
||||
- **10-100x faster** for multi-observation queries
|
||||
|
||||
**Clarity:**
|
||||
|
||||
- See everything first
|
||||
- Pick what matters
|
||||
- Get details only for what you need
|
||||
- See everything first (index)
|
||||
- Get timeline context around interesting results
|
||||
- Pick what matters based on context
|
||||
- Fetch details only for what you need (batch when possible)
|
||||
|
||||
---
|
||||
|
||||
**Remember:** ALWAYS search with format=index first. ALWAYS get timeline context for observations you're interested in. ALWAYS fetch by ID for details. The IDs are there for a reason - use them.
|
||||
**Remember:**
|
||||
|
||||
- ALWAYS search with `format="index"` first for token efficiency
|
||||
- ALWAYS get timeline context to understand what was happening
|
||||
- ALWAYS use `get_batch_observations` when fetching 2+ observations
|
||||
- The workflow is optimized: index → timeline → batch fetch = 10-100x faster
|
||||
|
||||
+50
-243
@@ -30,18 +30,8 @@ const WORKER_BASE_URL = `http://${WORKER_HOST}:${WORKER_PORT}`;
|
||||
const TOOL_ENDPOINT_MAP: Record<string, string> = {
|
||||
'search': '/api/search',
|
||||
'timeline': '/api/timeline',
|
||||
'decisions': '/api/decisions',
|
||||
'changes': '/api/changes',
|
||||
'how_it_works': '/api/how-it-works',
|
||||
'search_observations': '/api/search/observations',
|
||||
'search_sessions': '/api/search/sessions',
|
||||
'search_user_prompts': '/api/search/prompts',
|
||||
'find_by_concept': '/api/search/by-concept',
|
||||
'find_by_file': '/api/search/by-file',
|
||||
'find_by_type': '/api/search/by-type',
|
||||
'get_recent_context': '/api/context/recent',
|
||||
'get_context_timeline': '/api/context/timeline',
|
||||
'get_timeline_by_query': '/api/timeline/by-query',
|
||||
'progressive_ix': '/api/instructions'
|
||||
};
|
||||
|
||||
@@ -192,24 +182,25 @@ async function verifyWorkerConnection(): Promise<boolean> {
|
||||
|
||||
/**
|
||||
* Tool definitions with HTTP-based handlers
|
||||
* Descriptions removed - use progressive_ix tool for parameter documentation
|
||||
*/
|
||||
const tools = [
|
||||
{
|
||||
name: 'search',
|
||||
description: 'Search observations, sessions, and prompts',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Natural language search query for semantic ranking via ChromaDB vector search. Optional - omit for date-filtered queries only (Chroma cannot filter by date, requires direct SQLite).'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED for initial search), "full" for complete details (use only after reviewing index results)'),
|
||||
type: z.enum(['observations', 'sessions', 'prompts']).optional().describe('Filter by document type (observations, sessions, or prompts). Omit to search all types.'),
|
||||
obs_type: z.string().optional().describe('Filter observations by type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change). Only applies when type="observations"'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list). Only applies when type="observations"'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match). Only applies when type="observations"'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
query: z.string().optional(),
|
||||
format: z.enum(['index', 'full']).default('index'),
|
||||
type: z.enum(['observations', 'sessions', 'prompts']).optional(),
|
||||
obs_type: z.string().optional(),
|
||||
concepts: z.string().optional(),
|
||||
files: z.string().optional(),
|
||||
project: z.string().optional(),
|
||||
dateStart: z.union([z.string(), z.number()]).optional(),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional(),
|
||||
limit: z.number().min(1).max(100).default(20),
|
||||
offset: z.number().min(0).default(0),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['search'];
|
||||
@@ -220,195 +211,33 @@ const tools = [
|
||||
name: 'timeline',
|
||||
description: 'Get timeline around observation ID or query',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Natural language query to find anchor observation (query-based mode). Mutually exclusive with anchor.'),
|
||||
anchor: z.number().optional().describe('Observation ID to use as anchor (anchor-based mode). Mutually exclusive with query.'),
|
||||
depth_before: z.number().min(0).max(100).default(10).describe('Number of observations to fetch before anchor'),
|
||||
depth_after: z.number().min(0).max(100).default(10).describe('Number of observations to fetch after anchor'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter observations by type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name')
|
||||
query: z.string().optional(),
|
||||
anchor: z.number().optional(),
|
||||
depth_before: z.number().min(0).max(100).default(10),
|
||||
depth_after: z.number().min(0).max(100).default(10),
|
||||
format: z.enum(['index', 'full']).default('index'),
|
||||
type: z.string().optional(),
|
||||
concepts: z.string().optional(),
|
||||
files: z.string().optional(),
|
||||
project: z.string().optional()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['timeline'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'decisions',
|
||||
description: 'Find architectural and design decisions',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Natural language query for finding decisions'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['decisions'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'changes',
|
||||
description: 'Find code changes and refactorings',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Natural language query for finding changes'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['changes'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'how_it_works',
|
||||
description: 'Understand system architecture',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Natural language query for understanding how something works'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['how_it_works'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search_observations',
|
||||
description: '[DEPRECATED] Search observations only',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Full-text search query (FTS5)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('relevance').describe('Sort order (relevance only when query provided)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['search_observations'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search_sessions',
|
||||
description: '[DEPRECATED] Search sessions only',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Full-text search query (FTS5)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('relevance').describe('Sort order (relevance only when query provided)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['search_sessions'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search_user_prompts',
|
||||
description: '[DEPRECATED] Search prompts only',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Full-text search query (FTS5)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('relevance').describe('Sort order (relevance only when query provided)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['search_user_prompts'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_by_concept',
|
||||
description: 'Find observations by concept tags',
|
||||
inputSchema: z.object({
|
||||
concepts: z.string().describe('Concept tag(s) to filter by (single value or comma-separated list)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['find_by_concept'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_by_file',
|
||||
description: 'Find observations by file paths',
|
||||
inputSchema: z.object({
|
||||
files: z.string().describe('File path(s) to filter by (single value or comma-separated list for partial match)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['find_by_file'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_by_type',
|
||||
description: 'Find observations by type',
|
||||
inputSchema: z.object({
|
||||
type: z.string().describe('Observation type(s) to filter by (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['find_by_type'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_recent_context',
|
||||
description: 'Get recent timeline items',
|
||||
inputSchema: z.object({
|
||||
limit: z.number().min(1).max(100).default(30).describe('Maximum number of timeline items to return'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
limit: z.number().min(1).max(100).default(30),
|
||||
format: z.enum(['index', 'full']).default('index'),
|
||||
type: z.string().optional(),
|
||||
concepts: z.string().optional(),
|
||||
files: z.string().optional(),
|
||||
project: z.string().optional(),
|
||||
dateStart: z.union([z.string(), z.number()]).optional(),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['get_recent_context'];
|
||||
@@ -419,47 +248,25 @@ const tools = [
|
||||
name: 'get_context_timeline',
|
||||
description: 'Get timeline around specific observation',
|
||||
inputSchema: z.object({
|
||||
anchor: z.number().describe('Observation ID to use as anchor point'),
|
||||
depth_before: z.number().min(0).max(100).default(10).describe('Number of observations to fetch before anchor'),
|
||||
depth_after: z.number().min(0).max(100).default(10).describe('Number of observations to fetch after anchor'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name')
|
||||
anchor: z.number(),
|
||||
depth_before: z.number().min(0).max(100).default(10),
|
||||
depth_after: z.number().min(0).max(100).default(10),
|
||||
format: z.enum(['index', 'full']).default('index'),
|
||||
type: z.string().optional(),
|
||||
concepts: z.string().optional(),
|
||||
files: z.string().optional(),
|
||||
project: z.string().optional()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['get_context_timeline'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_timeline_by_query',
|
||||
description: 'Search and get timeline in one call',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Natural language query to find anchor observation'),
|
||||
depth_before: z.number().min(0).max(100).default(10).describe('Number of observations to fetch before anchor'),
|
||||
depth_after: z.number().min(0).max(100).default(10).describe('Number of observations to fetch after anchor'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['get_timeline_by_query'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'progressive_ix',
|
||||
description: 'Load detailed instructions for mem-search tools',
|
||||
description: 'Load parameter docs and usage instructions',
|
||||
inputSchema: z.object({
|
||||
topic: z.enum(['workflow', 'search_params', 'examples', 'all'])
|
||||
.default('all')
|
||||
.describe('Which instruction section to load: workflow (4-step process), search_params (parameters reference), examples (usage examples), all (complete guide)')
|
||||
topic: z.enum(['workflow', 'search_params', 'examples', 'all']).default('all')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['progressive_ix'];
|
||||
@@ -468,9 +275,9 @@ const tools = [
|
||||
},
|
||||
{
|
||||
name: 'get_observation',
|
||||
description: 'Get full details for a single observation by ID',
|
||||
description: 'Get observation by ID',
|
||||
inputSchema: z.object({
|
||||
id: z.number().describe('Observation ID from search/timeline results')
|
||||
id: z.number()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIWithPath('/api/observation', args.id);
|
||||
@@ -478,12 +285,12 @@ const tools = [
|
||||
},
|
||||
{
|
||||
name: 'get_batch_observations',
|
||||
description: 'Get full details for multiple observations by IDs in one request',
|
||||
description: 'Get multiple observations by IDs',
|
||||
inputSchema: z.object({
|
||||
ids: z.array(z.number()).describe('Array of observation IDs to fetch'),
|
||||
orderBy: z.enum(['date_desc', 'date_asc']).optional().describe('Sort order for results'),
|
||||
limit: z.number().optional().describe('Maximum number of results to return'),
|
||||
project: z.string().optional().describe('Filter by project name')
|
||||
ids: z.array(z.number()),
|
||||
orderBy: z.enum(['date_desc', 'date_asc']).optional(),
|
||||
limit: z.number().optional(),
|
||||
project: z.string().optional()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIPost('/api/observations/batch', args);
|
||||
@@ -491,9 +298,9 @@ const tools = [
|
||||
},
|
||||
{
|
||||
name: 'get_session',
|
||||
description: 'Get full session summary by ID',
|
||||
description: 'Get session summary by ID',
|
||||
inputSchema: z.object({
|
||||
id: z.number().describe('Session ID (just the number from S1234)')
|
||||
id: z.number()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIWithPath('/api/session', args.id);
|
||||
@@ -503,7 +310,7 @@ const tools = [
|
||||
name: 'get_prompt',
|
||||
description: 'Get user prompt by ID',
|
||||
inputSchema: z.object({
|
||||
id: z.number().describe('Prompt ID from search results')
|
||||
id: z.number()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIWithPath('/api/prompt', args.id);
|
||||
|
||||
@@ -38,9 +38,7 @@ Other tips:
|
||||
const date = new Date(obs.created_at_epoch).toLocaleString();
|
||||
const type = obs.type ? `[${obs.type}]` : '';
|
||||
|
||||
return `${index + 1}. ${type} ${title}
|
||||
Date: ${date}
|
||||
Source: claude-mem://observation/${obs.id}`;
|
||||
return ` ${index + 1}. ${type} ${title}\n Date: ${date}\n Source: claude-mem://observation/${obs.id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,9 +48,7 @@ Other tips:
|
||||
const title = session.request || `Session ${session.sdk_session_id?.substring(0, 8) || 'unknown'}`;
|
||||
const date = new Date(session.created_at_epoch).toLocaleString();
|
||||
|
||||
return `${index + 1}. ${title}
|
||||
Date: ${date}
|
||||
Source: claude-mem://session/${session.sdk_session_id}`;
|
||||
return ` ${index + 1}. ${title}\n Date: ${date}\n Source: claude-mem://session/${session.sdk_session_id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,9 +57,7 @@ Other tips:
|
||||
formatUserPromptIndex(prompt: UserPromptSearchResult, index: number): string {
|
||||
const date = new Date(prompt.created_at_epoch).toLocaleString();
|
||||
|
||||
return `${index + 1}. "${prompt.prompt_text}"
|
||||
Date: ${date} | Prompt #${prompt.prompt_number}
|
||||
Source: claude-mem://user-prompt/${prompt.id}`;
|
||||
return ` ${index + 1}. "${prompt.prompt_text}"\n Date: ${date} | Prompt #${prompt.prompt_number}\n Source: claude-mem://user-prompt/${prompt.id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -248,7 +248,7 @@ export class SearchManager {
|
||||
|
||||
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 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);
|
||||
@@ -258,7 +258,7 @@ export class SearchManager {
|
||||
return this.formatter.formatUserPromptIndex(item.data, i);
|
||||
}
|
||||
});
|
||||
combinedText = header + formattedResults.join('\n\n') + this.formatter.formatSearchTips();
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
const formattedResults = limitedResults.map(item => {
|
||||
if (item.type === 'observation') {
|
||||
@@ -688,7 +688,7 @@ export class SearchManager {
|
||||
|
||||
let combinedText: string;
|
||||
if (format === 'index') {
|
||||
const header = `Found ${results.length} decision(s):\n\n`;
|
||||
const header = ` ⎿ Found ${results.length} decision(s):\n\n`;
|
||||
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
@@ -786,7 +786,7 @@ export class SearchManager {
|
||||
|
||||
let combinedText: string;
|
||||
if (format === 'index') {
|
||||
const header = `Found ${results.length} change-related observation(s):\n\n`;
|
||||
const header = ` ⎿ Found ${results.length} change-related observation(s):\n\n`;
|
||||
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
@@ -862,7 +862,7 @@ export class SearchManager {
|
||||
|
||||
let combinedText: string;
|
||||
if (format === 'index') {
|
||||
const header = `Found ${results.length} "how it works" observation(s):\n\n`;
|
||||
const header = ` ⎿ Found ${results.length} "how it works" observation(s):\n\n`;
|
||||
const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
@@ -939,9 +939,9 @@ export class SearchManager {
|
||||
// Format based on requested format
|
||||
let combinedText: string;
|
||||
if (format === 'index') {
|
||||
const header = `Found ${results.length} observation(s) matching "${query}":\n\n`;
|
||||
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') + this.formatter.formatSearchTips();
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
const formattedResults = results.map((obs) => this.formatter.formatObservationResult(obs));
|
||||
combinedText = formattedResults.join('\n\n---\n\n');
|
||||
@@ -1016,9 +1016,9 @@ export class SearchManager {
|
||||
// Format based on requested format
|
||||
let combinedText: string;
|
||||
if (format === 'index') {
|
||||
const header = `Found ${results.length} session(s) matching "${query}":\n\n`;
|
||||
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') + this.formatter.formatSearchTips();
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
const formattedResults = results.map((session) => this.formatter.formatSessionResult(session));
|
||||
combinedText = formattedResults.join('\n\n---\n\n');
|
||||
@@ -1093,9 +1093,9 @@ export class SearchManager {
|
||||
// Format based on requested format
|
||||
let combinedText: string;
|
||||
if (format === 'index') {
|
||||
const header = `Found ${results.length} user prompt(s) matching "${query}":\n\n`;
|
||||
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') + this.formatter.formatSearchTips();
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
const formattedResults = results.map((prompt) => this.formatter.formatUserPromptResult(prompt));
|
||||
combinedText = formattedResults.join('\n\n---\n\n');
|
||||
@@ -1182,9 +1182,9 @@ export class SearchManager {
|
||||
// Format based on requested format
|
||||
let combinedText: string;
|
||||
if (format === 'index') {
|
||||
const header = `Found ${results.length} observation(s) with concept "${concept}":\n\n`;
|
||||
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') + this.formatter.formatSearchTips();
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
const formattedResults = results.map((obs) => this.formatter.formatObservationResult(obs));
|
||||
combinedText = formattedResults.join('\n\n---\n\n');
|
||||
@@ -1279,7 +1279,7 @@ export class SearchManager {
|
||||
|
||||
let combinedText: string;
|
||||
if (format === 'index') {
|
||||
const header = `Found ${totalResults} result(s) for file "${filePath}":\n\n`;
|
||||
const header = ` ⎿ Found ${totalResults} result(s) for file "${filePath}":\n\n`;
|
||||
const formattedResults: string[] = [];
|
||||
|
||||
// Add observations
|
||||
@@ -1292,7 +1292,7 @@ export class SearchManager {
|
||||
formattedResults.push(this.formatter.formatSessionIndex(session, i + observations.length));
|
||||
});
|
||||
|
||||
combinedText = header + formattedResults.join('\n\n') + this.formatter.formatSearchTips();
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
const formattedResults: string[] = [];
|
||||
|
||||
@@ -1391,9 +1391,9 @@ export class SearchManager {
|
||||
// Format based on requested format
|
||||
let combinedText: string;
|
||||
if (format === 'index') {
|
||||
const header = `Found ${results.length} observation(s) with type "${typeStr}":\n\n`;
|
||||
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') + this.formatter.formatSearchTips();
|
||||
combinedText = header + formattedResults.join('\n\n');
|
||||
} else {
|
||||
const formattedResults = results.map((obs) => this.formatter.formatObservationResult(obs));
|
||||
combinedText = formattedResults.join('\n\n---\n\n');
|
||||
|
||||
Reference in New Issue
Block a user