diff --git a/src/services/database/nodes.ts b/src/services/database/nodes.ts index b11b2d3..0c4c0a4 100644 --- a/src/services/database/nodes.ts +++ b/src/services/database/nodes.ts @@ -10,15 +10,16 @@ export class NodeService { // PostgreSQL path removed in SQLite-only consolidation private async getNodesSQLite(filters: NodeFilters = {}): Promise { - const { dimensions, search, limit = 100, offset = 0, sortBy, dimensionsMatch = 'any' } = filters; + const { dimensions, search, limit = 100, offset = 0, sortBy, dimensionsMatch = 'any', + createdAfter, createdBefore, eventAfter, eventBefore } = filters; const sqlite = getSQLiteClient(); // Use nodes_v view for array-like dimensions behavior (exclude embedding BLOB for performance) let query = ` - SELECT n.id, n.title, n.description, n.notes, n.link, n.event_date, n.metadata, n.chunk, + SELECT n.id, n.title, n.description, n.notes, n.link, n.event_date, n.metadata, n.chunk, n.chunk_status, n.embedding_updated_at, n.embedding_text, n.created_at, n.updated_at, - COALESCE((SELECT JSON_GROUP_ARRAY(d.dimension) + COALESCE((SELECT JSON_GROUP_ARRAY(d.dimension) FROM node_dimensions d WHERE d.node_id = n.id), '[]') as dimensions_json, (SELECT COUNT(*) FROM edges WHERE from_node_id = n.id OR to_node_id = n.id) as edge_count FROM nodes n @@ -47,15 +48,33 @@ export class NodeService { } } - // Text search in title, description, and content (SQLite LIKE with COLLATE NOCASE) + // Text search in title, description, and notes (SQLite LIKE with COLLATE NOCASE) if (search) { query += ` AND (n.title LIKE ? COLLATE NOCASE OR n.description LIKE ? COLLATE NOCASE OR n.notes LIKE ? COLLATE NOCASE)`; params.push(`%${search}%`, `%${search}%`, `%${search}%`); } + // Temporal filters + if (createdAfter) { + query += ` AND n.created_at >= ?`; + params.push(createdAfter); + } + if (createdBefore) { + query += ` AND n.created_at < ?`; + params.push(createdBefore); + } + if (eventAfter) { + query += ` AND n.event_date >= ?`; + params.push(eventAfter); + } + if (eventBefore) { + query += ` AND n.event_date < ?`; + params.push(eventBefore); + } + // Sorting logic if (search) { - // For search queries, prioritize by relevance: exact title → starts with → contains in title → description → content + // For search queries, prioritize by relevance: exact title → starts with → contains in title → description → notes query += ` ORDER BY CASE WHEN LOWER(n.title) = LOWER(?) THEN 1 ELSE 6 END, CASE WHEN LOWER(n.title) LIKE LOWER(?) THEN 2 ELSE 6 END, @@ -68,7 +87,7 @@ export class NodeService { `${search}%`, // Starts with search term `%${search}%`, // Contains in title `%${search}%`, // Contains in description - `%${search}%` // Contains in content + `%${search}%` // Contains in notes ); } else if (sortBy === 'edges') { // Sort by edge count (most connected first) diff --git a/src/tools/database/queryDimensionNodes.ts b/src/tools/database/queryDimensionNodes.ts index cc11be6..d86de47 100644 --- a/src/tools/database/queryDimensionNodes.ts +++ b/src/tools/database/queryDimensionNodes.ts @@ -5,7 +5,7 @@ import { formatNodeForChat } from '../infrastructure/nodeFormatter'; import type { Node } from '@/types/database'; export const queryDimensionNodesTool = tool({ - description: 'Query all nodes within a specific dimension. Returns nodes sorted by edge count (most connected first).', + description: 'Get nodes in a dimension, sorted by connection count.', inputSchema: z.object({ dimension: z.string().describe('The dimension name to query nodes from'), limit: z.number().optional().default(20).describe('Maximum number of nodes to return (default: 20)'), @@ -43,6 +43,9 @@ export const queryDimensionNodesTool = tool({ }), edgeCount: node.edge_count || 0, dimensions: node.dimensions || [], + created_at: node.created_at, + updated_at: node.updated_at, + event_date: node.event_date ?? null, }; if (includeContent && node.notes) { diff --git a/src/tools/database/queryNodes.ts b/src/tools/database/queryNodes.ts index 729e017..4009809 100644 --- a/src/tools/database/queryNodes.ts +++ b/src/tools/database/queryNodes.ts @@ -10,7 +10,11 @@ export const queryNodesTool = tool({ filters: z.object({ dimensions: z.array(z.string()).describe('Filter by dimensions (e.g., ["research", "ai", "technology"]). Replaces old type/stage filtering.').optional(), search: z.string().describe('Search term to match against title or notes').optional(), - limit: z.number().min(1).max(50).default(10).describe('Maximum number of results to return') + limit: z.number().min(1).max(50).default(10).describe('Maximum number of results to return'), + createdAfter: z.string().optional().describe('ISO date (YYYY-MM-DD). Only return nodes created on or after this date.'), + createdBefore: z.string().optional().describe('ISO date (YYYY-MM-DD). Only return nodes created before this date.'), + eventAfter: z.string().optional().describe('ISO date (YYYY-MM-DD). Only return nodes with event_date on or after this date.'), + eventBefore: z.string().optional().describe('ISO date (YYYY-MM-DD). Only return nodes with event_date before this date.'), }).optional() }), execute: async ({ filters = {} }) => { @@ -47,6 +51,9 @@ export const queryNodesTool = tool({ id: node.id, title: node.title, dimensions: node.dimensions || [], + created_at: node.created_at, + updated_at: node.updated_at, + event_date: node.event_date ?? null, formatted_display: formatted, }], count: 1, @@ -65,7 +72,11 @@ export const queryNodesTool = tool({ const nodesPromise: Promise = nodeService.getNodes({ limit, dimensions: filters.dimensions, - search: filters.search + search: filters.search, + createdAfter: filters.createdAfter, + createdBefore: filters.createdBefore, + eventAfter: filters.eventAfter, + eventBefore: filters.eventBefore, }); const nodes = await Promise.race([nodesPromise, timeoutPromise]); @@ -85,6 +96,9 @@ export const queryNodesTool = tool({ id: node.id, title: node.title, dimensions: node.dimensions || [], + created_at: node.created_at, + updated_at: node.updated_at, + event_date: node.event_date ?? null, formatted_display: formatted }; }); diff --git a/src/types/database.ts b/src/types/database.ts index db8af22..47a7c15 100644 --- a/src/types/database.ts +++ b/src/types/database.ts @@ -3,19 +3,18 @@ export interface Node { id: number; title: string; description?: string; - notes?: string; // Consolidated content from description + abstract + notes + notes?: string; // User's notes/thoughts about this node link?: string; - event_date?: string; + event_date?: string | null; // When the thing actually happened (ISO 8601) dimensions: string[]; // Flexible dimensions replacing type + stage + segment + tags embedding?: Buffer; // Node-level embedding (BLOB data) chunk?: string; - metadata?: any; // Flexible metadata storage from extras + chunk_status + sub_type + metadata?: any; // Flexible metadata storage created_at: string; updated_at: string; - // Legacy pin flag (read-only, slated for removal) edge_count?: number; // Derived count of edges, included in some queries - // Optional embedding fields (restored from migration) + // Optional embedding fields embedding_updated_at?: string; embedding_text?: string; chunk_status?: 'not_chunked' | 'chunking' | 'chunked' | 'error' | null; @@ -72,6 +71,10 @@ export interface NodeFilters { offset?: number; sortBy?: 'updated' | 'edges' | 'created'; // Sort by updated_at, edge count, or created_at dimensionsMatch?: 'any' | 'all'; // 'any' = OR (default), 'all' = AND + createdAfter?: string; // ISO date (YYYY-MM-DD) — nodes created on or after + createdBefore?: string; // ISO date (YYYY-MM-DD) — nodes created before + eventAfter?: string; // ISO date (YYYY-MM-DD) — nodes with event_date on or after + eventBefore?: string; // ISO date (YYYY-MM-DD) — nodes with event_date before } export interface ChunkData { @@ -118,6 +121,7 @@ export interface DatabaseError { export interface Dimension { name: string; description?: string | null; + icon?: string | null; is_priority: boolean; updated_at: string; }