@@ -45,16 +45,13 @@ type Engine struct {
4545 pendingFiles map [string ]map [string ]struct {} // workspaceID -> set(filePath)
4646 pendingOverflow map [string ]bool // workspaceID -> too many pending changes, fallback to full scan
4747
48- progress * progressStore
48+
4949
5050 // detectionCache stores resolved WorkspaceContext with TTL to avoid
5151 // repeated full resolver cascades for the same path.
5252 detectionCache sync.Map // map[string]*detectionCacheEntry
5353
54- // resumeAttempts throttles auto-resume of interrupted indexing.
55- // Key: workspace ID, Value: time.Time of last resume attempt.
56- // Prevents CPU/log churn when indexing keeps failing (e.g. Ollama down).
57- resumeAttempts sync.Map
54+
5855
5956 // connectTriggered tracks whether background indexing was automatically
6057 // triggered for a workspace ID upon initial daemon resolution.
@@ -116,19 +113,16 @@ func NewEngine(idx *indexer.Service, srv *search.Service, registryPath string, c
116113 resolver : res ,
117114 config : cfg ,
118115 watchers : watcherMgr ,
119- progress : newProgressStore (),
116+
120117 pendingFiles : make (map [string ]map [string ]struct {}),
121118 pendingOverflow : make (map [string ]bool ),
122119 }
123120}
124121
125- // GetIndexProgress returns the last known indexing progress for a workspace.
126- // workspaceRoot is used as a hint to load persisted status from disc if not in memory.
127- func (e * Engine ) GetIndexProgress (workspaceID , workspaceRoot string ) * IndexProgress {
128- if e .progress == nil {
129- return nil
130- }
131- return e .progress .get (workspaceID , workspaceRoot )
122+ // GetIndexStatus returns the last known indexing status for a workspace.
123+ // Reads directly from {workspaceRoot}/.ragcode/index_status.json.
124+ func (e * Engine ) GetIndexStatus (workspaceRoot string ) * indexer.IndexStatus {
125+ return indexer .LoadIndexStatus (workspaceRoot )
132126}
133127
134128// ActiveIndexingJobs returns the IDs of workspaces currently being indexed.
@@ -413,22 +407,7 @@ func (e *Engine) SearchCode(ctx context.Context, filePath, queryText string, lim
413407 primaryColl := wctx .CollectionName (primaryLang )
414408 t1 := time .Now ()
415409
416- // Auto-resume: if GlobalPercent is between 1-99 and state is "running",
417- // the indexer was interrupted — trigger a background re-index.
418- if idxStatus := loadIndexStatus (wctx .Root ); idxStatus != nil {
419- if idxStatus .WorkspaceID == wctx .ID && idxStatus .GlobalPercent > 0 && idxStatus .GlobalPercent < 100 && idxStatus .State == "running" {
420- if _ , ok := e .indexingJobs .Load (wctx .ID ); ! ok {
421- const resumeCooldown = 5 * time .Minute
422- now := time .Now ()
423- if last , loaded := e .resumeAttempts .Load (wctx .ID ); ! loaded || now .Sub (last .(time.Time )) > resumeCooldown {
424- e .resumeAttempts .Store (wctx .ID , now )
425- logger .Instance .Info ("[IDX] ws=%s Indexing interrupted at %d%% — auto-resuming" , filepath .Base (wctx .Root ), idxStatus .GlobalPercent )
426- e .StartIndexingAsync (wctx .Root , wctx .ID , nil , false )
427- }
428- }
429- logger .Instance .Info ("[IDX] ws=%s Indexing in progress (%d%%) — will search available collections" , filepath .Base (wctx .Root ), idxStatus .GlobalPercent )
430- }
431- }
410+
432411
433412 // Check if the primary collection exists.
434413 // If not, trigger background indexing but do NOT block — the fan-out below
@@ -622,21 +601,7 @@ func (e *Engine) HybridSearchCode(ctx context.Context, filePath, queryText strin
622601
623602 collection := wctx .CollectionName (lang )
624603
625- // Auto-resume from index_status.json: if indexing was interrupted, resume it.
626- if idxStatus := loadIndexStatus (wctx .Root ); idxStatus != nil {
627- if idxStatus .WorkspaceID == wctx .ID && idxStatus .GlobalPercent > 0 && idxStatus .GlobalPercent < 100 && idxStatus .State == "running" {
628- if _ , ok := e .indexingJobs .Load (wctx .ID ); ! ok {
629- const resumeCooldown = 5 * time .Minute
630- now := time .Now ()
631- if last , loaded := e .resumeAttempts .Load (wctx .ID ); ! loaded || now .Sub (last .(time.Time )) > resumeCooldown {
632- e .resumeAttempts .Store (wctx .ID , now )
633- logger .Instance .Info ("[IDX] ws=%s Indexing interrupted at %d%% — auto-resuming" , filepath .Base (wctx .Root ), idxStatus .GlobalPercent )
634- e .StartIndexingAsync (wctx .Root , wctx .ID , nil , false )
635- }
636- }
637- logger .Instance .Info ("[IDX] ws=%s Indexing in progress (%d%%) — will search available collections" , filepath .Base (wctx .Root ), idxStatus .GlobalPercent )
638- }
639- }
604+
640605
641606 exists , err := e .search .CollectionExists (ctx , collection )
642607 if err != nil {
@@ -822,10 +787,7 @@ func (e *Engine) StartIndexingAsync(root, id string, changedFiles []string, recr
822787 logger .Instance .Warn ("[IDX] ⚠️ %d workspaces indexing simultaneously — Ollama requests will serialize implicitly (ws=%s)" , activeCount , filepath .Base (root ))
823788 }
824789
825- jobID := fmt .Sprintf ("%s-%d" , id , time .Now ().UnixNano ())
826- if e .progress != nil {
827- e .progress .start (id , root , jobID , time .Now ())
828- }
790+ indexer .SaveIndexStatus (root , & indexer.IndexStatus {State : "starting" , StartedAt : time .Now ().UTC ().Format (time .RFC3339 )})
829791
830792 go func () {
831793 defer func () {
@@ -847,13 +809,18 @@ func (e *Engine) StartIndexingAsync(root, id string, changedFiles []string, recr
847809
848810 if err != nil {
849811 logger .Instance .Error ("[IDX] ws=%s Background indexing failed: %v" , filepath .Base (root ), err )
850- if e .progress != nil {
851- e .progress .fail (id , root , time .Now (), err .Error ())
812+ if s := indexer .LoadIndexStatus (root ); s != nil {
813+ s .State = "failed"
814+ s .Error = err .Error ()
815+ s .EndedAt = time .Now ().UTC ().Format (time .RFC3339 )
816+ indexer .SaveIndexStatus (root , s )
852817 }
853818 } else {
854819 logger .Instance .Info ("[IDX] ✅ ws=%s Background indexing completed" , filepath .Base (root ))
855- if e .progress != nil {
856- e .progress .complete (id , root , time .Now ())
820+ if s := indexer .LoadIndexStatus (root ); s != nil {
821+ s .State = "completed"
822+ s .EndedAt = time .Now ().UTC ().Format (time .RFC3339 )
823+ indexer .SaveIndexStatus (root , s )
857824 }
858825 }
859826 }()
@@ -916,39 +883,34 @@ func (e *Engine) IndexWorkspace(ctx context.Context, path string, recreate bool)
916883
917884 wsName := filepath .Base (wctx .Root )
918885
919- // Pre-scan: count files for every language before starting indexing.
920- // This gives progressStore an accurate denominator from the start, so
921- // GlobalPercent increases monotonically instead of resetting per language.
922886 var excludePatterns []string
923887 if e .config != nil {
924888 excludePatterns = e .config .Workspace .ExcludePatterns
925889 }
926- // Pre-scan: single WalkDir counting files per language before indexing begins.
927- // This gives progressStore an accurate denominator from the start, so
928- // GlobalPercent increases monotonically. One combined walk avoids
929- // O(languages x files) traversals that per-language scans would incur.
930- if e .progress != nil {
931- fileCounts := e .indexer .CountAllFiles (wctx .Root , excludePatterns )
932- for _ , lang := range languages {
933- e .progress .preRegister (wctx .ID , lang , fileCounts [lang ], time .Now ())
934- }
935- }
936890
937891 var indexErrors []string
938892 for _ , lang := range languages {
939893 collection := wctx .CollectionName (lang )
940- progressCb := func (doneFiles , totalFiles int ) {
941- if e .progress != nil {
942- e .progress .update (wctx .ID , lang , doneFiles , totalFiles , time .Now ())
943- }
944- }
945894 logger .Instance .Info ("[IDX] ws=%s lang=%s ▶ starting" , wsName , lang )
946895 err := e .indexer .IndexWorkspace (ctx , wctx .Root , collection , indexer.Options {
947896 Language : lang ,
948897 WorkspaceName : wsName ,
949898 ExcludePatterns : excludePatterns ,
950899 Recreate : recreate ,
951- Progress : progressCb ,
900+ Progress : func (doneFiles , totalFiles int ) {
901+ if s := indexer .LoadIndexStatus (wctx .Root ); s != nil {
902+ s .State = "running"
903+ if s .Languages == nil {
904+ s .Languages = make (map [string ]indexer.LangStatus )
905+ }
906+ ls := s .Languages [lang ]
907+ ls .OnDisk = totalFiles
908+ ls .Changed = totalFiles
909+ ls .Processed = doneFiles
910+ s .Languages [lang ] = ls
911+ indexer .SaveIndexStatus (wctx .Root , s )
912+ }
913+ },
952914 })
953915 if err != nil {
954916 logger .Instance .Error ("[IDX] ws=%s lang=%s ❌ failed: %v" , wsName , lang , err )
0 commit comments