1313import { readFileSync , statSync , readdirSync , existsSync , mkdirSync } from "node:fs" ;
1414import { resolve , extname , relative , join , basename } from "node:path" ;
1515import { createHash } from "node:crypto" ;
16- import { getStore , CODEGRAPH_DIR } from "./store.mjs" ;
16+ import { getStore , hasOpenStore , CODEGRAPH_DIR } from "./store.mjs" ;
1717import { runFrameworkOverlay } from "./framework.mjs" ;
1818import { parseFile , languageFor , supportedExtensions } from "./parser.mjs" ;
1919import { discoverWorkspace , persistWorkspace } from "./workspace.mjs" ;
@@ -61,12 +61,15 @@ function referenceEdgeEvidence(ref) {
6161export async function indexProject ( projectPath , { languages } = { } ) {
6262 const absPath = resolve ( projectPath ) ;
6363 const t0 = Date . now ( ) ;
64+ let store ;
65+ const shouldCloseStore = ! hasOpenStore ( absPath , { mode : "write" } ) ;
6466
6567 // Ensure .hex-skills/codegraph dir exists
6668 const dbDir = join ( absPath , CODEGRAPH_DIR ) ;
6769 if ( ! existsSync ( dbDir ) ) mkdirSync ( dbDir , { recursive : true } ) ;
6870
69- const store = getStore ( absPath ) ;
71+ try {
72+ store = getStore ( absPath ) ;
7073
7174 // Filter extensions by language if specified
7275 const allowedExts = languages
@@ -170,18 +173,24 @@ export async function indexProject(projectPath, { languages } = {}) {
170173 const elapsed = Date . now ( ) - t0 ;
171174 const stats = store . stats ( ) ;
172175
173- return [
174- `Indexed ${ stats . files } files, ${ stats . nodes } symbols, ${ stats . edges } edges in ${ elapsed } ms` ,
175- purged > 0 ? `Purged ${ purged } deleted files` : null ,
176- `Parsed ${ parsed } files (${ filesToIndex . length - parsed } skipped, unchanged)` ,
177- `Built ${ edgeCount } new call edges` ,
178- precise . precise_edges > 0 ? `Added ${ precise . precise_edges } precise overlay edges` : null ,
179- framework . edge_count > 0 ? `Added ${ framework . edge_count } framework overlay edges` : null ,
180- ...( precise . providers || [ ] )
181- . filter ( provider => provider . status && provider . status !== "available" )
182- . map ( provider => provider . message )
183- . filter ( Boolean ) ,
184- ] . filter ( Boolean ) . join ( "\n" ) ;
176+ return [
177+ `Indexed ${ stats . files } files, ${ stats . nodes } symbols, ${ stats . edges } edges in ${ elapsed } ms` ,
178+ purged > 0 ? `Purged ${ purged } deleted files` : null ,
179+ `Parsed ${ parsed } files (${ filesToIndex . length - parsed } skipped, unchanged)` ,
180+ `Built ${ edgeCount } new call edges` ,
181+ precise . precise_edges > 0 ? `Added ${ precise . precise_edges } precise overlay edges` : null ,
182+ framework . edge_count > 0 ? `Added ${ framework . edge_count } framework overlay edges` : null ,
183+ ...( precise . providers || [ ] )
184+ . filter ( provider => provider . status && provider . status !== "available" )
185+ . map ( provider => provider . message )
186+ . filter ( Boolean ) ,
187+ ] . filter ( Boolean ) . join ( "\n" ) ;
188+ } finally {
189+ if ( store && shouldCloseStore ) {
190+ try { store . checkpoint ( ) ; } catch { /* best-effort WAL flush */ }
191+ store . close ( ) ;
192+ }
193+ }
185194}
186195
187196/**
@@ -192,52 +201,61 @@ export async function indexProject(projectPath, { languages } = {}) {
192201export async function reindexFile ( projectPath , filePath ) {
193202 const absPath = resolve ( projectPath ) ;
194203 const fullPath = resolve ( absPath , filePath ) ;
204+ let store ;
205+ const shouldCloseStore = ! hasOpenStore ( absPath , { mode : "write" } ) ;
195206
196- if ( ! existsSync ( fullPath ) ) {
197- const store = getStore ( absPath ) ;
198- store . deleteFile ( filePath ) ;
199- return ;
200- }
207+ try {
208+ if ( ! existsSync ( fullPath ) ) {
209+ store = getStore ( absPath ) ;
210+ store . deleteFile ( filePath ) ;
211+ return ;
212+ }
201213
202- const source = readFileSync ( fullPath , "utf-8" ) . replace ( / \r \n / g, "\n" ) ;
203- const hash = createHash ( "md5" ) . update ( source ) . digest ( "hex" ) . slice ( 0 , 12 ) ;
204- const stat = statSync ( fullPath ) ;
205- const language = languageFor ( extname ( filePath ) . toLowerCase ( ) ) ;
206- if ( ! language ) return ;
214+ const source = readFileSync ( fullPath , "utf-8" ) . replace ( / \r \n / g, "\n" ) ;
215+ const hash = createHash ( "md5" ) . update ( source ) . digest ( "hex" ) . slice ( 0 , 12 ) ;
216+ const stat = statSync ( fullPath ) ;
217+ const language = languageFor ( extname ( filePath ) . toLowerCase ( ) ) ;
218+ if ( ! language ) return ;
219+
220+ store = getStore ( absPath ) ;
221+ const allSourceFiles = [ ] ;
222+ walkDir ( absPath , absPath , new Set ( supportedExtensions ( ) ) , store , [ ] , allSourceFiles ) ;
223+ const workspace = persistWorkspace ( store , discoverWorkspace ( absPath , allSourceFiles ) ) ;
224+ const projectLanguages = [ ...new Set ( allSourceFiles . map ( file => file . language ) . filter ( Boolean ) ) ] ;
225+ const { definitions, imports, calls, references, flow_ir, exports : fileExports , defaultExport, reexports } = await parseFile ( fullPath , source , { cloneDetection : true } ) ;
226+ const nodeIds = store . bulkInsert (
227+ filePath ,
228+ stat . mtimeMs ,
229+ hash ,
230+ language ,
231+ definitions ,
232+ imports ,
233+ workspace . ownershipIds . get ( filePath ) || null ,
234+ ) ;
207235
208- const store = getStore ( absPath ) ;
209- const allSourceFiles = [ ] ;
210- walkDir ( absPath , absPath , new Set ( supportedExtensions ( ) ) , store , [ ] , allSourceFiles ) ;
211- const workspace = persistWorkspace ( store , discoverWorkspace ( absPath , allSourceFiles ) ) ;
212- const projectLanguages = [ ...new Set ( allSourceFiles . map ( file => file . language ) . filter ( Boolean ) ) ] ;
213- const { definitions, imports, calls, references, flow_ir, exports : fileExports , defaultExport, reexports } = await parseFile ( fullPath , source , { cloneDetection : true } ) ;
214- const nodeIds = store . bulkInsert (
215- filePath ,
216- stat . mtimeMs ,
217- hash ,
218- language ,
219- definitions ,
220- imports ,
221- workspace . ownershipIds . get ( filePath ) || null ,
222- ) ;
223-
224- persistCloneData ( store , definitions , nodeIds ) ;
225- resolveFileEdges ( store , workspace , filePath , {
226- source, definitions, imports, calls, references, flow_ir,
227- exports : fileExports , defaultExport, reexports, nodeIds, language,
228- } ) ;
229- store . rebuildAllModuleLayerEdges ( ) ;
230- await runPreciseOverlay ( {
231- projectPath : absPath ,
232- store,
233- languages : projectLanguages ,
234- sourceFiles : allSourceFiles . map ( file => file . relPath ) ,
235- } ) ;
236- runFrameworkOverlay ( {
237- projectPath : absPath ,
238- store,
239- sourceFiles : allSourceFiles . map ( file => file . relPath ) ,
240- } ) ;
236+ persistCloneData ( store , definitions , nodeIds ) ;
237+ resolveFileEdges ( store , workspace , filePath , {
238+ source, definitions, imports, calls, references, flow_ir,
239+ exports : fileExports , defaultExport, reexports, nodeIds, language,
240+ } ) ;
241+ store . rebuildAllModuleLayerEdges ( ) ;
242+ await runPreciseOverlay ( {
243+ projectPath : absPath ,
244+ store,
245+ languages : projectLanguages ,
246+ sourceFiles : allSourceFiles . map ( file => file . relPath ) ,
247+ } ) ;
248+ runFrameworkOverlay ( {
249+ projectPath : absPath ,
250+ store,
251+ sourceFiles : allSourceFiles . map ( file => file . relPath ) ,
252+ } ) ;
253+ } finally {
254+ if ( store && shouldCloseStore ) {
255+ try { store . checkpoint ( ) ; } catch { /* best-effort WAL flush */ }
256+ store . close ( ) ;
257+ }
258+ }
241259}
242260
243261// --- Helpers ---
0 commit comments