claude-brain 0.30.2 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
|
@@ -1,190 +1,190 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Temporal Query Processor
|
|
3
|
-
* Parses temporal expressions from natural language using chrono-node
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as chrono from 'chrono-node'
|
|
7
|
-
import type { Logger } from 'pino'
|
|
8
|
-
|
|
9
|
-
export interface TemporalQuery {
|
|
10
|
-
/** Original query text */
|
|
11
|
-
original: string
|
|
12
|
-
/** Cleaned query with temporal expressions removed */
|
|
13
|
-
cleaned: string
|
|
14
|
-
/** Parsed start date (if any) */
|
|
15
|
-
startDate?: string
|
|
16
|
-
/** Parsed end date (if any) */
|
|
17
|
-
endDate?: string
|
|
18
|
-
/** Temporal intent detected */
|
|
19
|
-
intent: 'range' | 'before' | 'after' | 'point' | 'none'
|
|
20
|
-
/** Relative time expressions found */
|
|
21
|
-
expressions: string[]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class TemporalQueryProcessor {
|
|
25
|
-
private logger: Logger
|
|
26
|
-
|
|
27
|
-
constructor(logger: Logger) {
|
|
28
|
-
this.logger = logger.child({ component: 'temporal-query-processor' })
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Parse temporal expressions from a natural language query
|
|
33
|
-
*/
|
|
34
|
-
parse(query: string): TemporalQuery {
|
|
35
|
-
const result: TemporalQuery = {
|
|
36
|
-
original: query,
|
|
37
|
-
cleaned: query,
|
|
38
|
-
intent: 'none',
|
|
39
|
-
expressions: []
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
// Parse with chrono-node
|
|
44
|
-
const parsed = chrono.parse(query, new Date(), { forwardDate: false })
|
|
45
|
-
|
|
46
|
-
if (parsed.length === 0) {
|
|
47
|
-
// Try manual temporal patterns
|
|
48
|
-
return this.parseManualPatterns(query, result)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const ref = parsed[0]!
|
|
52
|
-
result.expressions.push(ref.text)
|
|
53
|
-
|
|
54
|
-
// Remove temporal expression from query for cleaner semantic search
|
|
55
|
-
result.cleaned = query.replace(ref.text, '').replace(/\s+/g, ' ').trim()
|
|
56
|
-
|
|
57
|
-
if (ref.start && ref.end) {
|
|
58
|
-
// Date range
|
|
59
|
-
result.startDate = ref.start.date().toISOString()
|
|
60
|
-
result.endDate = ref.end.date().toISOString()
|
|
61
|
-
result.intent = 'range'
|
|
62
|
-
} else if (ref.start) {
|
|
63
|
-
const startDate = ref.start.date()
|
|
64
|
-
|
|
65
|
-
// Check context for before/after
|
|
66
|
-
const beforeIndex = query.toLowerCase().indexOf('before')
|
|
67
|
-
const afterIndex = query.toLowerCase().indexOf('after')
|
|
68
|
-
const sinceIndex = query.toLowerCase().indexOf('since')
|
|
69
|
-
const untilIndex = query.toLowerCase().indexOf('until')
|
|
70
|
-
|
|
71
|
-
if (beforeIndex >= 0 && beforeIndex < query.indexOf(ref.text)) {
|
|
72
|
-
result.endDate = startDate.toISOString()
|
|
73
|
-
result.intent = 'before'
|
|
74
|
-
} else if (
|
|
75
|
-
(afterIndex >= 0 && afterIndex < query.indexOf(ref.text)) ||
|
|
76
|
-
(sinceIndex >= 0 && sinceIndex < query.indexOf(ref.text))
|
|
77
|
-
) {
|
|
78
|
-
result.startDate = startDate.toISOString()
|
|
79
|
-
result.intent = 'after'
|
|
80
|
-
} else if (untilIndex >= 0 && untilIndex < query.indexOf(ref.text)) {
|
|
81
|
-
result.endDate = startDate.toISOString()
|
|
82
|
-
result.intent = 'before'
|
|
83
|
-
} else {
|
|
84
|
-
// Default: treat as a point in time, create a window around it
|
|
85
|
-
const dayBefore = new Date(startDate)
|
|
86
|
-
dayBefore.setDate(dayBefore.getDate() - 1)
|
|
87
|
-
const dayAfter = new Date(startDate)
|
|
88
|
-
dayAfter.setDate(dayAfter.getDate() + 1)
|
|
89
|
-
|
|
90
|
-
result.startDate = dayBefore.toISOString()
|
|
91
|
-
result.endDate = dayAfter.toISOString()
|
|
92
|
-
result.intent = 'point'
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Handle multiple temporal expressions
|
|
97
|
-
for (let i = 1; i < parsed.length; i++) {
|
|
98
|
-
result.expressions.push(parsed[i]!.text)
|
|
99
|
-
result.cleaned = result.cleaned.replace(parsed[i]!.text, '').replace(/\s+/g, ' ').trim()
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
this.logger.debug({ error, query }, 'Failed to parse temporal expression')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return result
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private parseManualPatterns(query: string, result: TemporalQuery): TemporalQuery {
|
|
109
|
-
const lowerQuery = query.toLowerCase()
|
|
110
|
-
const now = new Date()
|
|
111
|
-
|
|
112
|
-
// "last week", "last month", "last year"
|
|
113
|
-
const lastMatch = lowerQuery.match(/\b(?:in the )?last\s+(week|month|year|quarter|(\d+)\s*(days?|weeks?|months?))\b/)
|
|
114
|
-
if (lastMatch) {
|
|
115
|
-
result.expressions.push(lastMatch[0])
|
|
116
|
-
result.cleaned = query.replace(new RegExp(lastMatch[0], 'i'), '').replace(/\s+/g, ' ').trim()
|
|
117
|
-
result.endDate = now.toISOString()
|
|
118
|
-
|
|
119
|
-
const start = new Date(now)
|
|
120
|
-
if (lastMatch[1] === 'week') {
|
|
121
|
-
start.setDate(start.getDate() - 7)
|
|
122
|
-
} else if (lastMatch[1] === 'month') {
|
|
123
|
-
start.setMonth(start.getMonth() - 1)
|
|
124
|
-
} else if (lastMatch[1] === 'year') {
|
|
125
|
-
start.setFullYear(start.getFullYear() - 1)
|
|
126
|
-
} else if (lastMatch[1] === 'quarter') {
|
|
127
|
-
start.setMonth(start.getMonth() - 3)
|
|
128
|
-
} else if (lastMatch[2] && lastMatch[3]) {
|
|
129
|
-
const n = parseInt(lastMatch[2])
|
|
130
|
-
const unit = lastMatch[3].replace(/s$/, '')
|
|
131
|
-
if (unit === 'day') start.setDate(start.getDate() - n)
|
|
132
|
-
else if (unit === 'week') start.setDate(start.getDate() - n * 7)
|
|
133
|
-
else if (unit === 'month') start.setMonth(start.getMonth() - n)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
result.startDate = start.toISOString()
|
|
137
|
-
result.intent = 'range'
|
|
138
|
-
return result
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// "recently", "lately"
|
|
142
|
-
if (/\b(recently|lately)\b/.test(lowerQuery)) {
|
|
143
|
-
const match = lowerQuery.match(/\b(recently|lately)\b/)!
|
|
144
|
-
result.expressions.push(match[0])
|
|
145
|
-
result.cleaned = query.replace(new RegExp(match[0], 'i'), '').replace(/\s+/g, ' ').trim()
|
|
146
|
-
const start = new Date(now)
|
|
147
|
-
start.setDate(start.getDate() - 14) // 2 weeks for "recently"
|
|
148
|
-
result.startDate = start.toISOString()
|
|
149
|
-
result.endDate = now.toISOString()
|
|
150
|
-
result.intent = 'range'
|
|
151
|
-
return result
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// "this week", "this month", "this year"
|
|
155
|
-
const thisMatch = lowerQuery.match(/\bthis\s+(week|month|year)\b/)
|
|
156
|
-
if (thisMatch) {
|
|
157
|
-
result.expressions.push(thisMatch[0])
|
|
158
|
-
result.cleaned = query.replace(new RegExp(thisMatch[0], 'i'), '').replace(/\s+/g, ' ').trim()
|
|
159
|
-
result.endDate = now.toISOString()
|
|
160
|
-
|
|
161
|
-
const start = new Date(now)
|
|
162
|
-
if (thisMatch[1] === 'week') {
|
|
163
|
-
start.setDate(start.getDate() - start.getDay())
|
|
164
|
-
start.setHours(0, 0, 0, 0)
|
|
165
|
-
} else if (thisMatch[1] === 'month') {
|
|
166
|
-
start.setDate(1)
|
|
167
|
-
start.setHours(0, 0, 0, 0)
|
|
168
|
-
} else if (thisMatch[1] === 'year') {
|
|
169
|
-
start.setMonth(0, 1)
|
|
170
|
-
start.setHours(0, 0, 0, 0)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
result.startDate = start.toISOString()
|
|
174
|
-
result.intent = 'range'
|
|
175
|
-
return result
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// "ever", "all time", "historically"
|
|
179
|
-
if (/\b(ever|all\s+time|historically|over\s+time)\b/.test(lowerQuery)) {
|
|
180
|
-
const match = lowerQuery.match(/\b(ever|all\s+time|historically|over\s+time)\b/)!
|
|
181
|
-
result.expressions.push(match[0])
|
|
182
|
-
result.cleaned = query.replace(new RegExp(match[0], 'i'), '').replace(/\s+/g, ' ').trim()
|
|
183
|
-
result.intent = 'range'
|
|
184
|
-
// No date constraints = all time
|
|
185
|
-
return result
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return result
|
|
189
|
-
}
|
|
190
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Query Processor
|
|
3
|
+
* Parses temporal expressions from natural language using chrono-node
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as chrono from 'chrono-node'
|
|
7
|
+
import type { Logger } from 'pino'
|
|
8
|
+
|
|
9
|
+
export interface TemporalQuery {
|
|
10
|
+
/** Original query text */
|
|
11
|
+
original: string
|
|
12
|
+
/** Cleaned query with temporal expressions removed */
|
|
13
|
+
cleaned: string
|
|
14
|
+
/** Parsed start date (if any) */
|
|
15
|
+
startDate?: string
|
|
16
|
+
/** Parsed end date (if any) */
|
|
17
|
+
endDate?: string
|
|
18
|
+
/** Temporal intent detected */
|
|
19
|
+
intent: 'range' | 'before' | 'after' | 'point' | 'none'
|
|
20
|
+
/** Relative time expressions found */
|
|
21
|
+
expressions: string[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class TemporalQueryProcessor {
|
|
25
|
+
private logger: Logger
|
|
26
|
+
|
|
27
|
+
constructor(logger: Logger) {
|
|
28
|
+
this.logger = logger.child({ component: 'temporal-query-processor' })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse temporal expressions from a natural language query
|
|
33
|
+
*/
|
|
34
|
+
parse(query: string): TemporalQuery {
|
|
35
|
+
const result: TemporalQuery = {
|
|
36
|
+
original: query,
|
|
37
|
+
cleaned: query,
|
|
38
|
+
intent: 'none',
|
|
39
|
+
expressions: []
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Parse with chrono-node
|
|
44
|
+
const parsed = chrono.parse(query, new Date(), { forwardDate: false })
|
|
45
|
+
|
|
46
|
+
if (parsed.length === 0) {
|
|
47
|
+
// Try manual temporal patterns
|
|
48
|
+
return this.parseManualPatterns(query, result)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const ref = parsed[0]!
|
|
52
|
+
result.expressions.push(ref.text)
|
|
53
|
+
|
|
54
|
+
// Remove temporal expression from query for cleaner semantic search
|
|
55
|
+
result.cleaned = query.replace(ref.text, '').replace(/\s+/g, ' ').trim()
|
|
56
|
+
|
|
57
|
+
if (ref.start && ref.end) {
|
|
58
|
+
// Date range
|
|
59
|
+
result.startDate = ref.start.date().toISOString()
|
|
60
|
+
result.endDate = ref.end.date().toISOString()
|
|
61
|
+
result.intent = 'range'
|
|
62
|
+
} else if (ref.start) {
|
|
63
|
+
const startDate = ref.start.date()
|
|
64
|
+
|
|
65
|
+
// Check context for before/after
|
|
66
|
+
const beforeIndex = query.toLowerCase().indexOf('before')
|
|
67
|
+
const afterIndex = query.toLowerCase().indexOf('after')
|
|
68
|
+
const sinceIndex = query.toLowerCase().indexOf('since')
|
|
69
|
+
const untilIndex = query.toLowerCase().indexOf('until')
|
|
70
|
+
|
|
71
|
+
if (beforeIndex >= 0 && beforeIndex < query.indexOf(ref.text)) {
|
|
72
|
+
result.endDate = startDate.toISOString()
|
|
73
|
+
result.intent = 'before'
|
|
74
|
+
} else if (
|
|
75
|
+
(afterIndex >= 0 && afterIndex < query.indexOf(ref.text)) ||
|
|
76
|
+
(sinceIndex >= 0 && sinceIndex < query.indexOf(ref.text))
|
|
77
|
+
) {
|
|
78
|
+
result.startDate = startDate.toISOString()
|
|
79
|
+
result.intent = 'after'
|
|
80
|
+
} else if (untilIndex >= 0 && untilIndex < query.indexOf(ref.text)) {
|
|
81
|
+
result.endDate = startDate.toISOString()
|
|
82
|
+
result.intent = 'before'
|
|
83
|
+
} else {
|
|
84
|
+
// Default: treat as a point in time, create a window around it
|
|
85
|
+
const dayBefore = new Date(startDate)
|
|
86
|
+
dayBefore.setDate(dayBefore.getDate() - 1)
|
|
87
|
+
const dayAfter = new Date(startDate)
|
|
88
|
+
dayAfter.setDate(dayAfter.getDate() + 1)
|
|
89
|
+
|
|
90
|
+
result.startDate = dayBefore.toISOString()
|
|
91
|
+
result.endDate = dayAfter.toISOString()
|
|
92
|
+
result.intent = 'point'
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Handle multiple temporal expressions
|
|
97
|
+
for (let i = 1; i < parsed.length; i++) {
|
|
98
|
+
result.expressions.push(parsed[i]!.text)
|
|
99
|
+
result.cleaned = result.cleaned.replace(parsed[i]!.text, '').replace(/\s+/g, ' ').trim()
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
this.logger.debug({ error, query }, 'Failed to parse temporal expression')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private parseManualPatterns(query: string, result: TemporalQuery): TemporalQuery {
|
|
109
|
+
const lowerQuery = query.toLowerCase()
|
|
110
|
+
const now = new Date()
|
|
111
|
+
|
|
112
|
+
// "last week", "last month", "last year"
|
|
113
|
+
const lastMatch = lowerQuery.match(/\b(?:in the )?last\s+(week|month|year|quarter|(\d+)\s*(days?|weeks?|months?))\b/)
|
|
114
|
+
if (lastMatch) {
|
|
115
|
+
result.expressions.push(lastMatch[0])
|
|
116
|
+
result.cleaned = query.replace(new RegExp(lastMatch[0], 'i'), '').replace(/\s+/g, ' ').trim()
|
|
117
|
+
result.endDate = now.toISOString()
|
|
118
|
+
|
|
119
|
+
const start = new Date(now)
|
|
120
|
+
if (lastMatch[1] === 'week') {
|
|
121
|
+
start.setDate(start.getDate() - 7)
|
|
122
|
+
} else if (lastMatch[1] === 'month') {
|
|
123
|
+
start.setMonth(start.getMonth() - 1)
|
|
124
|
+
} else if (lastMatch[1] === 'year') {
|
|
125
|
+
start.setFullYear(start.getFullYear() - 1)
|
|
126
|
+
} else if (lastMatch[1] === 'quarter') {
|
|
127
|
+
start.setMonth(start.getMonth() - 3)
|
|
128
|
+
} else if (lastMatch[2] && lastMatch[3]) {
|
|
129
|
+
const n = parseInt(lastMatch[2])
|
|
130
|
+
const unit = lastMatch[3].replace(/s$/, '')
|
|
131
|
+
if (unit === 'day') start.setDate(start.getDate() - n)
|
|
132
|
+
else if (unit === 'week') start.setDate(start.getDate() - n * 7)
|
|
133
|
+
else if (unit === 'month') start.setMonth(start.getMonth() - n)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
result.startDate = start.toISOString()
|
|
137
|
+
result.intent = 'range'
|
|
138
|
+
return result
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// "recently", "lately"
|
|
142
|
+
if (/\b(recently|lately)\b/.test(lowerQuery)) {
|
|
143
|
+
const match = lowerQuery.match(/\b(recently|lately)\b/)!
|
|
144
|
+
result.expressions.push(match[0])
|
|
145
|
+
result.cleaned = query.replace(new RegExp(match[0], 'i'), '').replace(/\s+/g, ' ').trim()
|
|
146
|
+
const start = new Date(now)
|
|
147
|
+
start.setDate(start.getDate() - 14) // 2 weeks for "recently"
|
|
148
|
+
result.startDate = start.toISOString()
|
|
149
|
+
result.endDate = now.toISOString()
|
|
150
|
+
result.intent = 'range'
|
|
151
|
+
return result
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// "this week", "this month", "this year"
|
|
155
|
+
const thisMatch = lowerQuery.match(/\bthis\s+(week|month|year)\b/)
|
|
156
|
+
if (thisMatch) {
|
|
157
|
+
result.expressions.push(thisMatch[0])
|
|
158
|
+
result.cleaned = query.replace(new RegExp(thisMatch[0], 'i'), '').replace(/\s+/g, ' ').trim()
|
|
159
|
+
result.endDate = now.toISOString()
|
|
160
|
+
|
|
161
|
+
const start = new Date(now)
|
|
162
|
+
if (thisMatch[1] === 'week') {
|
|
163
|
+
start.setDate(start.getDate() - start.getDay())
|
|
164
|
+
start.setHours(0, 0, 0, 0)
|
|
165
|
+
} else if (thisMatch[1] === 'month') {
|
|
166
|
+
start.setDate(1)
|
|
167
|
+
start.setHours(0, 0, 0, 0)
|
|
168
|
+
} else if (thisMatch[1] === 'year') {
|
|
169
|
+
start.setMonth(0, 1)
|
|
170
|
+
start.setHours(0, 0, 0, 0)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
result.startDate = start.toISOString()
|
|
174
|
+
result.intent = 'range'
|
|
175
|
+
return result
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// "ever", "all time", "historically"
|
|
179
|
+
if (/\b(ever|all\s+time|historically|over\s+time)\b/.test(lowerQuery)) {
|
|
180
|
+
const match = lowerQuery.match(/\b(ever|all\s+time|historically|over\s+time)\b/)!
|
|
181
|
+
result.expressions.push(match[0])
|
|
182
|
+
result.cleaned = query.replace(new RegExp(match[0], 'i'), '').replace(/\s+/g, ' ').trim()
|
|
183
|
+
result.intent = 'range'
|
|
184
|
+
// No date constraints = all time
|
|
185
|
+
return result
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result
|
|
189
|
+
}
|
|
190
|
+
}
|