Merge remote-tracking branch 'origin/bracket-nonsense'

# Conflicts:
#	plugin/scripts/search-server.cjs
This commit is contained in:
Alex Newman
2025-11-30 22:59:50 -05:00
13 changed files with 217 additions and 194 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "claude-mem", "name": "claude-mem",
"version": "6.0.9", "version": "6.3.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "claude-mem", "name": "claude-mem",
"version": "6.0.9", "version": "6.3.6",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.27", "@anthropic-ai/claude-agent-sdk": "^0.1.27",
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -98,9 +98,9 @@ curl "http://localhost:37777/api/prompt/5421"
**Filters (optional):** **Filters (optional):**
- `type` - Filter to "observations", "sessions", or "prompts" - `type` - Filter to "observations", "sessions", or "prompts"
- `project` - Filter by project name - `project` - Filter by project name
- `dateRange[start]` - Start date (YYYY-MM-DD) - `dateStart` - Start date (YYYY-MM-DD or epoch timestamp)
- `dateRange[end]` - End date (YYYY-MM-DD) - `dateEnd` - End date (YYYY-MM-DD or epoch timestamp)
- `obs_type` - Filter observations by: bugfix, feature, decision, discovery, change - `obs_type` - Filter observations by type (comma-separated): bugfix, feature, decision, discovery, change
## Examples ## Examples
@@ -111,7 +111,7 @@ curl "http://localhost:37777/api/search?query=bug&type=observations&obs_type=bug
**Find what happened last week:** **Find what happened last week:**
```bash ```bash
curl "http://localhost:37777/api/search?query=&type=observations&dateRange[start]=2025-11-11&format=index&limit=10" curl "http://localhost:37777/api/search?query=&type=observations&dateStart=2025-11-11&format=index&limit=10"
``` ```
**Search everything:** **Search everything:**
@@ -28,7 +28,7 @@ curl -s "http://localhost:37777/api/search/by-concept?concept=discovery&format=i
- **format**: "index" (summary) or "full" (complete details). Default: "full" - **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100) - **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional) - **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional) - **dateStart/dateEnd**: Filter by date range (optional)
## When to Use Each Format ## When to Use Each Format
@@ -24,7 +24,7 @@ curl -s "http://localhost:37777/api/search/by-file?filePath=src/services/worker-
- **format**: "index" (summary) or "full" (complete details). Default: "full" - **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100) - **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional) - **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional) - **dateStart/dateEnd**: Filter by date range (optional)
## When to Use Each Format ## When to Use Each Format
@@ -118,7 +118,7 @@ Response: "No observations found for 'nonexistent.ts'. Try a partial path or che
1. Use format=index first to see overview of all changes 1. Use format=index first to see overview of all changes
2. Start with partial paths (e.g., filename only) for broader matches 2. Start with partial paths (e.g., filename only) for broader matches
3. Use full paths when you need specific file matches 3. Use full paths when you need specific file matches
4. Combine with dateRange to see recent changes: `?filePath=worker.ts&dateRange[start]=2024-11-01` 4. Combine with dateStart to see recent changes: `?filePath=worker.ts&dateStart=2024-11-01`
5. Use directory searches to see all work in a module 5. Use directory searches to see all work in a module
**Token Efficiency:** **Token Efficiency:**
@@ -27,7 +27,7 @@ curl -s "http://localhost:37777/api/search/by-type?type=bugfix&format=index&limi
- **format**: "index" (summary) or "full" (complete details). Default: "full" - **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100) - **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional) - **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional) - **dateStart/dateEnd**: Filter by date range (optional)
## When to Use Each Format ## When to Use Each Format
@@ -114,7 +114,7 @@ Fix: Use one of the valid type values
1. Use format=index first to see overview 1. Use format=index first to see overview
2. Start with limit=5-10 to avoid token overload 2. Start with limit=5-10 to avoid token overload
3. Combine with dateRange for recent work: `?type=bugfix&dateRange[start]=2024-11-01` 3. Combine with dateStart for recent work: `?type=bugfix&dateStart=2024-11-01`
4. Use project filtering when working on one codebase 4. Use project filtering when working on one codebase
**Token Efficiency:** **Token Efficiency:**
@@ -186,7 +186,7 @@ Then choose anchor manually.
1. **Combine filters** for precision: 1. **Combine filters** for precision:
```bash ```bash
curl -s "http://localhost:37777/api/search/observations?query=authentication&type=feature&dateRange[start]=2024-11-01&format=index&limit=10" curl -s "http://localhost:37777/api/search/observations?query=authentication&type=feature&dateStart=2024-11-01&format=index&limit=10"
``` ```
2. **Review filtered results** 2. **Review filtered results**
@@ -207,7 +207,7 @@ Found 10 authentication features added in November:
**Why this workflow:** **Why this workflow:**
- Multiple filters narrow results before requesting full details - Multiple filters narrow results before requesting full details
- Type + query + dateRange = precise targeting - Type + query + dateStart/dateEnd = precise targeting
- Progressive disclosure: index first, full details selectively - Progressive disclosure: index first, full details selectively
--- ---
@@ -228,7 +228,7 @@ Found 10 authentication features added in November:
1. **Start with index format** - Always use `format=index` first 1. **Start with index format** - Always use `format=index` first
2. **Use specialized tools** - by-type, by-file, by-concept when applicable 2. **Use specialized tools** - by-type, by-file, by-concept when applicable
3. **Compose operations** - Combine search + timeline for investigations 3. **Compose operations** - Combine search + timeline for investigations
4. **Filter early** - Use type, dateRange, project to narrow before expanding 4. **Filter early** - Use type, dateStart/dateEnd, project to narrow before expanding
5. **Progressive disclosure** - Load full details only for relevant items 5. **Progressive disclosure** - Load full details only for relevant items
## Token Budget Awareness ## Token Budget Awareness
+3 -3
View File
@@ -106,9 +106,9 @@ Many endpoints share these parameters:
- **limit**: Number of results to return - **limit**: Number of results to return
- **offset**: Number of results to skip (for pagination) - **offset**: Number of results to skip (for pagination)
- **project**: Filter by project name - **project**: Filter by project name
- **dateRange**: Filter by date range - **dateStart/dateEnd**: Filter by date range
- `dateRange[start]`: Start date (ISO string or epoch) - `dateStart`: Start date (YYYY-MM-DD or epoch timestamp)
- `dateRange[end]`: End date (ISO string or epoch) - `dateEnd`: End date (YYYY-MM-DD or epoch timestamp)
## Error Handling ## Error Handling
@@ -21,12 +21,12 @@ curl -s "http://localhost:37777/api/search/observations?query=authentication&for
- **format**: "index" (summary) or "full" (complete details). Default: "full" - **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100) - **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional) - **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional) - `dateRange[start]` and/or `dateRange[end]` - **dateStart/dateEnd**: Filter by date range (optional) - `dateStart` and/or `dateEnd` (YYYY-MM-DD format or epoch timestamp)
- **obs_type**: Filter by observation type: bugfix, feature, refactor, decision, discovery, change (optional) - **obs_type**: Filter by observation type (comma-separated): bugfix, feature, refactor, decision, discovery, change (optional)
- **concepts**: Filter by concept tags (optional) - **concepts**: Filter by concept tags (comma-separated, optional)
- **files**: Filter by file paths (optional) - **files**: Filter by file paths (comma-separated, optional)
**Important**: When omitting `query`, you MUST provide at least one filter (project, dateRange, obs_type, concepts, or files) **Important**: When omitting `query`, you MUST provide at least one filter (project, dateStart/dateEnd, obs_type, concepts, or files)
## When to Use Each Format ## When to Use Each Format
@@ -88,13 +88,13 @@ Search without query text (direct SQLite filtering):
```bash ```bash
# Get all observations from November 2025 # Get all observations from November 2025
curl -s "http://localhost:37777/api/search?type=observations&dateRange[start]=2025-11-01&format=index" curl -s "http://localhost:37777/api/search?type=observations&dateStart=2025-11-01&format=index"
# Get all bug fixes from a specific project # Get all bug fixes from a specific project
curl -s "http://localhost:37777/api/search?type=observations&obs_type=bugfix&project=api-server&format=index" curl -s "http://localhost:37777/api/search?type=observations&obs_type=bugfix&project=api-server&format=index"
# Get all observations from last 7 days # Get all observations from last 7 days
curl -s "http://localhost:37777/api/search?type=observations&dateRange[start]=2025-11-11&format=index" curl -s "http://localhost:37777/api/search?type=observations&dateStart=2025-11-11&format=index"
``` ```
## Error Handling ## Error Handling
@@ -103,7 +103,7 @@ curl -s "http://localhost:37777/api/search?type=observations&dateRange[start]=20
```json ```json
{"error": "Either query or filters required for search"} {"error": "Either query or filters required for search"}
``` ```
Fix: Provide either a query parameter OR at least one filter (project, dateRange, obs_type, concepts, files) Fix: Provide either a query parameter OR at least one filter (project, dateStart/dateEnd, obs_type, concepts, files)
**No results found:** **No results found:**
```json ```json
@@ -21,7 +21,7 @@ curl -s "http://localhost:37777/api/search/prompts?query=authentication&format=i
- **format**: "index" (truncated prompts) or "full" (complete prompt text). Default: "full" - **format**: "index" (truncated prompts) or "full" (complete prompt text). Default: "full"
- **limit**: Number of results (default: 20, max: 100) - **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional) - **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional) - **dateStart/dateEnd**: Filter by date range (optional)
## When to Use Each Format ## When to Use Each Format
@@ -99,7 +99,7 @@ Response: "No user prompts found for 'foobar'. Try different search terms."
1. Use exact phrases in quotes: `?query="how do I"` for precise matches 1. Use exact phrases in quotes: `?query="how do I"` for precise matches
2. Start with format=index to see preview, then get full text if needed 2. Start with format=index to see preview, then get full text if needed
3. Use dateRange to find recent questions: `?query=bug&dateRange[start]=2024-11-01` 3. Use dateStart to find recent questions: `?query=bug&dateStart=2024-11-01`
4. Prompts show what was asked, sessions/observations show what was done 4. Prompts show what was asked, sessions/observations show what was done
5. Combine with session search to see both question and answer 5. Combine with session search to see both question and answer
@@ -21,7 +21,7 @@ curl -s "http://localhost:37777/api/search/sessions?query=authentication&format=
- **format**: "index" (summary) or "full" (complete details). Default: "full" - **format**: "index" (summary) or "full" (complete details). Default: "full"
- **limit**: Number of results (default: 20, max: 100) - **limit**: Number of results (default: 20, max: 100)
- **project**: Filter by project name (optional) - **project**: Filter by project name (optional)
- **dateRange**: Filter by date range (optional) - **dateStart/dateEnd**: Filter by date range (optional)
## When to Use Each Format ## When to Use Each Format
@@ -102,7 +102,7 @@ Response: "No sessions found for 'foobar'. Try different search terms."
1. Be specific: "JWT authentication implementation" > "auth" 1. Be specific: "JWT authentication implementation" > "auth"
2. Start with format=index and limit=5-10 2. Start with format=index and limit=5-10
3. Use dateRange for recent sessions: `?query=auth&dateRange[start]=2024-11-01` 3. Use dateStart for recent sessions: `?query=auth&dateStart=2024-11-01`
4. Sessions provide high-level overview, observations provide details 4. Sessions provide high-level overview, observations provide details
5. Use project filtering when working on one codebase 5. Use project filtering when working on one codebase
@@ -69,7 +69,7 @@ curl -s "http://localhost:37777/api/search/observations?query=authentication&for
### Step 4: Refine with Filters (If Needed) ### Step 4: Refine with Filters (If Needed)
**Techniques:** **Techniques:**
- Use `type`, `dateRange`, `concepts`, `files` filters - Use `type`, `dateStart`/`dateEnd`, `concepts`, `files` filters
- Narrow scope BEFORE requesting more results - Narrow scope BEFORE requesting more results
- Use `offset` for pagination instead of large limits - Use `offset` for pagination instead of large limits
+91 -68
View File
@@ -127,7 +127,7 @@ Search workflow:
1. Initial search: Use default (index) format to see titles, dates, and sources 1. Initial search: Use default (index) format to see titles, dates, and sources
2. Review results: Identify which items are most relevant to your needs 2. Review results: Identify which items are most relevant to your needs
3. Deep dive: Only then use format: "full" on specific items of interest 3. Deep dive: Only then use format: "full" on specific items of interest
4. Narrow down: Use filters (type, dateRange, concepts, files) to refine results 4. Narrow down: Use filters (type, dateStart/dateEnd, concepts, files) to refine results
Other tips: Other tips:
To search by concept: Use find_by_concept tool To search by concept: Use find_by_concept tool
@@ -378,20 +378,62 @@ function formatUserPromptResult(prompt: UserPromptSearchResult): string {
} }
/** /**
* Common filter schema * Helper to normalize query parameters from URL-friendly format
* Converts comma-separated strings to arrays and flattens date params
*/
function normalizeParams(args: any): any {
const normalized: any = { ...args };
// Parse comma-separated concepts into array
if (normalized.concepts && typeof normalized.concepts === 'string') {
normalized.concepts = normalized.concepts.split(',').map((s: string) => s.trim()).filter(Boolean);
}
// Parse comma-separated files into array
if (normalized.files && typeof normalized.files === 'string') {
normalized.files = normalized.files.split(',').map((s: string) => s.trim()).filter(Boolean);
}
// Parse comma-separated obs_type into array
if (normalized.obs_type && typeof normalized.obs_type === 'string') {
normalized.obs_type = normalized.obs_type.split(',').map((s: string) => s.trim()).filter(Boolean);
}
// Parse comma-separated type (for filterSchema) into array
if (normalized.type && typeof normalized.type === 'string' && normalized.type.includes(',')) {
normalized.type = normalized.type.split(',').map((s: string) => s.trim()).filter(Boolean);
}
// Flatten dateStart/dateEnd into dateRange object
if (normalized.dateStart || normalized.dateEnd) {
normalized.dateRange = {
start: normalized.dateStart,
end: normalized.dateEnd
};
delete normalized.dateStart;
delete normalized.dateEnd;
}
return normalized;
}
/**
* Common filter schema (accepts simple strings that get normalized to arrays)
*/ */
const filterSchema = z.object({ const filterSchema = z.object({
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
type: z.union([ type: z.union([
z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']), z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']),
z.array(z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change'])) z.array(z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']))
]).optional().describe('Filter by observation type'), ]).optional().describe('Filter by observation type (single value or comma-separated list)'),
concepts: z.union([z.string(), z.array(z.string())]).optional().describe('Filter by concept tags'), concepts: z.union([z.string(), z.array(z.string())]).optional().describe('Filter by concept tags (single value or comma-separated list)'),
files: z.union([z.string(), z.array(z.string())]).optional().describe('Filter by file paths (partial match)'), files: z.union([z.string(), z.array(z.string())]).optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date (ISO string or epoch)'),
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date (ISO string or epoch)'),
dateRange: z.object({ dateRange: z.object({
start: z.union([z.string(), z.number()]).optional().describe('Start date (ISO string or epoch)'), start: z.union([z.string(), z.number()]).optional().describe('Start date (ISO string or epoch)'),
end: z.union([z.string(), z.number()]).optional().describe('End date (ISO string or epoch)') end: z.union([z.string(), z.number()]).optional().describe('End date (ISO string or epoch)')
}).optional().describe('Filter by date range'), }).optional().describe('Filter by date range (use dateStart/dateEnd instead for simpler URLs)'),
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'), 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'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
@@ -406,30 +448,21 @@ const tools = [
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).'), 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)'), 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.'), type: z.enum(['observations', 'sessions', 'prompts']).optional().describe('Filter by document type (observations, sessions, or prompts). Omit to search all types.'),
obs_type: z.union([ 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"'),
z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']), concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list). Only applies when type="observations"'),
z.array(z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change'])) files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match). Only applies when type="observations"'),
]).optional().describe('Filter observations by type. Only applies when type="observations"'),
concepts: z.union([
z.string(),
z.array(z.string())
]).optional().describe('Filter by concept tags. Only applies when type="observations"'),
files: z.union([
z.string(),
z.array(z.string())
]).optional().describe('Filter by file paths (partial match). Only applies when type="observations"'),
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
dateRange: z.object({ dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
start: z.union([z.string(), z.number()]).optional().describe('Start date (ISO string or epoch)'), dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
end: z.union([z.string(), z.number()]).optional().describe('End date (ISO string or epoch)')
}).optional().describe('Filter by date range'),
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'), 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'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { query, format = 'index', type, obs_type, concepts, files, ...options } = args; // Normalize URL-friendly params to internal format
const normalized = normalizeParams(args);
const { query, format = 'index', 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[] = [];
@@ -969,17 +1002,16 @@ const tools = [
query: z.string().optional().describe('Search query to filter decisions semantically'), query: z.string().optional().describe('Search query to filter decisions semantically'),
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default), "full" for complete details'), format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default), "full" for complete details'),
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
dateRange: z.object({ dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
start: z.union([z.string(), z.number()]).optional(), dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
end: z.union([z.string(), z.number()]).optional()
}).optional().describe('Filter by date range'),
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'), 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'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { query, format = 'index', ...filters } = args; const normalized = normalizeParams(args);
const { query, format = 'index', ...filters } = normalized;
let results: ObservationSearchResult[] = []; let results: ObservationSearchResult[] = [];
// Search for decision-type observations // Search for decision-type observations
@@ -1069,17 +1101,16 @@ const tools = [
inputSchema: z.object({ inputSchema: z.object({
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default), "full" for complete details'), format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default), "full" for complete details'),
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
dateRange: z.object({ dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
start: z.union([z.string(), z.number()]).optional(), dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
end: z.union([z.string(), z.number()]).optional()
}).optional().describe('Filter by date range'),
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'), 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'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { format = 'index', ...filters } = args; const normalized = normalizeParams(args);
const { format = 'index', ...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
@@ -1177,17 +1208,16 @@ const tools = [
inputSchema: z.object({ inputSchema: z.object({
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default), "full" for complete details'), format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default), "full" for complete details'),
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
dateRange: z.object({ dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
start: z.union([z.string(), z.number()]).optional(), dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
end: z.union([z.string(), z.number()]).optional()
}).optional().describe('Filter by date range'),
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'), 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'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { format = 'index', ...filters } = args; const normalized = normalizeParams(args);
const { format = 'index', ...filters } = normalized;
let results: ObservationSearchResult[] = []; let results: ObservationSearchResult[] = [];
// Search for how-it-works concept observations // Search for how-it-works concept observations
@@ -1267,7 +1297,8 @@ const tools = [
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { query, format = 'index', ...options } = args; const normalized = normalizeParams(args);
const { query, format = 'index', ...options } = normalized;
let results: ObservationSearchResult[] = []; let results: ObservationSearchResult[] = [];
// Vector-first search via ChromaDB // Vector-first search via ChromaDB
@@ -1345,17 +1376,16 @@ const tools = [
query: z.string().describe('Natural language search query for semantic ranking via ChromaDB vector search'), query: z.string().describe('Natural language search query for semantic ranking via ChromaDB vector search'),
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)'), 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)'),
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
dateRange: z.object({ dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
start: z.union([z.string(), z.number()]).optional(), dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
end: z.union([z.string(), z.number()]).optional()
}).optional().describe('Filter by date range'),
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'), 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'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { query, format = 'index', ...options } = args; const normalized = normalizeParams(args);
const { query, format = 'index', ...options } = normalized;
let results: SessionSummarySearchResult[] = []; let results: SessionSummarySearchResult[] = [];
// Vector-first search via ChromaDB // Vector-first search via ChromaDB
@@ -1433,17 +1463,16 @@ const tools = [
concept: z.string().describe('Concept tag to search for. Available: discovery, problem-solution, what-changed, how-it-works, pattern, gotcha, change'), concept: z.string().describe('Concept tag to search for. Available: discovery, problem-solution, what-changed, how-it-works, pattern, gotcha, change'),
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)'), 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)'),
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
dateRange: z.object({ dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
start: z.union([z.string(), z.number()]).optional(), dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
end: z.union([z.string(), z.number()]).optional()
}).optional().describe('Filter by date range'),
limit: z.number().min(1).max(100).default(20).describe('Maximum results. IMPORTANT: Start with 3-5 to avoid exceeding MCP token limits, even in index mode.'), limit: z.number().min(1).max(100).default(20).describe('Maximum results. IMPORTANT: Start with 3-5 to avoid exceeding MCP token limits, even in index mode.'),
offset: z.number().min(0).default(0).describe('Number of results to skip'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { concept, format = 'index', ...filters } = args; const normalized = normalizeParams(args);
const { concept, format = 'index', ...filters } = normalized;
let results: ObservationSearchResult[] = []; let results: ObservationSearchResult[] = [];
// Metadata-first, semantic-enhanced search // Metadata-first, semantic-enhanced search
@@ -1533,17 +1562,16 @@ const tools = [
filePath: z.string().describe('File path to search for (supports partial matching)'), filePath: z.string().describe('File path to search for (supports partial matching)'),
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)'), 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)'),
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
dateRange: z.object({ dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
start: z.union([z.string(), z.number()]).optional(), dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
end: z.union([z.string(), z.number()]).optional()
}).optional().describe('Filter by date range'),
limit: z.number().min(1).max(100).default(20).describe('Maximum results. IMPORTANT: Start with 3-5 to avoid exceeding MCP token limits, even in index mode.'), limit: z.number().min(1).max(100).default(20).describe('Maximum results. IMPORTANT: Start with 3-5 to avoid exceeding MCP token limits, even in index mode.'),
offset: z.number().min(0).default(0).describe('Number of results to skip'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { filePath, format = 'index', ...filters } = args; const normalized = normalizeParams(args);
const { filePath, format = 'index', ...filters } = normalized;
let observations: ObservationSearchResult[] = []; let observations: ObservationSearchResult[] = [];
let sessions: SessionSummarySearchResult[] = []; let sessions: SessionSummarySearchResult[] = [];
@@ -1660,23 +1688,19 @@ const tools = [
name: 'find_by_type', name: 'find_by_type',
description: 'Find observations of a specific type (decision, bugfix, feature, refactor, discovery, change). IMPORTANT: Always use index format first (default) to get an overview with minimal token usage, then use format: "full" only for specific items of interest.', description: 'Find observations of a specific type (decision, bugfix, feature, refactor, discovery, change). IMPORTANT: Always use index format first (default) to get an overview with minimal token usage, then use format: "full" only for specific items of interest.',
inputSchema: z.object({ inputSchema: z.object({
type: z.union([ type: z.string().describe('Observation type(s) to filter by (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']),
z.array(z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']))
]).describe('Observation type(s) to filter by'),
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)'), 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)'),
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
dateRange: z.object({ dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
start: z.union([z.string(), z.number()]).optional(), dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
end: z.union([z.string(), z.number()]).optional()
}).optional().describe('Filter by date range'),
limit: z.number().min(1).max(100).default(20).describe('Maximum results. IMPORTANT: Start with 3-5 to avoid exceeding MCP token limits, even in index mode.'), limit: z.number().min(1).max(100).default(20).describe('Maximum results. IMPORTANT: Start with 3-5 to avoid exceeding MCP token limits, even in index mode.'),
offset: z.number().min(0).default(0).describe('Number of results to skip'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { type, format = 'index', ...filters } = args; const normalized = normalizeParams(args);
const { type, format = 'index', ...filters } = normalized;
const typeStr = Array.isArray(type) ? type.join(', ') : type; const typeStr = Array.isArray(type) ? type.join(', ') : type;
let results: ObservationSearchResult[] = []; let results: ObservationSearchResult[] = [];
@@ -1905,17 +1929,16 @@ const tools = [
query: z.string().describe('Natural language search query for semantic ranking via ChromaDB vector search'), query: z.string().describe('Natural language search query for semantic ranking via ChromaDB vector search'),
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for truncated prompts/dates (default, RECOMMENDED for initial search), "full" for complete prompt text (use only after reviewing index results)'), format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for truncated prompts/dates (default, RECOMMENDED for initial search), "full" for complete prompt text (use only after reviewing index results)'),
project: z.string().optional().describe('Filter by project name'), project: z.string().optional().describe('Filter by project name'),
dateRange: z.object({ dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
start: z.union([z.string(), z.number()]).optional(), dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
end: z.union([z.string(), z.number()]).optional()
}).optional().describe('Filter by date range'),
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'), 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'), 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') orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
}), }),
handler: async (args: any) => { handler: async (args: any) => {
try { try {
const { query, format = 'index', ...options } = args; const normalized = normalizeParams(args);
const { query, format = 'index', ...options } = normalized;
let results: UserPromptSearchResult[] = []; let results: UserPromptSearchResult[] = [];
// Vector-first search via ChromaDB // Vector-first search via ChromaDB