claude-brain 0.30.2 → 0.31.1
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 +59 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +195 -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,291 +1,291 @@
|
|
|
1
|
-
import { Database } from 'bun:sqlite'
|
|
2
|
-
import type { Logger } from 'pino'
|
|
3
|
-
import type { SymbolResult, FileResult, DependencyResult, FileMapEntry, IndexStats } from './types'
|
|
4
|
-
|
|
5
|
-
export class CodeQuery {
|
|
6
|
-
private db: Database
|
|
7
|
-
private logger: Logger
|
|
8
|
-
private memoryDb?: Database
|
|
9
|
-
|
|
10
|
-
constructor(db: Database, logger: Logger) {
|
|
11
|
-
this.db = db
|
|
12
|
-
this.logger = logger.child({ component: 'code-query' })
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Phase 29: Set memory DB for file-linked memory annotations */
|
|
16
|
-
setMemoryDb(db: Database): void {
|
|
17
|
-
this.memoryDb = db
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
findSymbols(query: string, project: string, limit: number = 20): SymbolResult[] {
|
|
21
|
-
if (!query.trim()) return []
|
|
22
|
-
|
|
23
|
-
// First try exact match
|
|
24
|
-
const exact = this.db.query(
|
|
25
|
-
`SELECT symbol_name, symbol_type, file_path, line_start, line_end, signature
|
|
26
|
-
FROM code_symbols
|
|
27
|
-
WHERE project = ? AND symbol_name = ?
|
|
28
|
-
LIMIT ?`
|
|
29
|
-
).all(project, query, limit) as RawSymbolRow[]
|
|
30
|
-
|
|
31
|
-
if (exact.length > 0) {
|
|
32
|
-
return exact.map(row => ({
|
|
33
|
-
symbol: row.symbol_name,
|
|
34
|
-
type: row.symbol_type as
|
|
35
|
-
filePath: row.file_path,
|
|
36
|
-
lineStart: row.line_start,
|
|
37
|
-
lineEnd: row.line_end ?? undefined,
|
|
38
|
-
signature: row.signature ?? undefined,
|
|
39
|
-
confidence: 1.0,
|
|
40
|
-
}))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Try prefix match
|
|
44
|
-
const prefix = this.db.query(
|
|
45
|
-
`SELECT symbol_name, symbol_type, file_path, line_start, line_end, signature
|
|
46
|
-
FROM code_symbols
|
|
47
|
-
WHERE project = ? AND symbol_name LIKE ? || '%'
|
|
48
|
-
LIMIT ?`
|
|
49
|
-
).all(project, query, limit) as RawSymbolRow[]
|
|
50
|
-
|
|
51
|
-
if (prefix.length > 0) {
|
|
52
|
-
return prefix.map(row => ({
|
|
53
|
-
symbol: row.symbol_name,
|
|
54
|
-
type: row.symbol_type as
|
|
55
|
-
filePath: row.file_path,
|
|
56
|
-
lineStart: row.line_start,
|
|
57
|
-
lineEnd: row.line_end ?? undefined,
|
|
58
|
-
signature: row.signature ?? undefined,
|
|
59
|
-
confidence: 0.9,
|
|
60
|
-
}))
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Fall back to FTS5 search
|
|
64
|
-
try {
|
|
65
|
-
const ftsResults = this.db.query(
|
|
66
|
-
`SELECT s.symbol_name, s.symbol_type, s.file_path, s.line_start, s.line_end, s.signature,
|
|
67
|
-
rank
|
|
68
|
-
FROM code_symbols_fts fts
|
|
69
|
-
JOIN code_symbols s ON s.id = fts.rowid
|
|
70
|
-
WHERE code_symbols_fts MATCH ? AND s.project = ?
|
|
71
|
-
ORDER BY rank
|
|
72
|
-
LIMIT ?`
|
|
73
|
-
).all(query, project, limit) as (RawSymbolRow & { rank: number })[]
|
|
74
|
-
|
|
75
|
-
if (ftsResults.length === 0) return []
|
|
76
|
-
|
|
77
|
-
// Normalize FTS5 rank to 0.5-0.85 confidence
|
|
78
|
-
const maxRank = Math.abs(ftsResults[0].rank) || 1
|
|
79
|
-
return ftsResults.map(row => ({
|
|
80
|
-
symbol: row.symbol_name,
|
|
81
|
-
type: row.symbol_type as
|
|
82
|
-
filePath: row.file_path,
|
|
83
|
-
lineStart: row.line_start,
|
|
84
|
-
lineEnd: row.line_end ?? undefined,
|
|
85
|
-
signature: row.signature ?? undefined,
|
|
86
|
-
confidence: 0.5 + 0.35 * (Math.abs(row.rank) / maxRank),
|
|
87
|
-
}))
|
|
88
|
-
} catch (error) {
|
|
89
|
-
// FTS5 can throw on invalid query syntax
|
|
90
|
-
this.logger.debug({ error, query }, 'FTS5 query failed, falling back to LIKE')
|
|
91
|
-
|
|
92
|
-
const likeResults = this.db.query(
|
|
93
|
-
`SELECT symbol_name, symbol_type, file_path, line_start, line_end, signature
|
|
94
|
-
FROM code_symbols
|
|
95
|
-
WHERE project = ? AND symbol_name LIKE '%' || ? || '%'
|
|
96
|
-
LIMIT ?`
|
|
97
|
-
).all(project, query, limit) as RawSymbolRow[]
|
|
98
|
-
|
|
99
|
-
return likeResults.map(row => ({
|
|
100
|
-
symbol: row.symbol_name,
|
|
101
|
-
type: row.symbol_type as
|
|
102
|
-
filePath: row.file_path,
|
|
103
|
-
lineStart: row.line_start,
|
|
104
|
-
lineEnd: row.line_end ?? undefined,
|
|
105
|
-
signature: row.signature ?? undefined,
|
|
106
|
-
confidence: 0.5,
|
|
107
|
-
}))
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
findFiles(query: string, project: string): FileResult[] {
|
|
112
|
-
if (!query.trim()) return []
|
|
113
|
-
|
|
114
|
-
const rows = this.db.query(
|
|
115
|
-
`SELECT file_path, language, summary, symbol_count, line_count
|
|
116
|
-
FROM code_files
|
|
117
|
-
WHERE project = ? AND (file_path LIKE '%' || ? || '%' OR summary LIKE '%' || ? || '%')
|
|
118
|
-
ORDER BY file_path
|
|
119
|
-
LIMIT 50`
|
|
120
|
-
).all(project, query, query) as RawFileRow[]
|
|
121
|
-
|
|
122
|
-
return rows.map(row => ({
|
|
123
|
-
filePath: row.file_path,
|
|
124
|
-
language: row.language ?? undefined,
|
|
125
|
-
summary: row.summary ?? undefined,
|
|
126
|
-
symbolCount: row.symbol_count,
|
|
127
|
-
lineCount: row.line_count,
|
|
128
|
-
}))
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
getDependencies(filePath: string, project: string): DependencyResult {
|
|
132
|
-
const imports = this.db.query(
|
|
133
|
-
`SELECT target_file, import_names
|
|
134
|
-
FROM code_dependencies
|
|
135
|
-
WHERE project = ? AND source_file = ?`
|
|
136
|
-
).all(project, filePath) as { target_file: string; import_names: string | null }[]
|
|
137
|
-
|
|
138
|
-
const importedBy = this.db.query(
|
|
139
|
-
`SELECT source_file, import_names
|
|
140
|
-
FROM code_dependencies
|
|
141
|
-
WHERE project = ? AND target_file = ?`
|
|
142
|
-
).all(project, filePath) as { source_file: string; import_names: string | null }[]
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
file: filePath,
|
|
146
|
-
imports: imports.map(row => ({
|
|
147
|
-
file: row.target_file,
|
|
148
|
-
names: row.import_names ? row.import_names.split(', ').filter(Boolean) : [],
|
|
149
|
-
})),
|
|
150
|
-
importedBy: importedBy.map(row => ({
|
|
151
|
-
file: row.source_file,
|
|
152
|
-
names: row.import_names ? row.import_names.split(', ').filter(Boolean) : [],
|
|
153
|
-
})),
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
getDependents(filePath: string, project: string): DependencyResult {
|
|
158
|
-
// getDependents is the inverse view — same data, different perspective
|
|
159
|
-
return this.getDependencies(filePath, project)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
getFileMap(project: string, maxEntries: number = 100): FileMapEntry[] {
|
|
163
|
-
const rows = this.db.query(
|
|
164
|
-
`SELECT f.file_path, f.language, f.summary,
|
|
165
|
-
GROUP_CONCAT(CASE WHEN s.exported = 1 THEN s.symbol_name END) as symbols
|
|
166
|
-
FROM code_files f
|
|
167
|
-
LEFT JOIN code_symbols s ON s.project = f.project AND s.file_path = f.file_path
|
|
168
|
-
WHERE f.project = ?
|
|
169
|
-
GROUP BY f.file_path
|
|
170
|
-
ORDER BY f.file_path
|
|
171
|
-
LIMIT ?`
|
|
172
|
-
).all(project, maxEntries) as {
|
|
173
|
-
file_path: string
|
|
174
|
-
language: string | null
|
|
175
|
-
summary: string | null
|
|
176
|
-
symbols: string | null
|
|
177
|
-
}[]
|
|
178
|
-
|
|
179
|
-
// Phase 29: Count linked memories per file from observations table
|
|
180
|
-
const memoryCounts = this.getMemoryCounts(project)
|
|
181
|
-
|
|
182
|
-
return rows.map(row => ({
|
|
183
|
-
path: row.file_path,
|
|
184
|
-
language: row.language ?? undefined,
|
|
185
|
-
summary: row.summary ?? undefined,
|
|
186
|
-
symbols: row.symbols ? [...new Set(row.symbols.split(',').filter(Boolean))] : [],
|
|
187
|
-
linkedMemories: memoryCounts.get(row.file_path) ?? undefined,
|
|
188
|
-
}))
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** Phase 29: Query observations table for file path reference counts */
|
|
192
|
-
private getMemoryCounts(project: string): Map<string, number> {
|
|
193
|
-
const counts = new Map<string, number>()
|
|
194
|
-
if (!this.memoryDb) return counts
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
const rows = this.memoryDb.prepare(
|
|
198
|
-
`SELECT file_paths FROM observations
|
|
199
|
-
WHERE project = ? AND file_paths IS NOT NULL AND archived = 0`
|
|
200
|
-
).all(project) as { file_paths: string }[]
|
|
201
|
-
|
|
202
|
-
for (const row of rows) {
|
|
203
|
-
try {
|
|
204
|
-
const paths: string[] = JSON.parse(row.file_paths)
|
|
205
|
-
if (Array.isArray(paths)) {
|
|
206
|
-
for (const fp of paths) {
|
|
207
|
-
counts.set(fp, (counts.get(fp) || 0) + 1)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
} catch {
|
|
211
|
-
// Skip malformed JSON
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
} catch (error) {
|
|
215
|
-
this.logger.debug({ error }, 'Failed to query memory counts from observations')
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return counts
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
formatFileMap(entries: FileMapEntry[]): string {
|
|
222
|
-
const lines: string[] = []
|
|
223
|
-
let totalSize = 0
|
|
224
|
-
const MAX_SIZE = 4096 // 4KB budget
|
|
225
|
-
|
|
226
|
-
for (const entry of entries) {
|
|
227
|
-
let line = entry.path
|
|
228
|
-
if (entry.symbols.length > 0) {
|
|
229
|
-
const symbolStr = entry.symbols.length <= 5
|
|
230
|
-
? entry.symbols.join(', ')
|
|
231
|
-
: `${entry.symbols.slice(0, 5).join(', ')} +${entry.symbols.length - 5}`
|
|
232
|
-
line += ` — ${symbolStr}`
|
|
233
|
-
}
|
|
234
|
-
if (entry.summary) {
|
|
235
|
-
line += ` — ${entry.summary}`
|
|
236
|
-
}
|
|
237
|
-
// Phase 29: Append linked memory count annotation
|
|
238
|
-
if (entry.linkedMemories && entry.linkedMemories > 0) {
|
|
239
|
-
line += ` [${entry.linkedMemories} ${entry.linkedMemories === 1 ? 'memory' : 'memories'}]`
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Check if adding this line would exceed budget
|
|
243
|
-
if (totalSize + line.length + 1 > MAX_SIZE) break
|
|
244
|
-
totalSize += line.length + 1
|
|
245
|
-
lines.push(line)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return lines.join('\n')
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
getStats(project: string): IndexStats {
|
|
252
|
-
const fileRow = this.db.query(
|
|
253
|
-
'SELECT COUNT(*) as cnt FROM code_files WHERE project = ?'
|
|
254
|
-
).get(project) as { cnt: number } | null
|
|
255
|
-
|
|
256
|
-
const symbolRow = this.db.query(
|
|
257
|
-
'SELECT COUNT(*) as cnt FROM code_symbols WHERE project = ?'
|
|
258
|
-
).get(project) as { cnt: number } | null
|
|
259
|
-
|
|
260
|
-
const lastRow = this.db.query(
|
|
261
|
-
'SELECT MAX(last_indexed) as last_indexed FROM code_files WHERE project = ?'
|
|
262
|
-
).get(project) as { last_indexed: string | null } | null
|
|
263
|
-
|
|
264
|
-
return {
|
|
265
|
-
project,
|
|
266
|
-
filesIndexed: fileRow?.cnt ?? 0,
|
|
267
|
-
totalSymbols: symbolRow?.cnt ?? 0,
|
|
268
|
-
lastIndexed: lastRow?.last_indexed ?? null,
|
|
269
|
-
staleFiles: 0,
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// ─── Internal Row Types ─────────────────────────────────────
|
|
275
|
-
|
|
276
|
-
interface RawSymbolRow {
|
|
277
|
-
symbol_name: string
|
|
278
|
-
symbol_type: string
|
|
279
|
-
file_path: string
|
|
280
|
-
line_start: number
|
|
281
|
-
line_end: number | null
|
|
282
|
-
signature: string | null
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
interface RawFileRow {
|
|
286
|
-
file_path: string
|
|
287
|
-
language: string | null
|
|
288
|
-
summary: string | null
|
|
289
|
-
symbol_count: number
|
|
290
|
-
line_count: number
|
|
291
|
-
}
|
|
1
|
+
import { Database } from 'bun:sqlite'
|
|
2
|
+
import type { Logger } from 'pino'
|
|
3
|
+
import type { SymbolResult, FileResult, DependencyResult, FileMapEntry, IndexStats, SymbolType } from './types'
|
|
4
|
+
|
|
5
|
+
export class CodeQuery {
|
|
6
|
+
private db: Database
|
|
7
|
+
private logger: Logger
|
|
8
|
+
private memoryDb?: Database
|
|
9
|
+
|
|
10
|
+
constructor(db: Database, logger: Logger) {
|
|
11
|
+
this.db = db
|
|
12
|
+
this.logger = logger.child({ component: 'code-query' })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Phase 29: Set memory DB for file-linked memory annotations */
|
|
16
|
+
setMemoryDb(db: Database): void {
|
|
17
|
+
this.memoryDb = db
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
findSymbols(query: string, project: string, limit: number = 20): SymbolResult[] {
|
|
21
|
+
if (!query.trim()) return []
|
|
22
|
+
|
|
23
|
+
// First try exact match
|
|
24
|
+
const exact = this.db.query(
|
|
25
|
+
`SELECT symbol_name, symbol_type, file_path, line_start, line_end, signature
|
|
26
|
+
FROM code_symbols
|
|
27
|
+
WHERE project = ? AND symbol_name = ?
|
|
28
|
+
LIMIT ?`
|
|
29
|
+
).all(project, query, limit) as RawSymbolRow[]
|
|
30
|
+
|
|
31
|
+
if (exact.length > 0) {
|
|
32
|
+
return exact.map(row => ({
|
|
33
|
+
symbol: row.symbol_name,
|
|
34
|
+
type: row.symbol_type as SymbolType,
|
|
35
|
+
filePath: row.file_path,
|
|
36
|
+
lineStart: row.line_start,
|
|
37
|
+
lineEnd: row.line_end ?? undefined,
|
|
38
|
+
signature: row.signature ?? undefined,
|
|
39
|
+
confidence: 1.0,
|
|
40
|
+
}))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Try prefix match
|
|
44
|
+
const prefix = this.db.query(
|
|
45
|
+
`SELECT symbol_name, symbol_type, file_path, line_start, line_end, signature
|
|
46
|
+
FROM code_symbols
|
|
47
|
+
WHERE project = ? AND symbol_name LIKE ? || '%'
|
|
48
|
+
LIMIT ?`
|
|
49
|
+
).all(project, query, limit) as RawSymbolRow[]
|
|
50
|
+
|
|
51
|
+
if (prefix.length > 0) {
|
|
52
|
+
return prefix.map(row => ({
|
|
53
|
+
symbol: row.symbol_name,
|
|
54
|
+
type: row.symbol_type as SymbolType,
|
|
55
|
+
filePath: row.file_path,
|
|
56
|
+
lineStart: row.line_start,
|
|
57
|
+
lineEnd: row.line_end ?? undefined,
|
|
58
|
+
signature: row.signature ?? undefined,
|
|
59
|
+
confidence: 0.9,
|
|
60
|
+
}))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Fall back to FTS5 search
|
|
64
|
+
try {
|
|
65
|
+
const ftsResults = this.db.query(
|
|
66
|
+
`SELECT s.symbol_name, s.symbol_type, s.file_path, s.line_start, s.line_end, s.signature,
|
|
67
|
+
rank
|
|
68
|
+
FROM code_symbols_fts fts
|
|
69
|
+
JOIN code_symbols s ON s.id = fts.rowid
|
|
70
|
+
WHERE code_symbols_fts MATCH ? AND s.project = ?
|
|
71
|
+
ORDER BY rank
|
|
72
|
+
LIMIT ?`
|
|
73
|
+
).all(query, project, limit) as (RawSymbolRow & { rank: number })[]
|
|
74
|
+
|
|
75
|
+
if (ftsResults.length === 0) return []
|
|
76
|
+
|
|
77
|
+
// Normalize FTS5 rank to 0.5-0.85 confidence
|
|
78
|
+
const maxRank = Math.abs(ftsResults[0].rank) || 1
|
|
79
|
+
return ftsResults.map(row => ({
|
|
80
|
+
symbol: row.symbol_name,
|
|
81
|
+
type: row.symbol_type as SymbolType,
|
|
82
|
+
filePath: row.file_path,
|
|
83
|
+
lineStart: row.line_start,
|
|
84
|
+
lineEnd: row.line_end ?? undefined,
|
|
85
|
+
signature: row.signature ?? undefined,
|
|
86
|
+
confidence: 0.5 + 0.35 * (Math.abs(row.rank) / maxRank),
|
|
87
|
+
}))
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// FTS5 can throw on invalid query syntax
|
|
90
|
+
this.logger.debug({ error, query }, 'FTS5 query failed, falling back to LIKE')
|
|
91
|
+
|
|
92
|
+
const likeResults = this.db.query(
|
|
93
|
+
`SELECT symbol_name, symbol_type, file_path, line_start, line_end, signature
|
|
94
|
+
FROM code_symbols
|
|
95
|
+
WHERE project = ? AND symbol_name LIKE '%' || ? || '%'
|
|
96
|
+
LIMIT ?`
|
|
97
|
+
).all(project, query, limit) as RawSymbolRow[]
|
|
98
|
+
|
|
99
|
+
return likeResults.map(row => ({
|
|
100
|
+
symbol: row.symbol_name,
|
|
101
|
+
type: row.symbol_type as SymbolType,
|
|
102
|
+
filePath: row.file_path,
|
|
103
|
+
lineStart: row.line_start,
|
|
104
|
+
lineEnd: row.line_end ?? undefined,
|
|
105
|
+
signature: row.signature ?? undefined,
|
|
106
|
+
confidence: 0.5,
|
|
107
|
+
}))
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
findFiles(query: string, project: string): FileResult[] {
|
|
112
|
+
if (!query.trim()) return []
|
|
113
|
+
|
|
114
|
+
const rows = this.db.query(
|
|
115
|
+
`SELECT file_path, language, summary, symbol_count, line_count
|
|
116
|
+
FROM code_files
|
|
117
|
+
WHERE project = ? AND (file_path LIKE '%' || ? || '%' OR summary LIKE '%' || ? || '%')
|
|
118
|
+
ORDER BY file_path
|
|
119
|
+
LIMIT 50`
|
|
120
|
+
).all(project, query, query) as RawFileRow[]
|
|
121
|
+
|
|
122
|
+
return rows.map(row => ({
|
|
123
|
+
filePath: row.file_path,
|
|
124
|
+
language: row.language ?? undefined,
|
|
125
|
+
summary: row.summary ?? undefined,
|
|
126
|
+
symbolCount: row.symbol_count,
|
|
127
|
+
lineCount: row.line_count,
|
|
128
|
+
}))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getDependencies(filePath: string, project: string): DependencyResult {
|
|
132
|
+
const imports = this.db.query(
|
|
133
|
+
`SELECT target_file, import_names
|
|
134
|
+
FROM code_dependencies
|
|
135
|
+
WHERE project = ? AND source_file = ?`
|
|
136
|
+
).all(project, filePath) as { target_file: string; import_names: string | null }[]
|
|
137
|
+
|
|
138
|
+
const importedBy = this.db.query(
|
|
139
|
+
`SELECT source_file, import_names
|
|
140
|
+
FROM code_dependencies
|
|
141
|
+
WHERE project = ? AND target_file = ?`
|
|
142
|
+
).all(project, filePath) as { source_file: string; import_names: string | null }[]
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
file: filePath,
|
|
146
|
+
imports: imports.map(row => ({
|
|
147
|
+
file: row.target_file,
|
|
148
|
+
names: row.import_names ? row.import_names.split(', ').filter(Boolean) : [],
|
|
149
|
+
})),
|
|
150
|
+
importedBy: importedBy.map(row => ({
|
|
151
|
+
file: row.source_file,
|
|
152
|
+
names: row.import_names ? row.import_names.split(', ').filter(Boolean) : [],
|
|
153
|
+
})),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getDependents(filePath: string, project: string): DependencyResult {
|
|
158
|
+
// getDependents is the inverse view — same data, different perspective
|
|
159
|
+
return this.getDependencies(filePath, project)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getFileMap(project: string, maxEntries: number = 100): FileMapEntry[] {
|
|
163
|
+
const rows = this.db.query(
|
|
164
|
+
`SELECT f.file_path, f.language, f.summary,
|
|
165
|
+
GROUP_CONCAT(CASE WHEN s.exported = 1 THEN s.symbol_name END) as symbols
|
|
166
|
+
FROM code_files f
|
|
167
|
+
LEFT JOIN code_symbols s ON s.project = f.project AND s.file_path = f.file_path
|
|
168
|
+
WHERE f.project = ?
|
|
169
|
+
GROUP BY f.file_path
|
|
170
|
+
ORDER BY f.file_path
|
|
171
|
+
LIMIT ?`
|
|
172
|
+
).all(project, maxEntries) as {
|
|
173
|
+
file_path: string
|
|
174
|
+
language: string | null
|
|
175
|
+
summary: string | null
|
|
176
|
+
symbols: string | null
|
|
177
|
+
}[]
|
|
178
|
+
|
|
179
|
+
// Phase 29: Count linked memories per file from observations table
|
|
180
|
+
const memoryCounts = this.getMemoryCounts(project)
|
|
181
|
+
|
|
182
|
+
return rows.map(row => ({
|
|
183
|
+
path: row.file_path,
|
|
184
|
+
language: row.language ?? undefined,
|
|
185
|
+
summary: row.summary ?? undefined,
|
|
186
|
+
symbols: row.symbols ? [...new Set(row.symbols.split(',').filter(Boolean))] : [],
|
|
187
|
+
linkedMemories: memoryCounts.get(row.file_path) ?? undefined,
|
|
188
|
+
}))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Phase 29: Query observations table for file path reference counts */
|
|
192
|
+
private getMemoryCounts(project: string): Map<string, number> {
|
|
193
|
+
const counts = new Map<string, number>()
|
|
194
|
+
if (!this.memoryDb) return counts
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const rows = this.memoryDb.prepare(
|
|
198
|
+
`SELECT file_paths FROM observations
|
|
199
|
+
WHERE project = ? AND file_paths IS NOT NULL AND archived = 0`
|
|
200
|
+
).all(project) as { file_paths: string }[]
|
|
201
|
+
|
|
202
|
+
for (const row of rows) {
|
|
203
|
+
try {
|
|
204
|
+
const paths: string[] = JSON.parse(row.file_paths)
|
|
205
|
+
if (Array.isArray(paths)) {
|
|
206
|
+
for (const fp of paths) {
|
|
207
|
+
counts.set(fp, (counts.get(fp) || 0) + 1)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
// Skip malformed JSON
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
this.logger.debug({ error }, 'Failed to query memory counts from observations')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return counts
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
formatFileMap(entries: FileMapEntry[]): string {
|
|
222
|
+
const lines: string[] = []
|
|
223
|
+
let totalSize = 0
|
|
224
|
+
const MAX_SIZE = 4096 // 4KB budget
|
|
225
|
+
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
let line = entry.path
|
|
228
|
+
if (entry.symbols.length > 0) {
|
|
229
|
+
const symbolStr = entry.symbols.length <= 5
|
|
230
|
+
? entry.symbols.join(', ')
|
|
231
|
+
: `${entry.symbols.slice(0, 5).join(', ')} +${entry.symbols.length - 5}`
|
|
232
|
+
line += ` — ${symbolStr}`
|
|
233
|
+
}
|
|
234
|
+
if (entry.summary) {
|
|
235
|
+
line += ` — ${entry.summary}`
|
|
236
|
+
}
|
|
237
|
+
// Phase 29: Append linked memory count annotation
|
|
238
|
+
if (entry.linkedMemories && entry.linkedMemories > 0) {
|
|
239
|
+
line += ` [${entry.linkedMemories} ${entry.linkedMemories === 1 ? 'memory' : 'memories'}]`
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check if adding this line would exceed budget
|
|
243
|
+
if (totalSize + line.length + 1 > MAX_SIZE) break
|
|
244
|
+
totalSize += line.length + 1
|
|
245
|
+
lines.push(line)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return lines.join('\n')
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
getStats(project: string): IndexStats {
|
|
252
|
+
const fileRow = this.db.query(
|
|
253
|
+
'SELECT COUNT(*) as cnt FROM code_files WHERE project = ?'
|
|
254
|
+
).get(project) as { cnt: number } | null
|
|
255
|
+
|
|
256
|
+
const symbolRow = this.db.query(
|
|
257
|
+
'SELECT COUNT(*) as cnt FROM code_symbols WHERE project = ?'
|
|
258
|
+
).get(project) as { cnt: number } | null
|
|
259
|
+
|
|
260
|
+
const lastRow = this.db.query(
|
|
261
|
+
'SELECT MAX(last_indexed) as last_indexed FROM code_files WHERE project = ?'
|
|
262
|
+
).get(project) as { last_indexed: string | null } | null
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
project,
|
|
266
|
+
filesIndexed: fileRow?.cnt ?? 0,
|
|
267
|
+
totalSymbols: symbolRow?.cnt ?? 0,
|
|
268
|
+
lastIndexed: lastRow?.last_indexed ?? null,
|
|
269
|
+
staleFiles: 0,
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── Internal Row Types ─────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
interface RawSymbolRow {
|
|
277
|
+
symbol_name: string
|
|
278
|
+
symbol_type: string
|
|
279
|
+
file_path: string
|
|
280
|
+
line_start: number
|
|
281
|
+
line_end: number | null
|
|
282
|
+
signature: string | null
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
interface RawFileRow {
|
|
286
|
+
file_path: string
|
|
287
|
+
language: string | null
|
|
288
|
+
summary: string | null
|
|
289
|
+
symbol_count: number
|
|
290
|
+
line_count: number
|
|
291
|
+
}
|