mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
feat: min_similarity override, cloud promo improvements (#570)
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
from loguru import logger
|
||||
|
||||
from basic_memory import db
|
||||
from basic_memory.config import BasicMemoryConfig, ProjectMode
|
||||
from basic_memory.config import BasicMemoryConfig, DatabaseBackend, ProjectMode
|
||||
from basic_memory.models import Project
|
||||
from basic_memory.repository import (
|
||||
ProjectRepository,
|
||||
@@ -174,9 +174,13 @@ async def initialize_app(
|
||||
Args:
|
||||
app_config: The Basic Memory project configuration
|
||||
"""
|
||||
# Skip initialization in cloud mode - cloud manages its own projects
|
||||
if app_config.cloud_mode_enabled:
|
||||
logger.debug("Skipping initialization in cloud mode - projects managed by cloud")
|
||||
# Trigger: database backend is Postgres (cloud deployment)
|
||||
# Why: cloud deployments manage their own projects and migrations via the cloud platform.
|
||||
# The local MCP server always uses SQLite and needs initialization even when
|
||||
# cloud_mode is enabled (for per-project cloud routing).
|
||||
# Outcome: skip initialization only for actual cloud Postgres deployments.
|
||||
if app_config.database_backend == DatabaseBackend.POSTGRES:
|
||||
logger.info("Skipping local initialization - Postgres backend manages its own schema")
|
||||
return
|
||||
|
||||
logger.info("Initializing app...")
|
||||
@@ -186,7 +190,7 @@ async def initialize_app(
|
||||
# Reconcile projects from config.json with projects table
|
||||
await reconcile_projects_with_config(app_config)
|
||||
|
||||
logger.info("App initialization completed (migration running in background if needed)")
|
||||
logger.info("App initialization completed")
|
||||
|
||||
|
||||
def ensure_initialization(app_config: BasicMemoryConfig) -> None:
|
||||
@@ -195,14 +199,13 @@ def ensure_initialization(app_config: BasicMemoryConfig) -> None:
|
||||
This is a wrapper for the async initialize_app function that can be
|
||||
called from synchronous code like CLI entry points.
|
||||
|
||||
No-op if app_config.cloud_mode == True. Cloud basic memory manages it's own projects
|
||||
No-op if database backend is Postgres (cloud deployment manages its own schema).
|
||||
|
||||
Args:
|
||||
app_config: The Basic Memory project configuration
|
||||
"""
|
||||
# Skip initialization in cloud mode - cloud manages its own projects
|
||||
if app_config.cloud_mode_enabled:
|
||||
logger.debug("Skipping initialization in cloud mode - projects managed by cloud")
|
||||
if app_config.database_backend == DatabaseBackend.POSTGRES:
|
||||
logger.info("Skipping local initialization - Postgres backend manages its own schema")
|
||||
return
|
||||
|
||||
async def _init_and_cleanup():
|
||||
|
||||
@@ -340,7 +340,9 @@ class LinkResolver:
|
||||
if not project:
|
||||
project = await self._project_repository.get_by_name_case_insensitive(identifier)
|
||||
if not project:
|
||||
project = await self._project_repository.get_by_permalink(generate_permalink(identifier))
|
||||
project = await self._project_repository.get_by_permalink(
|
||||
generate_permalink(identifier)
|
||||
)
|
||||
|
||||
if project:
|
||||
self._project_cache_by_identifier[cache_key] = project
|
||||
|
||||
@@ -20,7 +20,13 @@ from basic_memory.schemas import (
|
||||
ProjectStatistics,
|
||||
SystemStatus,
|
||||
)
|
||||
from basic_memory.config import WATCH_STATUS_JSON, ConfigManager, get_project_config, ProjectConfig
|
||||
from basic_memory.config import (
|
||||
WATCH_STATUS_JSON,
|
||||
ConfigManager,
|
||||
ProjectEntry,
|
||||
get_project_config,
|
||||
ProjectConfig,
|
||||
)
|
||||
from basic_memory.utils import generate_permalink
|
||||
|
||||
|
||||
@@ -62,20 +68,20 @@ class ProjectService:
|
||||
return self.config_manager.projects
|
||||
|
||||
@property
|
||||
def default_project(self) -> str:
|
||||
def default_project(self) -> Optional[str]:
|
||||
"""Get the name of the default project.
|
||||
|
||||
Returns:
|
||||
The name of the default project
|
||||
The name of the default project, or None if not set
|
||||
"""
|
||||
return self.config_manager.default_project
|
||||
|
||||
@property
|
||||
def current_project(self) -> str:
|
||||
def current_project(self) -> Optional[str]:
|
||||
"""Get the name of the currently active project.
|
||||
|
||||
Returns:
|
||||
The name of the current project
|
||||
The name of the current project, or None if not set
|
||||
"""
|
||||
return os.environ.get("BASIC_MEMORY_PROJECT", self.config_manager.default_project)
|
||||
|
||||
@@ -340,7 +346,9 @@ class ProjectService:
|
||||
# No default project - set the config default as default
|
||||
# This is defensive code for edge cases where no default exists
|
||||
config_default = self.config_manager.default_project # pragma: no cover
|
||||
config_project = await self.repository.get_by_name(config_default) # pragma: no cover
|
||||
config_project = (
|
||||
await self.repository.get_by_name(config_default) if config_default else None
|
||||
) # pragma: no cover
|
||||
if config_project: # pragma: no cover
|
||||
await self.repository.set_as_default(config_project.id) # pragma: no cover
|
||||
logger.info(
|
||||
@@ -364,11 +372,12 @@ class ProjectService:
|
||||
db_projects_by_permalink = {p.permalink: p for p in db_projects}
|
||||
|
||||
# Get all projects from configuration and normalize names if needed
|
||||
config_projects = self.config_manager.projects.copy()
|
||||
updated_config = {}
|
||||
# Use .config property (not load_config()) so tests can patch ConfigManager.config
|
||||
config = self.config_manager.config
|
||||
updated_config: Dict[str, ProjectEntry] = {}
|
||||
config_updated = False
|
||||
|
||||
for name, path in config_projects.items():
|
||||
for name, entry in config.projects.items():
|
||||
# Generate normalized name (what the database expects)
|
||||
normalized_name = generate_permalink(name)
|
||||
|
||||
@@ -376,25 +385,24 @@ class ProjectService:
|
||||
logger.info(f"Normalizing project name in config: '{name}' -> '{normalized_name}'")
|
||||
config_updated = True
|
||||
|
||||
updated_config[normalized_name] = path
|
||||
updated_config[normalized_name] = entry
|
||||
|
||||
# Update the configuration if any changes were made
|
||||
if config_updated:
|
||||
config = self.config_manager.load_config()
|
||||
config.projects = updated_config
|
||||
self.config_manager.save_config(config)
|
||||
logger.info("Config updated with normalized project names")
|
||||
|
||||
# Use the normalized config for further processing
|
||||
config_projects = updated_config
|
||||
# Use the normalized config for further processing — keys are now project names
|
||||
config_project_names = updated_config
|
||||
|
||||
# Add projects that exist in config but not in DB
|
||||
for name, path in config_projects.items():
|
||||
for name, entry in config_project_names.items():
|
||||
if name not in db_projects_by_permalink:
|
||||
logger.info(f"Adding project '{name}' to database")
|
||||
project_data = {
|
||||
"name": name,
|
||||
"path": path,
|
||||
"path": entry.path,
|
||||
"permalink": generate_permalink(name),
|
||||
"is_active": True,
|
||||
# Don't set is_default here - let the enforcement logic handle it
|
||||
@@ -405,7 +413,7 @@ class ProjectService:
|
||||
# Config is the source of truth - if a project was deleted from config,
|
||||
# it should be deleted from DB too (fixes issue #193)
|
||||
for name, project in db_projects_by_permalink.items():
|
||||
if name not in config_projects:
|
||||
if name not in config_project_names:
|
||||
logger.info(
|
||||
f"Removing project '{name}' from database (deleted from config, source of truth)"
|
||||
)
|
||||
@@ -456,8 +464,8 @@ class ProjectService:
|
||||
|
||||
# Update in configuration
|
||||
config = self.config_manager.load_config()
|
||||
old_path = config.projects[name]
|
||||
config.projects[name] = resolved_path
|
||||
old_path = config.projects[name].path
|
||||
config.projects[name].path = resolved_path
|
||||
self.config_manager.save_config(config)
|
||||
|
||||
# Update in database using robust lookup
|
||||
@@ -468,7 +476,7 @@ class ProjectService:
|
||||
else:
|
||||
logger.error(f"Project '{name}' exists in config but not in database")
|
||||
# Restore the old path in config since DB update failed
|
||||
config.projects[name] = old_path
|
||||
config.projects[name].path = old_path
|
||||
self.config_manager.save_config(config)
|
||||
raise ValueError(f"Project '{name}' not found in database")
|
||||
|
||||
@@ -504,7 +512,7 @@ class ProjectService:
|
||||
|
||||
# Update in config
|
||||
config = self.config_manager.load_config()
|
||||
config.projects[name] = resolved_path
|
||||
config.projects[name].path = resolved_path
|
||||
self.config_manager.save_config(config)
|
||||
|
||||
# Update in database
|
||||
|
||||
@@ -135,6 +135,7 @@ class SearchService:
|
||||
after_date=after_date,
|
||||
metadata_filters=metadata_filters,
|
||||
retrieval_mode=query.retrieval_mode or SearchRetrievalMode.FTS,
|
||||
min_similarity=query.min_similarity,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user