@@ -73,7 +73,7 @@ def _configure_embedding_runtime(
7373) -> tuple [SimpleNamespace , AsyncMock , list [str ]]:
7474 """Install the runtime boundaries needed to exercise the real reindex command."""
7575 app_config = _stub_app_config ()
76- project = SimpleNamespace (id = 1 , name = "foo" , path = "/tmp/foo" )
76+ project = SimpleNamespace (id = 1 , name = "foo" , permalink = "foo" , path = "/tmp/foo" )
7777 printed_lines : list [str ] = []
7878 project_index = AsyncMock (
7979 return_value = SimpleNamespace (
@@ -282,7 +282,7 @@ async def test_reindex_project_full_uses_core_project_index_and_reports_summary(
282282 session_maker ,
283283):
284284 app_config = _stub_app_config ()
285- project = SimpleNamespace (id = 1 , name = "foo" , path = "/tmp/foo" )
285+ project = SimpleNamespace (id = 1 , name = "foo" , permalink = "foo" , path = "/tmp/foo" )
286286 project_index = AsyncMock (
287287 return_value = SimpleNamespace (
288288 total_files = 3 ,
@@ -343,7 +343,7 @@ async def test_reindex_embeddings_only_full_passes_force_full_to_vector_reindex(
343343 session_maker ,
344344):
345345 app_config = _stub_app_config ()
346- project = SimpleNamespace (id = 1 , name = "foo" , path = "/tmp/foo" )
346+ project = SimpleNamespace (id = 1 , name = "foo" , permalink = "foo" , path = "/tmp/foo" )
347347 printed_lines : list [str ] = []
348348 vector_reindex_calls : list [dict [str , object ]] = []
349349
@@ -443,7 +443,7 @@ async def test_reindex_embeddings_only_warns_when_project_has_no_indexed_entitie
443443):
444444 """Embeddings-only mode explains that it cannot discover project files."""
445445 app_config = _stub_app_config ()
446- project = SimpleNamespace (id = 1 , name = "foo" , path = "/tmp/foo" )
446+ project = SimpleNamespace (id = 1 , name = "foo" , permalink = "foo" , path = "/tmp/foo" )
447447 printed_lines : list [str ] = []
448448
449449 class StubProjectRepository :
@@ -530,8 +530,8 @@ async def test_reindex_recovers_stuck_materializations_before_scan(monkeypatch,
530530 as a missing file. Recovery must re-drive stuck rows before each project scan."""
531531 app_config = _stub_app_config ()
532532 projects = [
533- SimpleNamespace (id = 1 , name = "foo" , path = "/tmp/foo" ),
534- SimpleNamespace (id = 2 , name = "bar" , path = "/tmp/bar" ),
533+ SimpleNamespace (id = 1 , name = "foo" , permalink = "foo" , path = "/tmp/foo" ),
534+ SimpleNamespace (id = 2 , name = "bar" , permalink = "bar" , path = "/tmp/bar" ),
535535 ]
536536 call_order : list [str ] = []
537537
@@ -721,7 +721,7 @@ async def test_reindex_full_does_not_double_embed(monkeypatch, session_maker):
721721 """A full reindex (search + embeddings) must embed once: the FTS rebuild runs
722722 with embeddings=False so only the explicit vector phase calls the provider."""
723723 app_config = _stub_app_config ()
724- project = SimpleNamespace (id = 1 , name = "foo" , path = "/tmp/foo" )
724+ project = SimpleNamespace (id = 1 , name = "foo" , permalink = "foo" , path = "/tmp/foo" )
725725 vector_reindex_calls : list [dict [str , object ]] = []
726726 project_index = AsyncMock (
727727 return_value = SimpleNamespace (
@@ -878,3 +878,77 @@ def test_reindex_embedding_success_reports_index_and_model_and_exits_zero(
878878 ) in output
879879 assert "Representative error:" not in output
880880 assert "Reindex complete!" in output
881+
882+
883+ # --- `bm project add` indexing (#1635) ---
884+
885+
886+ @pytest .mark .asyncio
887+ @pytest .mark .parametrize ("semantic_search_enabled" , [True , False ])
888+ async def test_project_add_indexing_runs_the_reindex_pass_then_reports_readiness (
889+ monkeypatch , semantic_search_enabled : bool
890+ ):
891+ """`project add` reuses the reindex pass, so its embedding phase shows the progress bar.
892+
893+ The old path made one foreground API request that embedded inline and printed
894+ nothing until it returned. Embeddings follow the config here, without the
895+ "Semantic search is not enabled" warning an explicit reindex prints.
896+ """
897+ app_config = _stub_app_config (semantic_search_enabled = semantic_search_enabled )
898+ monkeypatch .setattr (db_cmd , "ConfigManager" , lambda : SimpleNamespace (config = app_config ))
899+ steps : list [tuple [str , object ]] = []
900+
901+ async def fake_reindex (config , ** kwargs ):
902+ assert config is app_config
903+ steps .append (("reindex" , kwargs ))
904+
905+ async def fake_report (project : str ) -> None :
906+ steps .append (("readiness" , project ))
907+
908+ monkeypatch .setattr (db_cmd , "_reindex" , fake_reindex )
909+ monkeypatch .setattr (db_cmd , "report_project_readiness" , fake_report )
910+
911+ await db_cmd .index_project_and_report_readiness ("research" )
912+
913+ assert steps == [
914+ (
915+ "reindex" ,
916+ {
917+ "search" : True ,
918+ "embeddings" : semantic_search_enabled ,
919+ "full" : False ,
920+ "project" : "research" ,
921+ },
922+ ),
923+ ("readiness" , "research" ),
924+ ]
925+
926+
927+ @pytest .mark .asyncio
928+ async def test_reindex_matches_the_project_by_permalink (monkeypatch , session_maker ):
929+ """`new_default` names the project reconciliation stored as `new-default`.
930+
931+ Exact name equality missed it, so `bm project add new_default` reported its
932+ own index pass as "Project 'new_default' not found".
933+ """
934+ stats = _vector_stats (total_entities = 1 , embedded = 1 , skipped = 0 , errors = 0 )
935+ _ , project_index , _ = _configure_embedding_runtime (monkeypatch , session_maker , stats )
936+ stored = SimpleNamespace (id = 1 , name = "new-default" , permalink = "new-default" , path = "/tmp/n" )
937+
938+ class NormalizedProjectRepository :
939+ async def get_active_projects (self , session ):
940+ return [stored ]
941+
942+ monkeypatch .setattr ("basic_memory.repository.ProjectRepository" , NormalizedProjectRepository )
943+
944+ await db_cmd ._reindex (
945+ _stub_app_config (),
946+ search = True ,
947+ embeddings = False ,
948+ full = False ,
949+ project = "new_default" ,
950+ )
951+
952+ project_index .assert_awaited_once ()
953+ [indexed_call ] = project_index .await_args_list
954+ assert indexed_call .args [0 ] is stored
0 commit comments