@@ -26,7 +26,7 @@ class EmbeddingProgress:
2626 """Typed CLI progress payload for embedding backfills."""
2727
2828 entity_id : int
29- index : int
29+ completed : int
3030 total : int
3131
3232
@@ -147,20 +147,30 @@ def reindex(
147147 False , "--embeddings" , "-e" , help = "Rebuild vector embeddings (requires semantic search)"
148148 ),
149149 search : bool = typer .Option (False , "--search" , "-s" , help = "Rebuild full-text search index" ),
150+ full : bool = typer .Option (
151+ False ,
152+ "--full" ,
153+ help = "Force a full filesystem scan and file reindex instead of the default incremental scan" ,
154+ ),
150155 project : str = typer .Option (
151156 None , "--project" , "-p" , help = "Reindex a specific project (default: all)"
152157 ),
153158): # pragma: no cover
154159 """Rebuild search indexes and/or vector embeddings without dropping the database.
155160
156- By default rebuilds everything (search + embeddings if semantic is enabled).
157- Use --search or --embeddings to rebuild only one.
161+ By default runs incremental search + embeddings (if semantic search is enabled).
162+ Use --full to bypass incremental scan optimization, rebuild all file-backed search rows,
163+ and re-embed all eligible notes.
164+ Use --search or --embeddings to rebuild only one side.
158165
159166 Examples:
160- bm reindex # Rebuild everything
167+ bm reindex # Incremental search + embeddings
168+ bm reindex --full # Full search + full re-embed
161169 bm reindex --embeddings # Only rebuild vector embeddings
162170 bm reindex --search # Only rebuild FTS index
163- bm reindex -p claw # Reindex only the 'claw' project
171+ bm reindex --full --search # Full search only
172+ bm reindex --full --embeddings # Full re-embed only
173+ bm reindex -p claw --full # Full reindex for only the 'claw' project
164174 """
165175 # If neither flag is set, do both
166176 if not embeddings and not search :
@@ -179,10 +189,19 @@ def reindex(
179189 if not search :
180190 raise typer .Exit (0 )
181191
182- run_with_cleanup (_reindex (app_config , search = search , embeddings = embeddings , project = project ))
192+ run_with_cleanup (
193+ _reindex (app_config , search = search , embeddings = embeddings , full = full , project = project )
194+ )
183195
184196
185- async def _reindex (app_config , search : bool , embeddings : bool , project : str | None ):
197+ async def _reindex (
198+ app_config ,
199+ * ,
200+ search : bool ,
201+ embeddings : bool ,
202+ full : bool ,
203+ project : str | None ,
204+ ):
186205 """Run reindex operations."""
187206 from basic_memory .repository import EntityRepository
188207 from basic_memory .repository .search_repository import create_search_repository
@@ -220,6 +239,10 @@ async def _reindex(app_config, search: bool, embeddings: bool, project: str | No
220239 console .print (f"\n [bold]Project: [cyan]{ proj .name } [/cyan][/bold]" )
221240
222241 if search :
242+ search_mode_label = "full scan" if full else "incremental scan"
243+ console .print (
244+ f" Rebuilding full-text search index ([cyan]{ search_mode_label } [/cyan])..."
245+ )
223246 sync_service = await get_sync_service (proj )
224247 sync_dir = Path (proj .path )
225248 with Progress (
@@ -244,14 +267,19 @@ async def on_index_progress(update: IndexProgress) -> None:
244267 await sync_service .sync (
245268 sync_dir ,
246269 project_name = proj .name ,
270+ force_full = full ,
271+ sync_embeddings = False ,
247272 progress_callback = on_index_progress ,
248273 )
249274 progress .update (task , completed = progress .tasks [task ].total or 1 )
250275
251- console .print (" [green]✓ [/green] Full-text search index rebuilt" )
276+ console .print (" [green]done [/green] Full-text search index rebuilt" )
252277
253278 if embeddings :
254- console .print (" Building vector embeddings..." )
279+ embedding_mode_label = "full rebuild" if full else "incremental sync"
280+ console .print (
281+ f" Building vector embeddings ([cyan]{ embedding_mode_label } [/cyan])..."
282+ )
255283 entity_repository = EntityRepository (session_maker , project_id = proj .id )
256284 search_repository = create_search_repository (
257285 session_maker , project_id = proj .id , app_config = app_config
@@ -274,20 +302,27 @@ async def on_index_progress(update: IndexProgress) -> None:
274302 def on_progress (entity_id , index , total ):
275303 embedding_progress = EmbeddingProgress (
276304 entity_id = entity_id ,
277- index = index ,
305+ completed = index ,
278306 total = total ,
279307 )
308+ # Trigger: repository progress now reports terminal entity completion.
309+ # Why: operators need to see finished embedding work rather than
310+ # entities merely entering prepare.
311+ # Outcome: the CLI bar advances steadily with real completed work.
280312 progress .update (
281313 task ,
282314 total = embedding_progress .total ,
283- completed = embedding_progress .index ,
315+ completed = embedding_progress .completed ,
284316 )
285317
286- stats = await search_service .reindex_vectors (progress_callback = on_progress )
318+ stats = await search_service .reindex_vectors (
319+ progress_callback = on_progress ,
320+ force_full = full ,
321+ )
287322 progress .update (task , completed = stats ["total_entities" ])
288323
289324 console .print (
290- f" [green]✓ [/green] Embeddings complete: "
325+ f" [green]done [/green] Embeddings complete: "
291326 f"{ stats ['embedded' ]} entities embedded, "
292327 f"{ stats ['skipped' ]} skipped, "
293328 f"{ stats ['errors' ]} errors"
0 commit comments