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,352 +1,352 @@
|
|
|
1
|
-
import { Database } from 'bun:sqlite'
|
|
2
|
-
import { readdir, stat } from 'node:fs/promises'
|
|
3
|
-
import { join, relative, extname } from 'node:path'
|
|
4
|
-
import { createHash } from 'node:crypto'
|
|
5
|
-
import type { Logger } from 'pino'
|
|
6
|
-
import type { CodeParser } from './parser'
|
|
7
|
-
import type { IndexResult, IndexStats, ParsedFile } from './types'
|
|
8
|
-
import { createCodeSchema } from './schema'
|
|
9
|
-
|
|
10
|
-
const SKIP_DIRS = new Set([
|
|
11
|
-
'node_modules', '.git', 'dist', 'build', '__pycache__',
|
|
12
|
-
'.venv', 'target', '.cache', 'coverage', '.turbo', '.output', 'vendor',
|
|
13
|
-
])
|
|
14
|
-
|
|
15
|
-
const SKIP_FILES = new Set([
|
|
16
|
-
'package-lock.json', 'yarn.lock', 'bun.lockb', 'pnpm-lock.yaml', '.DS_Store',
|
|
17
|
-
])
|
|
18
|
-
|
|
19
|
-
const MAX_FILE_SIZE = 500 * 1024 // 500KB
|
|
20
|
-
|
|
21
|
-
const SUPPORTED_EXTENSIONS = new Set([
|
|
22
|
-
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
23
|
-
'.py', '.go', '.rs', '.vue',
|
|
24
|
-
'.html', '.css', '.json', '.yaml', '.yml',
|
|
25
|
-
])
|
|
26
|
-
|
|
27
|
-
const BATCH_SIZE = 100
|
|
28
|
-
|
|
29
|
-
export class CodeIndexer {
|
|
30
|
-
private db: Database
|
|
31
|
-
private parser: CodeParser
|
|
32
|
-
private logger: Logger
|
|
33
|
-
|
|
34
|
-
constructor(db: Database, parser: CodeParser, logger: Logger) {
|
|
35
|
-
this.db = db
|
|
36
|
-
this.parser = parser
|
|
37
|
-
this.logger = logger.child({ component: 'code-indexer' })
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async initialize(): Promise<void> {
|
|
41
|
-
createCodeSchema(this.db)
|
|
42
|
-
this.logger.info('Code intelligence schema initialized')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async indexProject(projectPath: string, projectName: string): Promise<IndexResult> {
|
|
46
|
-
const startTime = Date.now()
|
|
47
|
-
const errors: string[] = []
|
|
48
|
-
let filesIndexed = 0
|
|
49
|
-
let filesSkipped = 0
|
|
50
|
-
let symbolsFound = 0
|
|
51
|
-
|
|
52
|
-
this.logger.info({ projectPath, projectName }, 'Starting project index')
|
|
53
|
-
|
|
54
|
-
// Collect all eligible files
|
|
55
|
-
const files = await this.collectFiles(projectPath)
|
|
56
|
-
this.logger.info({ fileCount: files.length }, 'Files collected for indexing')
|
|
57
|
-
|
|
58
|
-
// Process in batches
|
|
59
|
-
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
60
|
-
const batch = files.slice(i, i + BATCH_SIZE)
|
|
61
|
-
this.db.run('BEGIN TRANSACTION')
|
|
62
|
-
try {
|
|
63
|
-
for (const filePath of batch) {
|
|
64
|
-
try {
|
|
65
|
-
const result = await this.indexSingleFile(filePath, projectPath, projectName)
|
|
66
|
-
if (result.skipped) {
|
|
67
|
-
filesSkipped++
|
|
68
|
-
} else {
|
|
69
|
-
filesIndexed++
|
|
70
|
-
symbolsFound += result.symbolCount
|
|
71
|
-
}
|
|
72
|
-
} catch (error) {
|
|
73
|
-
const msg = `Failed to index ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
74
|
-
errors.push(msg)
|
|
75
|
-
this.logger.warn({ filePath, error }, 'Failed to index file')
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
this.db.run('COMMIT')
|
|
79
|
-
} catch (error) {
|
|
80
|
-
this.db.run('ROLLBACK')
|
|
81
|
-
const msg = `Batch failed at offset ${i}: ${error instanceof Error ? error.message : String(error)}`
|
|
82
|
-
errors.push(msg)
|
|
83
|
-
this.logger.error({ error, batchOffset: i }, 'Batch transaction failed')
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const durationMs = Date.now() - startTime
|
|
88
|
-
this.logger.info(
|
|
89
|
-
{ filesIndexed, filesSkipped, symbolsFound, durationMs, errors: errors.length },
|
|
90
|
-
'Project indexing complete'
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
project: projectName,
|
|
95
|
-
filesIndexed,
|
|
96
|
-
filesSkipped,
|
|
97
|
-
symbolsFound,
|
|
98
|
-
durationMs,
|
|
99
|
-
errors,
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async indexFile(filePath: string, projectName: string): Promise<void> {
|
|
104
|
-
this.db.run('BEGIN TRANSACTION')
|
|
105
|
-
try {
|
|
106
|
-
// Clear existing data for this file
|
|
107
|
-
this.db.run('DELETE FROM code_symbols WHERE project = ? AND file_path = ?', [projectName, filePath])
|
|
108
|
-
this.db.run('DELETE FROM code_dependencies WHERE project = ? AND source_file = ?', [projectName, filePath])
|
|
109
|
-
this.db.run('DELETE FROM code_files WHERE project = ? AND file_path = ?', [projectName, filePath])
|
|
110
|
-
|
|
111
|
-
const content = await Bun.file(filePath).text()
|
|
112
|
-
const hash = createHash('sha256').update(content).digest('hex')
|
|
113
|
-
const parsed = await this.parser.parseFile(filePath, content)
|
|
114
|
-
|
|
115
|
-
this.storeFile(parsed, projectName, hash)
|
|
116
|
-
this.storeSymbols(parsed, projectName)
|
|
117
|
-
this.storeDependencies(parsed, projectName)
|
|
118
|
-
|
|
119
|
-
this.db.run('COMMIT')
|
|
120
|
-
this.logger.debug({ filePath, symbols: parsed.symbols.length }, 'File re-indexed')
|
|
121
|
-
} catch (error) {
|
|
122
|
-
this.db.run('ROLLBACK')
|
|
123
|
-
throw error
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async clearProject(projectName: string): Promise<void> {
|
|
128
|
-
this.db.run('BEGIN TRANSACTION')
|
|
129
|
-
try {
|
|
130
|
-
this.db.run('DELETE FROM code_symbols WHERE project = ?', [projectName])
|
|
131
|
-
this.db.run('DELETE FROM code_dependencies WHERE project = ?', [projectName])
|
|
132
|
-
this.db.run('DELETE FROM code_files WHERE project = ?', [projectName])
|
|
133
|
-
this.db.run('COMMIT')
|
|
134
|
-
this.logger.info({ projectName }, 'Project data cleared')
|
|
135
|
-
} catch (error) {
|
|
136
|
-
this.db.run('ROLLBACK')
|
|
137
|
-
throw error
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
getStats(projectName: string): IndexStats {
|
|
142
|
-
const fileRow = this.db.query(
|
|
143
|
-
'SELECT COUNT(*) as cnt FROM code_files WHERE project = ?'
|
|
144
|
-
).get(projectName) as { cnt: number } | null
|
|
145
|
-
|
|
146
|
-
const symbolRow = this.db.query(
|
|
147
|
-
'SELECT COUNT(*) as cnt FROM code_symbols WHERE project = ?'
|
|
148
|
-
).get(projectName) as { cnt: number } | null
|
|
149
|
-
|
|
150
|
-
const lastRow = this.db.query(
|
|
151
|
-
'SELECT MAX(last_indexed) as last_indexed FROM code_files WHERE project = ?'
|
|
152
|
-
).get(projectName) as { last_indexed: string | null } | null
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
project: projectName,
|
|
156
|
-
filesIndexed: fileRow?.cnt ?? 0,
|
|
157
|
-
totalSymbols: symbolRow?.cnt ?? 0,
|
|
158
|
-
lastIndexed: lastRow?.last_indexed ?? null,
|
|
159
|
-
staleFiles: 0, // Could be computed by comparing file hashes
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
generateFileSummary(parsed: ParsedFile): string {
|
|
164
|
-
const exported = parsed.symbols.filter(s => s.exported)
|
|
165
|
-
if (exported.length === 0) {
|
|
166
|
-
return `${parsed.language} file, ${parsed.lineCount} lines`
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const grouped = new Map<string, string[]>()
|
|
170
|
-
for (const sym of exported) {
|
|
171
|
-
const existing = grouped.get(sym.type) ?? []
|
|
172
|
-
existing.push(sym.name)
|
|
173
|
-
grouped.set(sym.type, existing)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const parts: string[] = []
|
|
177
|
-
for (const [type, names] of grouped) {
|
|
178
|
-
if (names.length <= 3) {
|
|
179
|
-
parts.push(`${names.join(', ')} (${type})`)
|
|
180
|
-
} else {
|
|
181
|
-
parts.push(`${names.slice(0, 3).join(', ')} +${names.length - 3} (${type})`)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return parts.join(' — ')
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// ─── Private Helpers ──────────────────────────────────────
|
|
189
|
-
|
|
190
|
-
private async collectFiles(dirPath: string): Promise<string[]> {
|
|
191
|
-
const files: string[] = []
|
|
192
|
-
await this.walkDirectory(dirPath, files)
|
|
193
|
-
if (files.length === 0) {
|
|
194
|
-
this.logger.warn({ dirPath }, 'collectFiles returned 0 files — trying fallback without withFileTypes')
|
|
195
|
-
await this.walkDirectoryFallback(dirPath, files)
|
|
196
|
-
}
|
|
197
|
-
return files
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private async walkDirectory(dirPath: string, files: string[]): Promise<void> {
|
|
201
|
-
let entries
|
|
202
|
-
try {
|
|
203
|
-
entries = await readdir(dirPath, { withFileTypes: true })
|
|
204
|
-
} catch (error) {
|
|
205
|
-
this.logger.debug({ dirPath, error: error instanceof Error ? error.message : String(error) }, 'Cannot read directory')
|
|
206
|
-
return
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
for (const entry of entries) {
|
|
210
|
-
if (entry.isDirectory()) {
|
|
211
|
-
if (!SKIP_DIRS.has(entry.name)) {
|
|
212
|
-
await this.walkDirectory(join(dirPath, entry.name), files)
|
|
213
|
-
}
|
|
214
|
-
} else if (entry.isFile()) {
|
|
215
|
-
if (SKIP_FILES.has(entry.name)) continue
|
|
216
|
-
const ext = extname(entry.name).toLowerCase()
|
|
217
|
-
if (!SUPPORTED_EXTENSIONS.has(ext)) continue
|
|
218
|
-
files.push(join(dirPath, entry.name))
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** Fallback walker that uses readdir (names only) + stat — works on all platforms */
|
|
224
|
-
private async walkDirectoryFallback(dirPath: string, files: string[]): Promise<void> {
|
|
225
|
-
let names: string[]
|
|
226
|
-
try {
|
|
227
|
-
names = await readdir(dirPath)
|
|
228
|
-
} catch (error) {
|
|
229
|
-
this.logger.debug({ dirPath, error: error instanceof Error ? error.message : String(error) }, 'Fallback: cannot read directory')
|
|
230
|
-
return
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
for (const name of names) {
|
|
234
|
-
const fullPath = join(dirPath, name)
|
|
235
|
-
let fileStat
|
|
236
|
-
try {
|
|
237
|
-
fileStat = await stat(fullPath)
|
|
238
|
-
} catch {
|
|
239
|
-
continue
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (fileStat.isDirectory()) {
|
|
243
|
-
if (!SKIP_DIRS.has(name)) {
|
|
244
|
-
await this.walkDirectoryFallback(fullPath, files)
|
|
245
|
-
}
|
|
246
|
-
} else if (fileStat.isFile()) {
|
|
247
|
-
if (SKIP_FILES.has(name)) continue
|
|
248
|
-
const ext = extname(name).toLowerCase()
|
|
249
|
-
if (!SUPPORTED_EXTENSIONS.has(ext)) continue
|
|
250
|
-
files.push(fullPath)
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private async indexSingleFile(
|
|
256
|
-
filePath: string,
|
|
257
|
-
projectPath: string,
|
|
258
|
-
projectName: string
|
|
259
|
-
): Promise<{ skipped: boolean; symbolCount: number }> {
|
|
260
|
-
// Check file size
|
|
261
|
-
const fileStats = await stat(filePath)
|
|
262
|
-
if (fileStats.size > MAX_FILE_SIZE) {
|
|
263
|
-
return { skipped: true, symbolCount: 0 }
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const content = await Bun.file(filePath).text()
|
|
267
|
-
const hash = createHash('sha256').update(content).digest('hex')
|
|
268
|
-
|
|
269
|
-
// Check if file is unchanged
|
|
270
|
-
const relativePath = relative(projectPath, filePath)
|
|
271
|
-
const existing = this.db.query(
|
|
272
|
-
'SELECT file_hash FROM code_files WHERE project = ? AND file_path = ?'
|
|
273
|
-
).get(projectName, relativePath) as { file_hash: string | null } | null
|
|
274
|
-
|
|
275
|
-
if (existing?.file_hash === hash) {
|
|
276
|
-
return { skipped: true, symbolCount: 0 }
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Clear old data for this file
|
|
280
|
-
this.db.run('DELETE FROM code_symbols WHERE project = ? AND file_path = ?', [projectName, relativePath])
|
|
281
|
-
this.db.run('DELETE FROM code_dependencies WHERE project = ? AND source_file = ?', [projectName, relativePath])
|
|
282
|
-
this.db.run('DELETE FROM code_files WHERE project = ? AND file_path = ?', [projectName, relativePath])
|
|
283
|
-
|
|
284
|
-
// Parse and store
|
|
285
|
-
const parsed = await this.parser.parseFile(filePath, content)
|
|
286
|
-
// Override filePath with relative path for storage
|
|
287
|
-
const storable: ParsedFile = { ...parsed, filePath: relativePath }
|
|
288
|
-
|
|
289
|
-
this.storeFile(storable, projectName, hash)
|
|
290
|
-
this.storeSymbols(storable, projectName)
|
|
291
|
-
this.storeDependencies(storable, projectName)
|
|
292
|
-
|
|
293
|
-
return { skipped: false, symbolCount: storable.symbols.length }
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
private storeFile(parsed: ParsedFile, projectName: string, hash: string): void {
|
|
297
|
-
const summary = this.generateFileSummary(parsed)
|
|
298
|
-
this.db.run(
|
|
299
|
-
`INSERT OR REPLACE INTO code_files (project, file_path, language, summary, symbol_count, line_count, last_indexed, file_hash)
|
|
300
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
301
|
-
[
|
|
302
|
-
projectName,
|
|
303
|
-
parsed.filePath,
|
|
304
|
-
parsed.language,
|
|
305
|
-
summary,
|
|
306
|
-
parsed.symbols.length,
|
|
307
|
-
parsed.lineCount,
|
|
308
|
-
new Date().toISOString(),
|
|
309
|
-
hash,
|
|
310
|
-
]
|
|
311
|
-
)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private storeSymbols(parsed: ParsedFile, projectName: string): void {
|
|
315
|
-
const stmt = this.db.prepare(
|
|
316
|
-
`INSERT INTO code_symbols (project, file_path, symbol_name, symbol_type, line_start, line_end, parent_symbol, signature, exported, updated_at)
|
|
317
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
for (const sym of parsed.symbols) {
|
|
321
|
-
stmt.run(
|
|
322
|
-
projectName,
|
|
323
|
-
parsed.filePath,
|
|
324
|
-
sym.name,
|
|
325
|
-
sym.type,
|
|
326
|
-
sym.lineStart,
|
|
327
|
-
sym.lineEnd ?? null,
|
|
328
|
-
sym.parentSymbol ?? null,
|
|
329
|
-
sym.signature ?? null,
|
|
330
|
-
sym.exported ? 1 : 0,
|
|
331
|
-
new Date().toISOString()
|
|
332
|
-
)
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private storeDependencies(parsed: ParsedFile, projectName: string): void {
|
|
337
|
-
const stmt = this.db.prepare(
|
|
338
|
-
`INSERT INTO code_dependencies (project, source_file, target_file, import_names, import_type)
|
|
339
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
for (const imp of parsed.imports) {
|
|
343
|
-
stmt.run(
|
|
344
|
-
projectName,
|
|
345
|
-
parsed.filePath,
|
|
346
|
-
imp.source,
|
|
347
|
-
imp.names.join(', '),
|
|
348
|
-
imp.importType
|
|
349
|
-
)
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
1
|
+
import { Database } from 'bun:sqlite'
|
|
2
|
+
import { readdir, stat } from 'node:fs/promises'
|
|
3
|
+
import { join, relative, extname } from 'node:path'
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
import type { Logger } from 'pino'
|
|
6
|
+
import type { CodeParser } from './parser'
|
|
7
|
+
import type { IndexResult, IndexStats, ParsedFile } from './types'
|
|
8
|
+
import { createCodeSchema } from './schema'
|
|
9
|
+
|
|
10
|
+
const SKIP_DIRS = new Set([
|
|
11
|
+
'node_modules', '.git', 'dist', 'build', '__pycache__',
|
|
12
|
+
'.venv', 'target', '.cache', 'coverage', '.turbo', '.output', 'vendor',
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
const SKIP_FILES = new Set([
|
|
16
|
+
'package-lock.json', 'yarn.lock', 'bun.lockb', 'pnpm-lock.yaml', '.DS_Store',
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
const MAX_FILE_SIZE = 500 * 1024 // 500KB
|
|
20
|
+
|
|
21
|
+
const SUPPORTED_EXTENSIONS = new Set([
|
|
22
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
23
|
+
'.py', '.go', '.rs', '.vue',
|
|
24
|
+
'.html', '.css', '.json', '.yaml', '.yml',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
const BATCH_SIZE = 100
|
|
28
|
+
|
|
29
|
+
export class CodeIndexer {
|
|
30
|
+
private db: Database
|
|
31
|
+
private parser: CodeParser
|
|
32
|
+
private logger: Logger
|
|
33
|
+
|
|
34
|
+
constructor(db: Database, parser: CodeParser, logger: Logger) {
|
|
35
|
+
this.db = db
|
|
36
|
+
this.parser = parser
|
|
37
|
+
this.logger = logger.child({ component: 'code-indexer' })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async initialize(): Promise<void> {
|
|
41
|
+
createCodeSchema(this.db)
|
|
42
|
+
this.logger.info('Code intelligence schema initialized')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async indexProject(projectPath: string, projectName: string): Promise<IndexResult> {
|
|
46
|
+
const startTime = Date.now()
|
|
47
|
+
const errors: string[] = []
|
|
48
|
+
let filesIndexed = 0
|
|
49
|
+
let filesSkipped = 0
|
|
50
|
+
let symbolsFound = 0
|
|
51
|
+
|
|
52
|
+
this.logger.info({ projectPath, projectName }, 'Starting project index')
|
|
53
|
+
|
|
54
|
+
// Collect all eligible files
|
|
55
|
+
const files = await this.collectFiles(projectPath)
|
|
56
|
+
this.logger.info({ fileCount: files.length }, 'Files collected for indexing')
|
|
57
|
+
|
|
58
|
+
// Process in batches
|
|
59
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
60
|
+
const batch = files.slice(i, i + BATCH_SIZE)
|
|
61
|
+
this.db.run('BEGIN TRANSACTION')
|
|
62
|
+
try {
|
|
63
|
+
for (const filePath of batch) {
|
|
64
|
+
try {
|
|
65
|
+
const result = await this.indexSingleFile(filePath, projectPath, projectName)
|
|
66
|
+
if (result.skipped) {
|
|
67
|
+
filesSkipped++
|
|
68
|
+
} else {
|
|
69
|
+
filesIndexed++
|
|
70
|
+
symbolsFound += result.symbolCount
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const msg = `Failed to index ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
74
|
+
errors.push(msg)
|
|
75
|
+
this.logger.warn({ filePath, error }, 'Failed to index file')
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
this.db.run('COMMIT')
|
|
79
|
+
} catch (error) {
|
|
80
|
+
this.db.run('ROLLBACK')
|
|
81
|
+
const msg = `Batch failed at offset ${i}: ${error instanceof Error ? error.message : String(error)}`
|
|
82
|
+
errors.push(msg)
|
|
83
|
+
this.logger.error({ error, batchOffset: i }, 'Batch transaction failed')
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const durationMs = Date.now() - startTime
|
|
88
|
+
this.logger.info(
|
|
89
|
+
{ filesIndexed, filesSkipped, symbolsFound, durationMs, errors: errors.length },
|
|
90
|
+
'Project indexing complete'
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
project: projectName,
|
|
95
|
+
filesIndexed,
|
|
96
|
+
filesSkipped,
|
|
97
|
+
symbolsFound,
|
|
98
|
+
durationMs,
|
|
99
|
+
errors,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async indexFile(filePath: string, projectName: string): Promise<void> {
|
|
104
|
+
this.db.run('BEGIN TRANSACTION')
|
|
105
|
+
try {
|
|
106
|
+
// Clear existing data for this file
|
|
107
|
+
this.db.run('DELETE FROM code_symbols WHERE project = ? AND file_path = ?', [projectName, filePath])
|
|
108
|
+
this.db.run('DELETE FROM code_dependencies WHERE project = ? AND source_file = ?', [projectName, filePath])
|
|
109
|
+
this.db.run('DELETE FROM code_files WHERE project = ? AND file_path = ?', [projectName, filePath])
|
|
110
|
+
|
|
111
|
+
const content = await Bun.file(filePath).text()
|
|
112
|
+
const hash = createHash('sha256').update(content).digest('hex')
|
|
113
|
+
const parsed = await this.parser.parseFile(filePath, content)
|
|
114
|
+
|
|
115
|
+
this.storeFile(parsed, projectName, hash)
|
|
116
|
+
this.storeSymbols(parsed, projectName)
|
|
117
|
+
this.storeDependencies(parsed, projectName)
|
|
118
|
+
|
|
119
|
+
this.db.run('COMMIT')
|
|
120
|
+
this.logger.debug({ filePath, symbols: parsed.symbols.length }, 'File re-indexed')
|
|
121
|
+
} catch (error) {
|
|
122
|
+
this.db.run('ROLLBACK')
|
|
123
|
+
throw error
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async clearProject(projectName: string): Promise<void> {
|
|
128
|
+
this.db.run('BEGIN TRANSACTION')
|
|
129
|
+
try {
|
|
130
|
+
this.db.run('DELETE FROM code_symbols WHERE project = ?', [projectName])
|
|
131
|
+
this.db.run('DELETE FROM code_dependencies WHERE project = ?', [projectName])
|
|
132
|
+
this.db.run('DELETE FROM code_files WHERE project = ?', [projectName])
|
|
133
|
+
this.db.run('COMMIT')
|
|
134
|
+
this.logger.info({ projectName }, 'Project data cleared')
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.db.run('ROLLBACK')
|
|
137
|
+
throw error
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getStats(projectName: string): IndexStats {
|
|
142
|
+
const fileRow = this.db.query(
|
|
143
|
+
'SELECT COUNT(*) as cnt FROM code_files WHERE project = ?'
|
|
144
|
+
).get(projectName) as { cnt: number } | null
|
|
145
|
+
|
|
146
|
+
const symbolRow = this.db.query(
|
|
147
|
+
'SELECT COUNT(*) as cnt FROM code_symbols WHERE project = ?'
|
|
148
|
+
).get(projectName) as { cnt: number } | null
|
|
149
|
+
|
|
150
|
+
const lastRow = this.db.query(
|
|
151
|
+
'SELECT MAX(last_indexed) as last_indexed FROM code_files WHERE project = ?'
|
|
152
|
+
).get(projectName) as { last_indexed: string | null } | null
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
project: projectName,
|
|
156
|
+
filesIndexed: fileRow?.cnt ?? 0,
|
|
157
|
+
totalSymbols: symbolRow?.cnt ?? 0,
|
|
158
|
+
lastIndexed: lastRow?.last_indexed ?? null,
|
|
159
|
+
staleFiles: 0, // Could be computed by comparing file hashes
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
generateFileSummary(parsed: ParsedFile): string {
|
|
164
|
+
const exported = parsed.symbols.filter(s => s.exported)
|
|
165
|
+
if (exported.length === 0) {
|
|
166
|
+
return `${parsed.language} file, ${parsed.lineCount} lines`
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const grouped = new Map<string, string[]>()
|
|
170
|
+
for (const sym of exported) {
|
|
171
|
+
const existing = grouped.get(sym.type) ?? []
|
|
172
|
+
existing.push(sym.name)
|
|
173
|
+
grouped.set(sym.type, existing)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const parts: string[] = []
|
|
177
|
+
for (const [type, names] of grouped) {
|
|
178
|
+
if (names.length <= 3) {
|
|
179
|
+
parts.push(`${names.join(', ')} (${type})`)
|
|
180
|
+
} else {
|
|
181
|
+
parts.push(`${names.slice(0, 3).join(', ')} +${names.length - 3} (${type})`)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return parts.join(' — ')
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── Private Helpers ──────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
private async collectFiles(dirPath: string): Promise<string[]> {
|
|
191
|
+
const files: string[] = []
|
|
192
|
+
await this.walkDirectory(dirPath, files)
|
|
193
|
+
if (files.length === 0) {
|
|
194
|
+
this.logger.warn({ dirPath }, 'collectFiles returned 0 files — trying fallback without withFileTypes')
|
|
195
|
+
await this.walkDirectoryFallback(dirPath, files)
|
|
196
|
+
}
|
|
197
|
+
return files
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async walkDirectory(dirPath: string, files: string[]): Promise<void> {
|
|
201
|
+
let entries
|
|
202
|
+
try {
|
|
203
|
+
entries = await readdir(dirPath, { withFileTypes: true })
|
|
204
|
+
} catch (error) {
|
|
205
|
+
this.logger.debug({ dirPath, error: error instanceof Error ? error.message : String(error) }, 'Cannot read directory')
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
if (entry.isDirectory()) {
|
|
211
|
+
if (!SKIP_DIRS.has(entry.name)) {
|
|
212
|
+
await this.walkDirectory(join(dirPath, entry.name), files)
|
|
213
|
+
}
|
|
214
|
+
} else if (entry.isFile()) {
|
|
215
|
+
if (SKIP_FILES.has(entry.name)) continue
|
|
216
|
+
const ext = extname(entry.name).toLowerCase()
|
|
217
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue
|
|
218
|
+
files.push(join(dirPath, entry.name))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Fallback walker that uses readdir (names only) + stat — works on all platforms */
|
|
224
|
+
private async walkDirectoryFallback(dirPath: string, files: string[]): Promise<void> {
|
|
225
|
+
let names: string[]
|
|
226
|
+
try {
|
|
227
|
+
names = await readdir(dirPath)
|
|
228
|
+
} catch (error) {
|
|
229
|
+
this.logger.debug({ dirPath, error: error instanceof Error ? error.message : String(error) }, 'Fallback: cannot read directory')
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const name of names) {
|
|
234
|
+
const fullPath = join(dirPath, name)
|
|
235
|
+
let fileStat
|
|
236
|
+
try {
|
|
237
|
+
fileStat = await stat(fullPath)
|
|
238
|
+
} catch {
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (fileStat.isDirectory()) {
|
|
243
|
+
if (!SKIP_DIRS.has(name)) {
|
|
244
|
+
await this.walkDirectoryFallback(fullPath, files)
|
|
245
|
+
}
|
|
246
|
+
} else if (fileStat.isFile()) {
|
|
247
|
+
if (SKIP_FILES.has(name)) continue
|
|
248
|
+
const ext = extname(name).toLowerCase()
|
|
249
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue
|
|
250
|
+
files.push(fullPath)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private async indexSingleFile(
|
|
256
|
+
filePath: string,
|
|
257
|
+
projectPath: string,
|
|
258
|
+
projectName: string
|
|
259
|
+
): Promise<{ skipped: boolean; symbolCount: number }> {
|
|
260
|
+
// Check file size
|
|
261
|
+
const fileStats = await stat(filePath)
|
|
262
|
+
if (fileStats.size > MAX_FILE_SIZE) {
|
|
263
|
+
return { skipped: true, symbolCount: 0 }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const content = await Bun.file(filePath).text()
|
|
267
|
+
const hash = createHash('sha256').update(content).digest('hex')
|
|
268
|
+
|
|
269
|
+
// Check if file is unchanged
|
|
270
|
+
const relativePath = relative(projectPath, filePath)
|
|
271
|
+
const existing = this.db.query(
|
|
272
|
+
'SELECT file_hash FROM code_files WHERE project = ? AND file_path = ?'
|
|
273
|
+
).get(projectName, relativePath) as { file_hash: string | null } | null
|
|
274
|
+
|
|
275
|
+
if (existing?.file_hash === hash) {
|
|
276
|
+
return { skipped: true, symbolCount: 0 }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Clear old data for this file
|
|
280
|
+
this.db.run('DELETE FROM code_symbols WHERE project = ? AND file_path = ?', [projectName, relativePath])
|
|
281
|
+
this.db.run('DELETE FROM code_dependencies WHERE project = ? AND source_file = ?', [projectName, relativePath])
|
|
282
|
+
this.db.run('DELETE FROM code_files WHERE project = ? AND file_path = ?', [projectName, relativePath])
|
|
283
|
+
|
|
284
|
+
// Parse and store
|
|
285
|
+
const parsed = await this.parser.parseFile(filePath, content)
|
|
286
|
+
// Override filePath with relative path for storage
|
|
287
|
+
const storable: ParsedFile = { ...parsed, filePath: relativePath }
|
|
288
|
+
|
|
289
|
+
this.storeFile(storable, projectName, hash)
|
|
290
|
+
this.storeSymbols(storable, projectName)
|
|
291
|
+
this.storeDependencies(storable, projectName)
|
|
292
|
+
|
|
293
|
+
return { skipped: false, symbolCount: storable.symbols.length }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private storeFile(parsed: ParsedFile, projectName: string, hash: string): void {
|
|
297
|
+
const summary = this.generateFileSummary(parsed)
|
|
298
|
+
this.db.run(
|
|
299
|
+
`INSERT OR REPLACE INTO code_files (project, file_path, language, summary, symbol_count, line_count, last_indexed, file_hash)
|
|
300
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
301
|
+
[
|
|
302
|
+
projectName,
|
|
303
|
+
parsed.filePath,
|
|
304
|
+
parsed.language,
|
|
305
|
+
summary,
|
|
306
|
+
parsed.symbols.length,
|
|
307
|
+
parsed.lineCount,
|
|
308
|
+
new Date().toISOString(),
|
|
309
|
+
hash,
|
|
310
|
+
]
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private storeSymbols(parsed: ParsedFile, projectName: string): void {
|
|
315
|
+
const stmt = this.db.prepare(
|
|
316
|
+
`INSERT INTO code_symbols (project, file_path, symbol_name, symbol_type, line_start, line_end, parent_symbol, signature, exported, updated_at)
|
|
317
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
for (const sym of parsed.symbols) {
|
|
321
|
+
stmt.run(
|
|
322
|
+
projectName,
|
|
323
|
+
parsed.filePath,
|
|
324
|
+
sym.name,
|
|
325
|
+
sym.type,
|
|
326
|
+
sym.lineStart,
|
|
327
|
+
sym.lineEnd ?? null,
|
|
328
|
+
sym.parentSymbol ?? null,
|
|
329
|
+
sym.signature ?? null,
|
|
330
|
+
sym.exported ? 1 : 0,
|
|
331
|
+
new Date().toISOString()
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private storeDependencies(parsed: ParsedFile, projectName: string): void {
|
|
337
|
+
const stmt = this.db.prepare(
|
|
338
|
+
`INSERT INTO code_dependencies (project, source_file, target_file, import_names, import_type)
|
|
339
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
for (const imp of parsed.imports) {
|
|
343
|
+
stmt.run(
|
|
344
|
+
projectName,
|
|
345
|
+
parsed.filePath,
|
|
346
|
+
imp.source,
|
|
347
|
+
imp.names.join(', '),
|
|
348
|
+
imp.importType
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|