sync: temporal-awareness from private repo

- Surface created_at, updated_at, event_date in queryNodes + queryDimensionNodes responses
- Add date range filters (createdAfter/Before, eventAfter/Before) to NodeFilters + nodeService.getNodes()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
“BeeRad”
2026-02-15 14:34:55 +11:00
co-authored by Claude Opus 4.6
parent 0095641278
commit 9954792b1d
4 changed files with 54 additions and 14 deletions
+23 -4
View File
@@ -10,7 +10,8 @@ export class NodeService {
// PostgreSQL path removed in SQLite-only consolidation
private async getNodesSQLite(filters: NodeFilters = {}): Promise<Node[]> {
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)
@@ -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)
+4 -1
View File
@@ -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) {
+16 -2
View File
@@ -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<Node[] | undefined> = 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<Node[] | undefined>([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
};
});
+9 -5
View File
@@ -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;
}