claude-brain 0.15.2 → 0.16.0
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 +191 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -11
- package/bunfig.toml +8 -8
- package/package.json +82 -82
- 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 +341 -341
- package/src/automation/auto-context.ts +240 -240
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/index.ts +11 -11
- 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 +205 -205
- package/src/cli/auto-setup.ts +82 -82
- package/src/cli/bin.ts +209 -202
- package/src/cli/commands/chroma.ts +573 -573
- package/src/cli/commands/git-hook.ts +189 -189
- package/src/cli/commands/hooks.ts +213 -213
- package/src/cli/commands/init.ts +122 -122
- package/src/cli/commands/install-mcp.ts +92 -92
- package/src/cli/commands/pack.ts +197 -197
- package/src/cli/commands/refresh.ts +323 -0
- package/src/cli/commands/serve.ts +167 -173
- package/src/cli/commands/start.ts +42 -42
- package/src/cli/commands/uninstall-mcp.ts +41 -41
- package/src/cli/commands/update.ts +124 -121
- package/src/cli/diagnose.ts +4 -4
- package/src/cli/health-check.ts +4 -4
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/setup.ts +4 -4
- 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/config/defaults.ts +50 -50
- package/src/config/home.ts +55 -55
- package/src/config/index.ts +7 -7
- package/src/config/loader.ts +166 -166
- package/src/config/migration.ts +76 -76
- package/src/config/schema.ts +360 -360
- package/src/config/validator.ts +184 -184
- package/src/config/watcher.ts +86 -86
- package/src/context/assembler.ts +398 -398
- package/src/context/cache-manager.ts +101 -101
- package/src/context/formatter.ts +84 -84
- package/src/context/hierarchy.ts +85 -85
- package/src/context/index.ts +83 -83
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/types.ts +252 -252
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +123 -123
- package/src/health/index.ts +229 -229
- package/src/hooks/brain-hook.ts +128 -112
- package/src/hooks/capture.ts +168 -205
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +207 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +194 -194
- package/src/hooks/passive-classifier.ts +404 -723
- package/src/hooks/queue.ts +129 -129
- package/src/hooks/session-tracker.ts +312 -275
- package/src/hooks/types.ts +47 -47
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/affinity.ts +162 -162
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +13 -13
- package/src/intelligence/cross-project/transfer.ts +201 -201
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +207 -207
- package/src/intelligence/prediction/context-anticipator.ts +198 -198
- package/src/intelligence/prediction/decision-predictor.ts +184 -184
- package/src/intelligence/prediction/index.ts +13 -13
- package/src/intelligence/prediction/recommender.ts +268 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +247 -247
- package/src/intelligence/reasoning/counterfactual.ts +248 -248
- package/src/intelligence/reasoning/index.ts +13 -13
- package/src/intelligence/reasoning/synthesizer.ts +169 -169
- package/src/intelligence/temporal/evolution.ts +197 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +259 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/knowledge/entity-extractor.ts +416 -416
- 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 +168 -168
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +174 -174
- package/src/memory/chroma/collection-manager.ts +94 -94
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +155 -155
- 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 +315 -315
- package/src/memory/chroma/store.ts +741 -741
- package/src/memory/consolidation/archiver.ts +164 -164
- package/src/memory/consolidation/merger.ts +186 -186
- package/src/memory/consolidation/scorer.ts +138 -138
- package/src/memory/context-builder.ts +236 -236
- package/src/memory/database.ts +169 -169
- package/src/memory/embedding-utils.ts +156 -156
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +351 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/index.ts +582 -582
- package/src/memory/knowledge-extractor.ts +455 -455
- package/src/memory/learning.ts +378 -378
- package/src/memory/patterns.ts +396 -396
- package/src/memory/schema.ts +88 -88
- package/src/memory/search.ts +309 -309
- package/src/memory/store.ts +787 -787
- package/src/memory/types.ts +121 -121
- package/src/orchestrator/coordinator.ts +272 -272
- package/src/orchestrator/decision-logger.ts +228 -228
- package/src/orchestrator/event-emitter.ts +198 -198
- package/src/orchestrator/event-queue.ts +184 -184
- package/src/orchestrator/handlers/base-handler.ts +70 -70
- package/src/orchestrator/handlers/context-handler.ts +73 -73
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/orchestrator/handlers/index.ts +10 -10
- package/src/orchestrator/handlers/status-handler.ts +131 -131
- package/src/orchestrator/handlers/task-handler.ts +171 -171
- package/src/orchestrator/index.ts +275 -275
- package/src/orchestrator/task-parser.ts +284 -284
- package/src/orchestrator/types.ts +98 -98
- 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 -300
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +223 -223
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +223 -223
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +163 -163
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +198 -198
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +236 -236
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +188 -188
- package/src/retrieval/reranker/model.ts +95 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +428 -428
- package/src/routing/intent-classifier.ts +450 -436
- package/src/routing/response-filter.ts +261 -258
- package/src/routing/router.ts +1441 -1322
- package/src/routing/search-engine.ts +515 -475
- package/src/routing/types.ts +94 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/handlers/call-tool.ts +156 -156
- package/src/server/handlers/index.ts +9 -9
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/analyze-decision-evolution.ts +151 -151
- package/src/server/handlers/tools/auto-remember.ts +200 -200
- package/src/server/handlers/tools/brain.ts +85 -85
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/detect-trends.ts +144 -144
- package/src/server/handlers/tools/find-cross-project-patterns.ts +168 -168
- package/src/server/handlers/tools/get-activity-log.ts +194 -194
- package/src/server/handlers/tools/get-code-standards.ts +124 -124
- package/src/server/handlers/tools/get-corrections.ts +154 -154
- package/src/server/handlers/tools/get-decision-timeline.ts +172 -172
- package/src/server/handlers/tools/get-episode.ts +103 -103
- package/src/server/handlers/tools/get-patterns.ts +158 -158
- package/src/server/handlers/tools/get-phase12-status.ts +63 -63
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/get-recommendations.ts +145 -145
- package/src/server/handlers/tools/index.ts +31 -31
- package/src/server/handlers/tools/init-project.ts +757 -757
- package/src/server/handlers/tools/list-episodes.ts +90 -90
- package/src/server/handlers/tools/list-projects.ts +125 -125
- package/src/server/handlers/tools/rate-memory.ts +101 -101
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +126 -126
- package/src/server/handlers/tools/record-correction.ts +125 -125
- package/src/server/handlers/tools/remember-decision.ts +153 -153
- package/src/server/handlers/tools/schemas.ts +253 -253
- package/src/server/handlers/tools/search-knowledge-graph.ts +102 -102
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/handlers/tools/what-if-analysis.ts +135 -135
- package/src/server/http-api.ts +693 -693
- package/src/server/index.ts +40 -40
- package/src/server/mcp-server.ts +283 -283
- package/src/server/providers/index.ts +7 -7
- package/src/server/providers/prompts.ts +327 -327
- package/src/server/providers/resources.ts +622 -622
- package/src/server/services.ts +468 -468
- package/src/server/types.ts +39 -39
- package/src/server/utils/error-handler.ts +155 -155
- package/src/server/utils/index.ts +13 -13
- package/src/server/utils/memory-indicator.ts +83 -83
- package/src/server/utils/request-context.ts +122 -122
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/utils/validators.ts +210 -210
- package/src/setup/index.ts +48 -48
- package/src/setup/wizard.ts +461 -461
- package/src/tools/index.ts +24 -24
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.test.ts +30 -30
- package/src/tools/schemas.ts +617 -617
- package/src/tools/types.ts +412 -412
- package/src/utils/circuit-breaker.ts +130 -130
- package/src/utils/cleanup.ts +34 -34
- package/src/utils/error-handler.ts +132 -132
- package/src/utils/error-messages.ts +60 -60
- package/src/utils/fallback.ts +45 -45
- package/src/utils/index.ts +54 -54
- package/src/utils/logger-utils.ts +80 -80
- package/src/utils/logger.ts +88 -88
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/retry.ts +94 -94
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/frontmatter.ts +264 -264
- package/src/vault/index.ts +318 -318
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +422 -422
- package/src/vault/reader.ts +264 -264
- package/src/vault/templates.ts +186 -186
- package/src/vault/types.ts +73 -73
- package/src/vault/watcher.ts +277 -277
- package/src/vault/writer.ts +413 -413
- package/tsconfig.json +30 -30
- package/src/cli/auto-update.ts +0 -157
package/src/vault/query.ts
CHANGED
|
@@ -1,422 +1,422 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vault Query Engine
|
|
3
|
-
* Powerful search and query capabilities across the entire vault
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import path from 'path'
|
|
7
|
-
import type { Logger } from 'pino'
|
|
8
|
-
import type { MarkdownFile } from './types'
|
|
9
|
-
import { VaultReader } from './reader'
|
|
10
|
-
import { FrontmatterUtils } from './frontmatter'
|
|
11
|
-
|
|
12
|
-
export interface QueryOptions {
|
|
13
|
-
text?: string // Full-text search
|
|
14
|
-
tags?: string[] // Filter by tags
|
|
15
|
-
status?: string // Filter by status
|
|
16
|
-
project?: string // Filter by project
|
|
17
|
-
type?: string // Filter by document type
|
|
18
|
-
dateRange?: {
|
|
19
|
-
// Filter by date range
|
|
20
|
-
start?: Date
|
|
21
|
-
end?: Date
|
|
22
|
-
field?: string // Which date field to use (default: 'updated')
|
|
23
|
-
}
|
|
24
|
-
techStack?: string[] // Filter by tech stack
|
|
25
|
-
limit?: number // Max results
|
|
26
|
-
offset?: number // Skip first N results
|
|
27
|
-
sortBy?: 'score' | 'date' | 'name' // Sort order
|
|
28
|
-
sortDirection?: 'asc' | 'desc'
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface QueryResult {
|
|
32
|
-
file: MarkdownFile
|
|
33
|
-
score: number // Relevance score
|
|
34
|
-
matches: string[] // Matched terms
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface QueryStats {
|
|
38
|
-
totalFiles: number
|
|
39
|
-
matchedFiles: number
|
|
40
|
-
executionTimeMs: number
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class VaultQuery {
|
|
44
|
-
private reader: VaultReader
|
|
45
|
-
private logger: Logger
|
|
46
|
-
|
|
47
|
-
constructor(reader: VaultReader, logger: Logger) {
|
|
48
|
-
this.reader = reader
|
|
49
|
-
this.logger = logger.child({ component: 'vault-query' })
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Query vault with multiple filters
|
|
54
|
-
*/
|
|
55
|
-
async query(
|
|
56
|
-
vaultPath: string,
|
|
57
|
-
options: QueryOptions
|
|
58
|
-
): Promise<{ results: QueryResult[]; stats: QueryStats }> {
|
|
59
|
-
const startTime = Date.now()
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
// Get all markdown files
|
|
63
|
-
const projectsPath = path.join(vaultPath, 'Projects')
|
|
64
|
-
const globalPath = path.join(vaultPath, 'Global')
|
|
65
|
-
|
|
66
|
-
// Read from both Projects and Global directories
|
|
67
|
-
const projectFiles = await this.reader.readDirectory(projectsPath, true)
|
|
68
|
-
const globalFiles = await this.reader.readDirectory(globalPath, true)
|
|
69
|
-
const allFiles = [...projectFiles, ...globalFiles]
|
|
70
|
-
|
|
71
|
-
// Read all files
|
|
72
|
-
const markdownFiles = await Promise.all(
|
|
73
|
-
allFiles.map(f =>
|
|
74
|
-
this.reader.readMarkdownFile(f).catch(() => null)
|
|
75
|
-
)
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
// Filter out failed reads
|
|
79
|
-
const validFiles = markdownFiles.filter(
|
|
80
|
-
(f): f is MarkdownFile => f !== null
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
// Filter and score
|
|
84
|
-
let results = validFiles
|
|
85
|
-
.map(file => this.scoreFile(file, options))
|
|
86
|
-
.filter(result => result.score > 0)
|
|
87
|
-
|
|
88
|
-
// Sort results
|
|
89
|
-
results = this.sortResults(results, options)
|
|
90
|
-
|
|
91
|
-
// Apply offset and limit
|
|
92
|
-
if (options.offset) {
|
|
93
|
-
results = results.slice(options.offset)
|
|
94
|
-
}
|
|
95
|
-
if (options.limit) {
|
|
96
|
-
results = results.slice(0, options.limit)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const stats: QueryStats = {
|
|
100
|
-
totalFiles: allFiles.length,
|
|
101
|
-
matchedFiles: results.length,
|
|
102
|
-
executionTimeMs: Date.now() - startTime
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
this.logger.debug(
|
|
106
|
-
{ options, stats },
|
|
107
|
-
'Query executed'
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
return { results, stats }
|
|
111
|
-
} catch (error) {
|
|
112
|
-
this.logger.error({ error, options }, 'Query failed')
|
|
113
|
-
return {
|
|
114
|
-
results: [],
|
|
115
|
-
stats: {
|
|
116
|
-
totalFiles: 0,
|
|
117
|
-
matchedFiles: 0,
|
|
118
|
-
executionTimeMs: Date.now() - startTime
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Score a file against query options
|
|
126
|
-
*/
|
|
127
|
-
private scoreFile(file: MarkdownFile, options: QueryOptions): QueryResult {
|
|
128
|
-
let score = 0
|
|
129
|
-
const matches: string[] = []
|
|
130
|
-
|
|
131
|
-
// Text search
|
|
132
|
-
if (options.text) {
|
|
133
|
-
const textScore = this.scoreTextMatch(file, options.text)
|
|
134
|
-
score += textScore.score
|
|
135
|
-
matches.push(...textScore.matches)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Tag filter
|
|
139
|
-
if (options.tags && options.tags.length > 0) {
|
|
140
|
-
const fileTags = FrontmatterUtils.getTags(file.frontmatter)
|
|
141
|
-
const matchedTags = options.tags.filter(tag =>
|
|
142
|
-
fileTags.some(ft => ft.toLowerCase() === tag.toLowerCase())
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
if (matchedTags.length > 0) {
|
|
146
|
-
score += matchedTags.length * 20
|
|
147
|
-
matches.push(...matchedTags.map(t => `tag:${t}`))
|
|
148
|
-
} else {
|
|
149
|
-
// No tag match means exclude this file
|
|
150
|
-
return { file, score: 0, matches: [] }
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Status filter
|
|
155
|
-
if (options.status) {
|
|
156
|
-
const fileStatus = FrontmatterUtils.getStatus(file.frontmatter)
|
|
157
|
-
if (fileStatus?.toLowerCase() === options.status.toLowerCase()) {
|
|
158
|
-
score += 15
|
|
159
|
-
matches.push(`status:${options.status}`)
|
|
160
|
-
} else {
|
|
161
|
-
return { file, score: 0, matches: [] }
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Project filter
|
|
166
|
-
if (options.project) {
|
|
167
|
-
const fileProject = FrontmatterUtils.getProject(file.frontmatter)
|
|
168
|
-
if (fileProject?.toLowerCase() === options.project.toLowerCase()) {
|
|
169
|
-
score += 25
|
|
170
|
-
matches.push(`project:${options.project}`)
|
|
171
|
-
} else {
|
|
172
|
-
return { file, score: 0, matches: [] }
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Type filter
|
|
177
|
-
if (options.type) {
|
|
178
|
-
const fileType = FrontmatterUtils.getType(file.frontmatter)
|
|
179
|
-
if (fileType?.toLowerCase() === options.type.toLowerCase()) {
|
|
180
|
-
score += 15
|
|
181
|
-
matches.push(`type:${options.type}`)
|
|
182
|
-
} else {
|
|
183
|
-
return { file, score: 0, matches: [] }
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Tech stack filter
|
|
188
|
-
if (options.techStack && options.techStack.length > 0) {
|
|
189
|
-
const fileTechStack = FrontmatterUtils.getTechStack(file.frontmatter)
|
|
190
|
-
const matchedTech = options.techStack.filter(tech =>
|
|
191
|
-
fileTechStack.some(ft => ft.toLowerCase() === tech.toLowerCase())
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
if (matchedTech.length > 0) {
|
|
195
|
-
score += matchedTech.length * 15
|
|
196
|
-
matches.push(...matchedTech.map(t => `tech:${t}`))
|
|
197
|
-
} else {
|
|
198
|
-
return { file, score: 0, matches: [] }
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Date range filter
|
|
203
|
-
if (options.dateRange) {
|
|
204
|
-
const dateField = options.dateRange.field || 'updated'
|
|
205
|
-
const fileDate = FrontmatterUtils.getDate(file.frontmatter, dateField)
|
|
206
|
-
|
|
207
|
-
if (fileDate) {
|
|
208
|
-
const { start, end } = options.dateRange
|
|
209
|
-
|
|
210
|
-
if (start && fileDate < start) {
|
|
211
|
-
return { file, score: 0, matches: [] }
|
|
212
|
-
}
|
|
213
|
-
if (end && fileDate > end) {
|
|
214
|
-
return { file, score: 0, matches: [] }
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
score += 5
|
|
218
|
-
} else if (options.dateRange.start || options.dateRange.end) {
|
|
219
|
-
// If date range is specified but file has no date, exclude it
|
|
220
|
-
return { file, score: 0, matches: [] }
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Boost score if no specific filters but file was matched
|
|
225
|
-
if (score === 0 && !options.text) {
|
|
226
|
-
// If no filters applied, give base score
|
|
227
|
-
score = 1
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return { file, score, matches }
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Score text match with term highlighting
|
|
235
|
-
*/
|
|
236
|
-
private scoreTextMatch(
|
|
237
|
-
file: MarkdownFile,
|
|
238
|
-
searchText: string
|
|
239
|
-
): { score: number; matches: string[] } {
|
|
240
|
-
const searchTerms = searchText.toLowerCase().split(/\s+/).filter(Boolean)
|
|
241
|
-
const content = file.content.toLowerCase()
|
|
242
|
-
const title = path.basename(file.path, '.md').toLowerCase()
|
|
243
|
-
const frontmatterText = JSON.stringify(file.frontmatter).toLowerCase()
|
|
244
|
-
|
|
245
|
-
let score = 0
|
|
246
|
-
const matches: string[] = []
|
|
247
|
-
|
|
248
|
-
for (const term of searchTerms) {
|
|
249
|
-
// Check title (highest weight)
|
|
250
|
-
if (title.includes(term)) {
|
|
251
|
-
score += 30
|
|
252
|
-
matches.push(`title:${term}`)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Check frontmatter (medium weight)
|
|
256
|
-
if (frontmatterText.includes(term)) {
|
|
257
|
-
score += 15
|
|
258
|
-
if (!matches.includes(`title:${term}`)) {
|
|
259
|
-
matches.push(`frontmatter:${term}`)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Check content (base weight)
|
|
264
|
-
if (content.includes(term)) {
|
|
265
|
-
// Count occurrences for scoring
|
|
266
|
-
const occurrences = (content.match(new RegExp(term, 'gi')) || []).length
|
|
267
|
-
score += Math.min(occurrences * 2, 20) // Cap at 20 points per term
|
|
268
|
-
if (!matches.some(m => m.includes(term))) {
|
|
269
|
-
matches.push(term)
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return { score, matches }
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Sort results based on options
|
|
279
|
-
*/
|
|
280
|
-
private sortResults(
|
|
281
|
-
results: QueryResult[],
|
|
282
|
-
options: QueryOptions
|
|
283
|
-
): QueryResult[] {
|
|
284
|
-
const sortBy = options.sortBy || 'score'
|
|
285
|
-
const direction = options.sortDirection || 'desc'
|
|
286
|
-
const multiplier = direction === 'desc' ? -1 : 1
|
|
287
|
-
|
|
288
|
-
return results.sort((a, b) => {
|
|
289
|
-
switch (sortBy) {
|
|
290
|
-
case 'score':
|
|
291
|
-
return (a.score - b.score) * multiplier
|
|
292
|
-
|
|
293
|
-
case 'date': {
|
|
294
|
-
const dateA = FrontmatterUtils.getDate(a.file.frontmatter)
|
|
295
|
-
const dateB = FrontmatterUtils.getDate(b.file.frontmatter)
|
|
296
|
-
if (!dateA && !dateB) return 0
|
|
297
|
-
if (!dateA) return 1
|
|
298
|
-
if (!dateB) return -1
|
|
299
|
-
return (dateA.getTime() - dateB.getTime()) * multiplier
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
case 'name': {
|
|
303
|
-
const nameA = path.basename(a.file.path)
|
|
304
|
-
const nameB = path.basename(b.file.path)
|
|
305
|
-
return nameA.localeCompare(nameB) * multiplier
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
default:
|
|
309
|
-
return (a.score - b.score) * multiplier
|
|
310
|
-
}
|
|
311
|
-
})
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Search by text only (simplified)
|
|
316
|
-
*/
|
|
317
|
-
async searchText(
|
|
318
|
-
vaultPath: string,
|
|
319
|
-
searchText: string,
|
|
320
|
-
limit: number = 10
|
|
321
|
-
): Promise<QueryResult[]> {
|
|
322
|
-
const { results } = await this.query(vaultPath, { text: searchText, limit })
|
|
323
|
-
return results
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Get files by tag
|
|
328
|
-
*/
|
|
329
|
-
async getByTag(vaultPath: string, tag: string): Promise<QueryResult[]> {
|
|
330
|
-
const { results } = await this.query(vaultPath, { tags: [tag] })
|
|
331
|
-
return results
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Get files by project
|
|
336
|
-
*/
|
|
337
|
-
async getByProject(
|
|
338
|
-
vaultPath: string,
|
|
339
|
-
project: string
|
|
340
|
-
): Promise<QueryResult[]> {
|
|
341
|
-
const { results } = await this.query(vaultPath, { project })
|
|
342
|
-
return results
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Get files by document type
|
|
347
|
-
*/
|
|
348
|
-
async getByType(vaultPath: string, type: string): Promise<QueryResult[]> {
|
|
349
|
-
const { results } = await this.query(vaultPath, { type })
|
|
350
|
-
return results
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Get recently updated files
|
|
355
|
-
*/
|
|
356
|
-
async getRecentlyUpdated(
|
|
357
|
-
vaultPath: string,
|
|
358
|
-
days: number = 7,
|
|
359
|
-
limit: number = 10
|
|
360
|
-
): Promise<QueryResult[]> {
|
|
361
|
-
const start = new Date()
|
|
362
|
-
start.setDate(start.getDate() - days)
|
|
363
|
-
|
|
364
|
-
const { results } = await this.query(vaultPath, {
|
|
365
|
-
dateRange: { start },
|
|
366
|
-
sortBy: 'date',
|
|
367
|
-
sortDirection: 'desc',
|
|
368
|
-
limit
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
return results
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Get all projects with their metadata
|
|
376
|
-
*/
|
|
377
|
-
async getAllProjects(vaultPath: string): Promise<
|
|
378
|
-
Array<{
|
|
379
|
-
name: string
|
|
380
|
-
status: string
|
|
381
|
-
contextFile: MarkdownFile | null
|
|
382
|
-
}>
|
|
383
|
-
> {
|
|
384
|
-
const projectsPath = path.join(vaultPath, 'Projects')
|
|
385
|
-
const projectDirs = await this.reader.getProjectDirectories(projectsPath)
|
|
386
|
-
|
|
387
|
-
const projects = await Promise.all(
|
|
388
|
-
projectDirs.map(async name => {
|
|
389
|
-
const contextPath = path.join(projectsPath, name, 'context.md')
|
|
390
|
-
let contextFile: MarkdownFile | null = null
|
|
391
|
-
|
|
392
|
-
try {
|
|
393
|
-
contextFile = await this.reader.readMarkdownFile(contextPath)
|
|
394
|
-
} catch {
|
|
395
|
-
// Context file might not exist
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
name,
|
|
400
|
-
status: contextFile
|
|
401
|
-
? FrontmatterUtils.getStatus(contextFile.frontmatter) || 'unknown'
|
|
402
|
-
: 'unknown',
|
|
403
|
-
contextFile
|
|
404
|
-
}
|
|
405
|
-
})
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
return projects
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Get project files
|
|
413
|
-
*/
|
|
414
|
-
async getProjectFiles(
|
|
415
|
-
vaultPath: string,
|
|
416
|
-
projectName: string
|
|
417
|
-
): Promise<MarkdownFile[]> {
|
|
418
|
-
const projectPath = path.join(vaultPath, 'Projects', projectName)
|
|
419
|
-
const files = await this.reader.readDirectory(projectPath, false)
|
|
420
|
-
return this.reader.readMarkdownFiles(files)
|
|
421
|
-
}
|
|
422
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Vault Query Engine
|
|
3
|
+
* Powerful search and query capabilities across the entire vault
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from 'path'
|
|
7
|
+
import type { Logger } from 'pino'
|
|
8
|
+
import type { MarkdownFile } from './types'
|
|
9
|
+
import { VaultReader } from './reader'
|
|
10
|
+
import { FrontmatterUtils } from './frontmatter'
|
|
11
|
+
|
|
12
|
+
export interface QueryOptions {
|
|
13
|
+
text?: string // Full-text search
|
|
14
|
+
tags?: string[] // Filter by tags
|
|
15
|
+
status?: string // Filter by status
|
|
16
|
+
project?: string // Filter by project
|
|
17
|
+
type?: string // Filter by document type
|
|
18
|
+
dateRange?: {
|
|
19
|
+
// Filter by date range
|
|
20
|
+
start?: Date
|
|
21
|
+
end?: Date
|
|
22
|
+
field?: string // Which date field to use (default: 'updated')
|
|
23
|
+
}
|
|
24
|
+
techStack?: string[] // Filter by tech stack
|
|
25
|
+
limit?: number // Max results
|
|
26
|
+
offset?: number // Skip first N results
|
|
27
|
+
sortBy?: 'score' | 'date' | 'name' // Sort order
|
|
28
|
+
sortDirection?: 'asc' | 'desc'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface QueryResult {
|
|
32
|
+
file: MarkdownFile
|
|
33
|
+
score: number // Relevance score
|
|
34
|
+
matches: string[] // Matched terms
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface QueryStats {
|
|
38
|
+
totalFiles: number
|
|
39
|
+
matchedFiles: number
|
|
40
|
+
executionTimeMs: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class VaultQuery {
|
|
44
|
+
private reader: VaultReader
|
|
45
|
+
private logger: Logger
|
|
46
|
+
|
|
47
|
+
constructor(reader: VaultReader, logger: Logger) {
|
|
48
|
+
this.reader = reader
|
|
49
|
+
this.logger = logger.child({ component: 'vault-query' })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Query vault with multiple filters
|
|
54
|
+
*/
|
|
55
|
+
async query(
|
|
56
|
+
vaultPath: string,
|
|
57
|
+
options: QueryOptions
|
|
58
|
+
): Promise<{ results: QueryResult[]; stats: QueryStats }> {
|
|
59
|
+
const startTime = Date.now()
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Get all markdown files
|
|
63
|
+
const projectsPath = path.join(vaultPath, 'Projects')
|
|
64
|
+
const globalPath = path.join(vaultPath, 'Global')
|
|
65
|
+
|
|
66
|
+
// Read from both Projects and Global directories
|
|
67
|
+
const projectFiles = await this.reader.readDirectory(projectsPath, true)
|
|
68
|
+
const globalFiles = await this.reader.readDirectory(globalPath, true)
|
|
69
|
+
const allFiles = [...projectFiles, ...globalFiles]
|
|
70
|
+
|
|
71
|
+
// Read all files
|
|
72
|
+
const markdownFiles = await Promise.all(
|
|
73
|
+
allFiles.map(f =>
|
|
74
|
+
this.reader.readMarkdownFile(f).catch(() => null)
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
// Filter out failed reads
|
|
79
|
+
const validFiles = markdownFiles.filter(
|
|
80
|
+
(f): f is MarkdownFile => f !== null
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// Filter and score
|
|
84
|
+
let results = validFiles
|
|
85
|
+
.map(file => this.scoreFile(file, options))
|
|
86
|
+
.filter(result => result.score > 0)
|
|
87
|
+
|
|
88
|
+
// Sort results
|
|
89
|
+
results = this.sortResults(results, options)
|
|
90
|
+
|
|
91
|
+
// Apply offset and limit
|
|
92
|
+
if (options.offset) {
|
|
93
|
+
results = results.slice(options.offset)
|
|
94
|
+
}
|
|
95
|
+
if (options.limit) {
|
|
96
|
+
results = results.slice(0, options.limit)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const stats: QueryStats = {
|
|
100
|
+
totalFiles: allFiles.length,
|
|
101
|
+
matchedFiles: results.length,
|
|
102
|
+
executionTimeMs: Date.now() - startTime
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.logger.debug(
|
|
106
|
+
{ options, stats },
|
|
107
|
+
'Query executed'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return { results, stats }
|
|
111
|
+
} catch (error) {
|
|
112
|
+
this.logger.error({ error, options }, 'Query failed')
|
|
113
|
+
return {
|
|
114
|
+
results: [],
|
|
115
|
+
stats: {
|
|
116
|
+
totalFiles: 0,
|
|
117
|
+
matchedFiles: 0,
|
|
118
|
+
executionTimeMs: Date.now() - startTime
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Score a file against query options
|
|
126
|
+
*/
|
|
127
|
+
private scoreFile(file: MarkdownFile, options: QueryOptions): QueryResult {
|
|
128
|
+
let score = 0
|
|
129
|
+
const matches: string[] = []
|
|
130
|
+
|
|
131
|
+
// Text search
|
|
132
|
+
if (options.text) {
|
|
133
|
+
const textScore = this.scoreTextMatch(file, options.text)
|
|
134
|
+
score += textScore.score
|
|
135
|
+
matches.push(...textScore.matches)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Tag filter
|
|
139
|
+
if (options.tags && options.tags.length > 0) {
|
|
140
|
+
const fileTags = FrontmatterUtils.getTags(file.frontmatter)
|
|
141
|
+
const matchedTags = options.tags.filter(tag =>
|
|
142
|
+
fileTags.some(ft => ft.toLowerCase() === tag.toLowerCase())
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if (matchedTags.length > 0) {
|
|
146
|
+
score += matchedTags.length * 20
|
|
147
|
+
matches.push(...matchedTags.map(t => `tag:${t}`))
|
|
148
|
+
} else {
|
|
149
|
+
// No tag match means exclude this file
|
|
150
|
+
return { file, score: 0, matches: [] }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Status filter
|
|
155
|
+
if (options.status) {
|
|
156
|
+
const fileStatus = FrontmatterUtils.getStatus(file.frontmatter)
|
|
157
|
+
if (fileStatus?.toLowerCase() === options.status.toLowerCase()) {
|
|
158
|
+
score += 15
|
|
159
|
+
matches.push(`status:${options.status}`)
|
|
160
|
+
} else {
|
|
161
|
+
return { file, score: 0, matches: [] }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Project filter
|
|
166
|
+
if (options.project) {
|
|
167
|
+
const fileProject = FrontmatterUtils.getProject(file.frontmatter)
|
|
168
|
+
if (fileProject?.toLowerCase() === options.project.toLowerCase()) {
|
|
169
|
+
score += 25
|
|
170
|
+
matches.push(`project:${options.project}`)
|
|
171
|
+
} else {
|
|
172
|
+
return { file, score: 0, matches: [] }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Type filter
|
|
177
|
+
if (options.type) {
|
|
178
|
+
const fileType = FrontmatterUtils.getType(file.frontmatter)
|
|
179
|
+
if (fileType?.toLowerCase() === options.type.toLowerCase()) {
|
|
180
|
+
score += 15
|
|
181
|
+
matches.push(`type:${options.type}`)
|
|
182
|
+
} else {
|
|
183
|
+
return { file, score: 0, matches: [] }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Tech stack filter
|
|
188
|
+
if (options.techStack && options.techStack.length > 0) {
|
|
189
|
+
const fileTechStack = FrontmatterUtils.getTechStack(file.frontmatter)
|
|
190
|
+
const matchedTech = options.techStack.filter(tech =>
|
|
191
|
+
fileTechStack.some(ft => ft.toLowerCase() === tech.toLowerCase())
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if (matchedTech.length > 0) {
|
|
195
|
+
score += matchedTech.length * 15
|
|
196
|
+
matches.push(...matchedTech.map(t => `tech:${t}`))
|
|
197
|
+
} else {
|
|
198
|
+
return { file, score: 0, matches: [] }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Date range filter
|
|
203
|
+
if (options.dateRange) {
|
|
204
|
+
const dateField = options.dateRange.field || 'updated'
|
|
205
|
+
const fileDate = FrontmatterUtils.getDate(file.frontmatter, dateField)
|
|
206
|
+
|
|
207
|
+
if (fileDate) {
|
|
208
|
+
const { start, end } = options.dateRange
|
|
209
|
+
|
|
210
|
+
if (start && fileDate < start) {
|
|
211
|
+
return { file, score: 0, matches: [] }
|
|
212
|
+
}
|
|
213
|
+
if (end && fileDate > end) {
|
|
214
|
+
return { file, score: 0, matches: [] }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
score += 5
|
|
218
|
+
} else if (options.dateRange.start || options.dateRange.end) {
|
|
219
|
+
// If date range is specified but file has no date, exclude it
|
|
220
|
+
return { file, score: 0, matches: [] }
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Boost score if no specific filters but file was matched
|
|
225
|
+
if (score === 0 && !options.text) {
|
|
226
|
+
// If no filters applied, give base score
|
|
227
|
+
score = 1
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { file, score, matches }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Score text match with term highlighting
|
|
235
|
+
*/
|
|
236
|
+
private scoreTextMatch(
|
|
237
|
+
file: MarkdownFile,
|
|
238
|
+
searchText: string
|
|
239
|
+
): { score: number; matches: string[] } {
|
|
240
|
+
const searchTerms = searchText.toLowerCase().split(/\s+/).filter(Boolean)
|
|
241
|
+
const content = file.content.toLowerCase()
|
|
242
|
+
const title = path.basename(file.path, '.md').toLowerCase()
|
|
243
|
+
const frontmatterText = JSON.stringify(file.frontmatter).toLowerCase()
|
|
244
|
+
|
|
245
|
+
let score = 0
|
|
246
|
+
const matches: string[] = []
|
|
247
|
+
|
|
248
|
+
for (const term of searchTerms) {
|
|
249
|
+
// Check title (highest weight)
|
|
250
|
+
if (title.includes(term)) {
|
|
251
|
+
score += 30
|
|
252
|
+
matches.push(`title:${term}`)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check frontmatter (medium weight)
|
|
256
|
+
if (frontmatterText.includes(term)) {
|
|
257
|
+
score += 15
|
|
258
|
+
if (!matches.includes(`title:${term}`)) {
|
|
259
|
+
matches.push(`frontmatter:${term}`)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check content (base weight)
|
|
264
|
+
if (content.includes(term)) {
|
|
265
|
+
// Count occurrences for scoring
|
|
266
|
+
const occurrences = (content.match(new RegExp(term, 'gi')) || []).length
|
|
267
|
+
score += Math.min(occurrences * 2, 20) // Cap at 20 points per term
|
|
268
|
+
if (!matches.some(m => m.includes(term))) {
|
|
269
|
+
matches.push(term)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return { score, matches }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Sort results based on options
|
|
279
|
+
*/
|
|
280
|
+
private sortResults(
|
|
281
|
+
results: QueryResult[],
|
|
282
|
+
options: QueryOptions
|
|
283
|
+
): QueryResult[] {
|
|
284
|
+
const sortBy = options.sortBy || 'score'
|
|
285
|
+
const direction = options.sortDirection || 'desc'
|
|
286
|
+
const multiplier = direction === 'desc' ? -1 : 1
|
|
287
|
+
|
|
288
|
+
return results.sort((a, b) => {
|
|
289
|
+
switch (sortBy) {
|
|
290
|
+
case 'score':
|
|
291
|
+
return (a.score - b.score) * multiplier
|
|
292
|
+
|
|
293
|
+
case 'date': {
|
|
294
|
+
const dateA = FrontmatterUtils.getDate(a.file.frontmatter)
|
|
295
|
+
const dateB = FrontmatterUtils.getDate(b.file.frontmatter)
|
|
296
|
+
if (!dateA && !dateB) return 0
|
|
297
|
+
if (!dateA) return 1
|
|
298
|
+
if (!dateB) return -1
|
|
299
|
+
return (dateA.getTime() - dateB.getTime()) * multiplier
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
case 'name': {
|
|
303
|
+
const nameA = path.basename(a.file.path)
|
|
304
|
+
const nameB = path.basename(b.file.path)
|
|
305
|
+
return nameA.localeCompare(nameB) * multiplier
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
default:
|
|
309
|
+
return (a.score - b.score) * multiplier
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Search by text only (simplified)
|
|
316
|
+
*/
|
|
317
|
+
async searchText(
|
|
318
|
+
vaultPath: string,
|
|
319
|
+
searchText: string,
|
|
320
|
+
limit: number = 10
|
|
321
|
+
): Promise<QueryResult[]> {
|
|
322
|
+
const { results } = await this.query(vaultPath, { text: searchText, limit })
|
|
323
|
+
return results
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get files by tag
|
|
328
|
+
*/
|
|
329
|
+
async getByTag(vaultPath: string, tag: string): Promise<QueryResult[]> {
|
|
330
|
+
const { results } = await this.query(vaultPath, { tags: [tag] })
|
|
331
|
+
return results
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get files by project
|
|
336
|
+
*/
|
|
337
|
+
async getByProject(
|
|
338
|
+
vaultPath: string,
|
|
339
|
+
project: string
|
|
340
|
+
): Promise<QueryResult[]> {
|
|
341
|
+
const { results } = await this.query(vaultPath, { project })
|
|
342
|
+
return results
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get files by document type
|
|
347
|
+
*/
|
|
348
|
+
async getByType(vaultPath: string, type: string): Promise<QueryResult[]> {
|
|
349
|
+
const { results } = await this.query(vaultPath, { type })
|
|
350
|
+
return results
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Get recently updated files
|
|
355
|
+
*/
|
|
356
|
+
async getRecentlyUpdated(
|
|
357
|
+
vaultPath: string,
|
|
358
|
+
days: number = 7,
|
|
359
|
+
limit: number = 10
|
|
360
|
+
): Promise<QueryResult[]> {
|
|
361
|
+
const start = new Date()
|
|
362
|
+
start.setDate(start.getDate() - days)
|
|
363
|
+
|
|
364
|
+
const { results } = await this.query(vaultPath, {
|
|
365
|
+
dateRange: { start },
|
|
366
|
+
sortBy: 'date',
|
|
367
|
+
sortDirection: 'desc',
|
|
368
|
+
limit
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
return results
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Get all projects with their metadata
|
|
376
|
+
*/
|
|
377
|
+
async getAllProjects(vaultPath: string): Promise<
|
|
378
|
+
Array<{
|
|
379
|
+
name: string
|
|
380
|
+
status: string
|
|
381
|
+
contextFile: MarkdownFile | null
|
|
382
|
+
}>
|
|
383
|
+
> {
|
|
384
|
+
const projectsPath = path.join(vaultPath, 'Projects')
|
|
385
|
+
const projectDirs = await this.reader.getProjectDirectories(projectsPath)
|
|
386
|
+
|
|
387
|
+
const projects = await Promise.all(
|
|
388
|
+
projectDirs.map(async name => {
|
|
389
|
+
const contextPath = path.join(projectsPath, name, 'context.md')
|
|
390
|
+
let contextFile: MarkdownFile | null = null
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
contextFile = await this.reader.readMarkdownFile(contextPath)
|
|
394
|
+
} catch {
|
|
395
|
+
// Context file might not exist
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
name,
|
|
400
|
+
status: contextFile
|
|
401
|
+
? FrontmatterUtils.getStatus(contextFile.frontmatter) || 'unknown'
|
|
402
|
+
: 'unknown',
|
|
403
|
+
contextFile
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
return projects
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Get project files
|
|
413
|
+
*/
|
|
414
|
+
async getProjectFiles(
|
|
415
|
+
vaultPath: string,
|
|
416
|
+
projectName: string
|
|
417
|
+
): Promise<MarkdownFile[]> {
|
|
418
|
+
const projectPath = path.join(vaultPath, 'Projects', projectName)
|
|
419
|
+
const files = await this.reader.readDirectory(projectPath, false)
|
|
420
|
+
return this.reader.readMarkdownFiles(files)
|
|
421
|
+
}
|
|
422
|
+
}
|