diff --git a/pyproject.toml b/pyproject.toml index a1d199083..3082522cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,6 +97,8 @@ dev = [ "pyright>=1.1.408", "pytest-testmon>=2.2.0", "ty>=0.0.18", + "cst-lsp>=0.1.3", + "libcst>=1.8.6", ] [tool.hatch.version] @@ -115,6 +117,7 @@ ignore = ["test/"] defineConstant = { DEBUG = true } reportMissingImports = "error" reportMissingTypeStubs = false +reportUnusedImport = "none" pythonVersion = "3.12" diff --git a/src/basic_memory/alembic/versions/j3d4e5f6g7h8_rename_entity_type_to_note_type.py b/src/basic_memory/alembic/versions/j3d4e5f6g7h8_rename_entity_type_to_note_type.py new file mode 100644 index 000000000..629f101ff --- /dev/null +++ b/src/basic_memory/alembic/versions/j3d4e5f6g7h8_rename_entity_type_to_note_type.py @@ -0,0 +1,167 @@ +"""Rename entity_type column to note_type + +Revision ID: j3d4e5f6g7h8 +Revises: i2c3d4e5f6g7 +Create Date: 2026-02-22 12:00:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +from sqlalchemy import text + +# revision identifiers, used by Alembic. +revision: str = "j3d4e5f6g7h8" +down_revision: Union[str, None] = "i2c3d4e5f6g7" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def table_exists(connection, table_name: str) -> bool: + """Check if a table exists (idempotent migration support).""" + if connection.dialect.name == "postgresql": + result = connection.execute( + text( + "SELECT 1 FROM information_schema.tables " + "WHERE table_name = :table_name" + ), + {"table_name": table_name}, + ) + return result.fetchone() is not None + # SQLite + result = connection.execute( + text("SELECT 1 FROM sqlite_master WHERE type='table' AND name = :table_name"), + {"table_name": table_name}, + ) + return result.fetchone() is not None + + +def index_exists(connection, index_name: str) -> bool: + """Check if an index exists (idempotent migration support).""" + if connection.dialect.name == "postgresql": + result = connection.execute( + text("SELECT 1 FROM pg_indexes WHERE indexname = :index_name"), + {"index_name": index_name}, + ) + return result.fetchone() is not None + # SQLite + result = connection.execute( + text("SELECT 1 FROM sqlite_master WHERE type='index' AND name = :index_name"), + {"index_name": index_name}, + ) + return result.fetchone() is not None + + +def column_exists(connection, table: str, column: str) -> bool: + """Check if a column exists in a table (idempotent migration support).""" + if connection.dialect.name == "postgresql": + result = connection.execute( + text( + "SELECT 1 FROM information_schema.columns " + "WHERE table_name = :table AND column_name = :column" + ), + {"table": table, "column": column}, + ) + return result.fetchone() is not None + # SQLite + result = connection.execute(text(f"PRAGMA table_info({table})")) + columns = [row[1] for row in result] + return column in columns + + +def upgrade() -> None: + """Rename entity_type → note_type on the entity table.""" + connection = op.get_bind() + dialect = connection.dialect.name + + # Skip if already migrated (idempotent) + if column_exists(connection, "entity", "note_type"): + return + + if dialect == "postgresql": + # Postgres supports direct column rename + op.execute("ALTER TABLE entity RENAME COLUMN entity_type TO note_type") + + # Recreate the index with new name + op.execute("DROP INDEX IF EXISTS ix_entity_type") + op.execute("CREATE INDEX ix_note_type ON entity (note_type)") + else: + # SQLite 3.25.0+ supports ALTER TABLE RENAME COLUMN directly. + # Avoids batch_alter_table which fails on tables with generated columns + # (duplicate column name error when recreating the table). + op.execute("ALTER TABLE entity RENAME COLUMN entity_type TO note_type") + + # Recreate the index with new name + if index_exists(connection, "ix_entity_type"): + op.drop_index("ix_entity_type", table_name="entity") + op.create_index("ix_note_type", "entity", ["note_type"]) + + # Update search index metadata: rename entity_type → note_type in JSON + # This updates the stored metadata so search results use the new field name + # Guard: search_index may not exist on a fresh DB (created by an earlier migration) + if not table_exists(connection, "search_index"): + return + + if dialect == "postgresql": + op.execute( + text(""" + UPDATE search_index + SET metadata = metadata - 'entity_type' || jsonb_build_object('note_type', metadata->'entity_type') + WHERE metadata ? 'entity_type' + """) + ) + else: + op.execute( + text(""" + UPDATE search_index + SET metadata = json_set( + json_remove(metadata, '$.entity_type'), + '$.note_type', + json_extract(metadata, '$.entity_type') + ) + WHERE json_extract(metadata, '$.entity_type') IS NOT NULL + """) + ) + + +def downgrade() -> None: + """Rename note_type → entity_type on the entity table.""" + connection = op.get_bind() + dialect = connection.dialect.name + + if dialect == "postgresql": + op.execute("ALTER TABLE entity RENAME COLUMN note_type TO entity_type") + op.execute("DROP INDEX IF EXISTS ix_note_type") + op.execute("CREATE INDEX ix_entity_type ON entity (entity_type)") + else: + op.execute("ALTER TABLE entity RENAME COLUMN note_type TO entity_type") + + if index_exists(connection, "ix_note_type"): + op.drop_index("ix_note_type", table_name="entity") + op.create_index("ix_entity_type", "entity", ["entity_type"]) + + # Revert search index metadata + if not table_exists(connection, "search_index"): + return + + if dialect == "postgresql": + op.execute( + text(""" + UPDATE search_index + SET metadata = metadata - 'note_type' || jsonb_build_object('entity_type', metadata->'note_type') + WHERE metadata ? 'note_type' + """) + ) + else: + op.execute( + text(""" + UPDATE search_index + SET metadata = json_set( + json_remove(metadata, '$.note_type'), + '$.entity_type', + json_extract(metadata, '$.note_type') + ) + WHERE json_extract(metadata, '$.note_type') IS NOT NULL + """) + ) diff --git a/src/basic_memory/api/v2/routers/knowledge_router.py b/src/basic_memory/api/v2/routers/knowledge_router.py index ecef6f735..6313ada63 100644 --- a/src/basic_memory/api/v2/routers/knowledge_router.py +++ b/src/basic_memory/api/v2/routers/knowledge_router.py @@ -201,7 +201,7 @@ async def create_entity( Created entity with generated external_id (UUID) and file content """ logger.info( - "API v2 request", endpoint="create_entity", entity_type=data.entity_type, title=data.title + "API v2 request", endpoint="create_entity", note_type=data.note_type, title=data.title ) if fast: diff --git a/src/basic_memory/api/v2/routers/resource_router.py b/src/basic_memory/api/v2/routers/resource_router.py index 7fd41d60f..0f27b412e 100644 --- a/src/basic_memory/api/v2/routers/resource_router.py +++ b/src/basic_memory/api/v2/routers/resource_router.py @@ -145,14 +145,14 @@ async def create_resource( # Determine file details file_name = PathLib(data.file_path).name content_type = file_service.content_type(data.file_path) - entity_type = "canvas" if data.file_path.endswith(".canvas") else "file" + note_type = "canvas" if data.file_path.endswith(".canvas") else "file" # Create a new entity model # Explicitly set external_id to ensure NOT NULL constraint is satisfied (fixes #512) entity = EntityModel( external_id=str(uuid.uuid4()), title=file_name, - entity_type=entity_type, + note_type=note_type, content_type=content_type, file_path=data.file_path, checksum=checksum, @@ -253,14 +253,14 @@ async def update_resource( # Determine file details file_name = PathLib(target_file_path).name content_type = file_service.content_type(target_file_path) - entity_type = "canvas" if target_file_path.endswith(".canvas") else "file" + note_type = "canvas" if target_file_path.endswith(".canvas") else "file" # Update entity using internal ID updated_entity = await entity_repository.update( entity.id, { "title": file_name, - "entity_type": entity_type, + "note_type": note_type, "content_type": content_type, "file_path": target_file_path, "checksum": checksum, diff --git a/src/basic_memory/api/v2/routers/schema_router.py b/src/basic_memory/api/v2/routers/schema_router.py index 84cd86da6..3c62f166e 100644 --- a/src/basic_memory/api/v2/routers/schema_router.py +++ b/src/basic_memory/api/v2/routers/schema_router.py @@ -51,7 +51,7 @@ def _entity_relations(entity: Entity) -> list[RelationData]: RelationData( relation_type=rel.relation_type, target_name=rel.to_name, - target_entity_type=rel.to_entity.entity_type if rel.to_entity else None, + target_note_type=rel.to_entity.note_type if rel.to_entity else None, ) for rel in entity.outgoing_relations ] @@ -69,8 +69,8 @@ def _entity_to_note_data(entity: Entity) -> NoteData: def _entity_frontmatter(entity: Entity) -> dict: """Build a frontmatter dict from an entity for schema resolution.""" frontmatter = dict(entity.entity_metadata) if entity.entity_metadata else {} - if entity.entity_type: - frontmatter.setdefault("type", entity.entity_type) + if entity.note_type: + frontmatter.setdefault("type", entity.note_type) return frontmatter @@ -81,7 +81,7 @@ def _entity_frontmatter(entity: Entity) -> dict: async def validate_schema( entity_repository: EntityRepositoryV2ExternalDep, project_id: str = Path(..., description="Project external UUID"), - entity_type: str | None = Query(None, description="Entity type to validate"), + note_type: str | None = Query(None, description="Note type to validate"), identifier: str | None = Query(None, description="Specific note identifier"), ): """Validate notes against their resolved schemas. @@ -95,7 +95,7 @@ async def validate_schema( if identifier: entity = await entity_repository.get_by_permalink(identifier) if not entity: - return ValidationReport(entity_type=entity_type, total_notes=0, results=[]) + return ValidationReport(note_type=note_type, total_notes=0, results=[]) frontmatter = _entity_frontmatter(entity) schema_ref = frontmatter.get("schema") @@ -120,7 +120,7 @@ async def search_fn(query: str) -> list[dict]: results.append(_to_note_validation_response(result)) return ValidationReport( - entity_type=entity_type or entity.entity_type, + note_type=note_type or entity.note_type, total_notes=1, valid_count=1 if (results and results[0].passed) else 0, warning_count=sum(len(r.warnings) for r in results), @@ -128,8 +128,8 @@ async def search_fn(query: str) -> list[dict]: results=results, ) - # --- Batch validation by entity type --- - entities = await _find_by_entity_type(entity_repository, entity_type) if entity_type else [] + # --- Batch validation by note type --- + entities = await _find_by_note_type(entity_repository, note_type) if note_type else [] for entity in entities: frontmatter = _entity_frontmatter(entity) @@ -156,7 +156,7 @@ async def search_fn(query: str) -> list[dict]: valid = sum(1 for r in results if r.passed) return ValidationReport( - entity_type=entity_type, + note_type=note_type, total_notes=len(results), total_entities=len(entities), valid_count=valid, @@ -173,7 +173,7 @@ async def search_fn(query: str) -> list[dict]: async def infer_schema_endpoint( entity_repository: EntityRepositoryV2ExternalDep, project_id: str = Path(..., description="Project external UUID"), - entity_type: str = Query(..., description="Entity type to analyze"), + note_type: str = Query(..., description="Note type to analyze"), threshold: float = Query(0.25, description="Minimum frequency for optional fields"), ): """Infer a schema from existing notes of a given type. @@ -181,13 +181,13 @@ async def infer_schema_endpoint( Examines observation categories and relation types across all notes of the given type. Returns frequency analysis and suggested Picoschema. """ - entities = await _find_by_entity_type(entity_repository, entity_type) + entities = await _find_by_note_type(entity_repository, note_type) notes_data = [_entity_to_note_data(entity) for entity in entities] - result = infer_schema(entity_type, notes_data, optional_threshold=threshold) + result = infer_schema(note_type, notes_data, optional_threshold=threshold) return InferenceReport( - entity_type=result.entity_type, + note_type=result.note_type, notes_analyzed=result.notes_analyzed, field_frequencies=[ FieldFrequencyResponse( @@ -212,10 +212,10 @@ async def infer_schema_endpoint( # --- Drift Detection --- -@router.get("/schema/diff/{entity_type}", response_model=DriftReport) +@router.get("/schema/diff/{note_type}", response_model=DriftReport) async def diff_schema_endpoint( entity_repository: EntityRepositoryV2ExternalDep, - entity_type: str = Path(..., description="Entity type to check for drift"), + note_type: str = Path(..., description="Note type to check for drift"), project_id: str = Path(..., description="Project external UUID"), ): """Show drift between a schema definition and actual note usage. @@ -229,21 +229,21 @@ async def search_fn(query: str) -> list[dict]: entities = await _find_schema_entities(entity_repository, query) return [_entity_frontmatter(e) for e in entities] - # Resolve schema by entity type - schema_frontmatter = {"type": entity_type} + # Resolve schema by note type + schema_frontmatter = {"type": note_type} schema_def = await resolve_schema(schema_frontmatter, search_fn) if not schema_def: - return DriftReport(entity_type=entity_type, schema_found=False) + return DriftReport(note_type=note_type, schema_found=False) # Collect all notes of this type - entities = await _find_by_entity_type(entity_repository, entity_type) + entities = await _find_by_note_type(entity_repository, note_type) notes_data = [_entity_to_note_data(entity) for entity in entities] result = diff_schema(schema_def, notes_data) return DriftReport( - entity_type=entity_type, + note_type=note_type, new_fields=[ DriftFieldResponse( name=f.name, @@ -271,19 +271,19 @@ async def search_fn(query: str) -> list[dict]: # --- Helpers --- -async def _find_by_entity_type( +async def _find_by_note_type( entity_repository: EntityRepositoryV2ExternalDep, - entity_type: str, + note_type: str, ) -> list[Entity]: """Find all entities of a given type using the repository's select pattern.""" - query = entity_repository.select().where(Entity.entity_type == entity_type) + query = entity_repository.select().where(Entity.note_type == note_type) result = await entity_repository.execute_query(query) return list(result.scalars().all()) async def _find_schema_entities( entity_repository: EntityRepositoryV2ExternalDep, - target_entity_type: str, + target_note_type: str, *, allow_reference_match: bool = False, ) -> list[Entity]: @@ -295,11 +295,11 @@ async def _find_schema_entities( 2) Only when allow_reference_match=True and no entity match was found, try exact reference matching by title/permalink (explicit schema references) """ - query = entity_repository.select().where(Entity.entity_type == "schema") + query = entity_repository.select().where(Entity.note_type == "schema") result = await entity_repository.execute_query(query) entities = list(result.scalars().all()) - normalized_target = generate_permalink(target_entity_type) + normalized_target = generate_permalink(target_note_type) entity_matches = [ e diff --git a/src/basic_memory/cli/commands/doctor.py b/src/basic_memory/cli/commands/doctor.py index 57961204b..be383bd3c 100644 --- a/src/basic_memory/cli/commands/doctor.py +++ b/src/basic_memory/cli/commands/doctor.py @@ -61,7 +61,7 @@ async def run_doctor() -> None: api_note = Entity( title=api_note_title, directory="doctor", - entity_type="note", + note_type="note", content_type="text/markdown", content=f"# {api_note_title}\n\n- [note] API to file check", entity_metadata={"tags": ["doctor"]}, diff --git a/src/basic_memory/cli/commands/project.py b/src/basic_memory/cli/commands/project.py index e9e6f6782..06b2066ea 100644 --- a/src/basic_memory/cli/commands/project.py +++ b/src/basic_memory/cli/commands/project.py @@ -775,16 +775,16 @@ def display_project_info( console.print(stats_table) - # Entity types - if info.statistics.entity_types: - entity_types_table = Table(title="Entity Types") - entity_types_table.add_column("Type", style="blue") - entity_types_table.add_column("Count", style="green") + # Note types + if info.statistics.note_types: + note_types_table = Table(title="Note Types") + note_types_table.add_column("Type", style="blue") + note_types_table.add_column("Count", style="green") - for entity_type, count in info.statistics.entity_types.items(): - entity_types_table.add_row(entity_type, str(count)) + for note_type, count in info.statistics.note_types.items(): + note_types_table.add_row(note_type, str(count)) - console.print(entity_types_table) + console.print(note_types_table) # Most connected entities if info.statistics.most_connected_entities: # pragma: no cover @@ -815,7 +815,7 @@ def display_project_info( ) recent_table.add_row( entity["title"], - entity["entity_type"], + entity["note_type"], updated_at.strftime("%Y-%m-%d %H:%M"), ) diff --git a/src/basic_memory/cli/commands/schema.py b/src/basic_memory/cli/commands/schema.py index e57f71a47..876c68859 100644 --- a/src/basic_memory/cli/commands/schema.py +++ b/src/basic_memory/cli/commands/schema.py @@ -47,8 +47,8 @@ def _resolve_project_name(project: Optional[str]) -> Optional[str]: def _render_validate_table(data: dict) -> None: """Render a validation report dict as a Rich table.""" - entity_type = data.get("entity_type") - title_label = entity_type or "all" + note_type = data.get("note_type") + title_label = note_type or "all" table = Table(title=f"Schema Validation: {title_label}") table.add_column("Note", style="cyan") @@ -84,12 +84,12 @@ def _render_validate_table(data: dict) -> None: def _render_infer_table(data: dict) -> None: """Render an inference report dict as a Rich table.""" - entity_type = data.get("entity_type", "") + note_type = data.get("note_type", "") notes_analyzed = data.get("notes_analyzed", 0) suggested_required = data.get("suggested_required", []) suggested_optional = data.get("suggested_optional", []) - console.print(f"\n[bold]Analyzing {notes_analyzed} notes with type: {entity_type}...[/bold]\n") + console.print(f"\n[bold]Analyzing {notes_analyzed} notes with type: {note_type}...[/bold]\n") table = Table(title="Field Frequencies") table.add_column("Field", style="cyan") @@ -126,7 +126,7 @@ def _render_infer_table(data: dict) -> None: def _render_diff_output(data: dict) -> None: """Render a drift report dict as Rich output.""" - entity_type = data.get("entity_type", "") + note_type = data.get("note_type", "") new_fields = data.get("new_fields", []) dropped_fields = data.get("dropped_fields", []) cardinality_changes = data.get("cardinality_changes", []) @@ -134,10 +134,10 @@ def _render_diff_output(data: dict) -> None: has_drift = new_fields or dropped_fields or cardinality_changes if not has_drift: - console.print(f"[green]No drift detected for {entity_type} schema.[/green]") + console.print(f"[green]No drift detected for {note_type} schema.[/green]") return - console.print(f"\n[bold]Schema drift detected for {entity_type}:[/bold]\n") + console.print(f"\n[bold]Schema drift detected for {note_type}:[/bold]\n") if new_fields: console.print("[green]+ New fields (common in notes, not in schema):[/green]") @@ -233,7 +233,7 @@ def validate( @schema_app.command() def infer( - entity_type: Annotated[ + note_type: Annotated[ str, typer.Argument(help="Note type to analyze (e.g., person, meeting)"), ], @@ -268,7 +268,7 @@ def infer( with force_routing(local=local, cloud=cloud): result = run_with_cleanup( mcp_schema_infer( - note_type=entity_type, + note_type=note_type, threshold=threshold, project=project_name, output_format="json", @@ -285,7 +285,7 @@ def infer( # Handle zero notes if result.get("notes_analyzed", 0) == 0: - console.print(f"[yellow]No notes found with type: {entity_type}[/yellow]") + console.print(f"[yellow]No notes found with type: {note_type}[/yellow]") return _render_infer_table(result) @@ -293,7 +293,7 @@ def infer( if save: console.print( f"\n[yellow]--save not yet implemented. " - f"Copy the schema above into schema/{entity_type}.md[/yellow]" + f"Copy the schema above into schema/{note_type}.md[/yellow]" ) except ValueError as e: console.print(f"[red]Error: {e}[/red]") @@ -308,7 +308,7 @@ def infer( @schema_app.command() def diff( - entity_type: Annotated[ + note_type: Annotated[ str, typer.Argument(help="Note type to check for drift"), ], @@ -337,7 +337,7 @@ def diff( with force_routing(local=local, cloud=cloud): result = run_with_cleanup( mcp_schema_diff( - note_type=entity_type, + note_type=note_type, project=project_name, output_format="json", ) diff --git a/src/basic_memory/cli/commands/tool.py b/src/basic_memory/cli/commands/tool.py index 9eae841bc..5071e7155 100644 --- a/src/basic_memory/cli/commands/tool.py +++ b/src/basic_memory/cli/commands/tool.py @@ -493,7 +493,7 @@ def search_notes( page=page, after_date=after_date, page_size=page_size, - types=note_types, + note_types=note_types, entity_types=entity_types, metadata_filters=metadata_filters, tags=tags, @@ -654,7 +654,7 @@ def schema_validate( @tool_app.command("schema-infer") def schema_infer( - entity_type: Annotated[ + note_type: Annotated[ str, typer.Argument(help="Note type to analyze (e.g., person, meeting)"), ], @@ -688,7 +688,7 @@ def schema_infer( with force_routing(local=local, cloud=cloud): result = run_with_cleanup( mcp_schema_infer( - note_type=entity_type, + note_type=note_type, threshold=threshold, project=project, workspace=workspace, @@ -711,7 +711,7 @@ def schema_infer( @tool_app.command("schema-diff") def schema_diff( - entity_type: Annotated[ + note_type: Annotated[ str, typer.Argument(help="Note type to check for drift"), ], @@ -741,7 +741,7 @@ def schema_diff( with force_routing(local=local, cloud=cloud): result = run_with_cleanup( mcp_schema_diff( - note_type=entity_type, + note_type=note_type, project=project, workspace=workspace, output_format="json", diff --git a/src/basic_memory/markdown/entity_parser.py b/src/basic_memory/markdown/entity_parser.py index 99db826a3..c33a64f3f 100644 --- a/src/basic_memory/markdown/entity_parser.py +++ b/src/basic_memory/markdown/entity_parser.py @@ -255,8 +255,8 @@ async def parse_markdown_content( else: metadata["title"] = title - entity_type = metadata.get("type") - metadata["type"] = entity_type if entity_type is not None else "note" + note_type = metadata.get("type") + metadata["type"] = note_type if note_type is not None else "note" tags = parse_tags(metadata.get("tags", [])) # pyright: ignore if tags: diff --git a/src/basic_memory/markdown/utils.py b/src/basic_memory/markdown/utils.py index 29381b2d1..9d7c55526 100644 --- a/src/basic_memory/markdown/utils.py +++ b/src/basic_memory/markdown/utils.py @@ -50,7 +50,7 @@ def entity_model_from_markdown( # Update basic fields model.title = markdown.frontmatter.title - model.entity_type = markdown.frontmatter.type + model.note_type = markdown.frontmatter.type # Only update permalink if it exists in frontmatter, otherwise preserve existing if markdown.frontmatter.permalink is not None: model.permalink = markdown.frontmatter.permalink @@ -86,7 +86,7 @@ async def schema_to_markdown(schema: Any) -> Post: Convert schema to markdown Post object. Args: - schema: Schema to convert (must have title, entity_type, and permalink attributes) + schema: Schema to convert (must have title, note_type, and permalink attributes) Returns: Post object with frontmatter metadata @@ -113,7 +113,7 @@ async def schema_to_markdown(schema: Any) -> Post: post = Post( content, title=schema.title, - type=schema.entity_type, + type=schema.note_type, ) # set the permalink if passed in if schema.permalink: diff --git a/src/basic_memory/mcp/clients/schema.py b/src/basic_memory/mcp/clients/schema.py index bfa9290bd..36f72aaa9 100644 --- a/src/basic_memory/mcp/clients/schema.py +++ b/src/basic_memory/mcp/clients/schema.py @@ -24,7 +24,7 @@ class SchemaClient: Usage: async with get_client() as http_client: client = SchemaClient(http_client, project_id) - report = await client.validate(entity_type="Person") + report = await client.validate(note_type="person") """ def __init__(self, http_client: AsyncClient, project_id: str): @@ -41,13 +41,13 @@ def __init__(self, http_client: AsyncClient, project_id: str): async def validate( self, *, - entity_type: str | None = None, + note_type: str | None = None, identifier: str | None = None, ) -> ValidationReport: """Validate notes against their resolved schemas. Args: - entity_type: Optional entity type to batch-validate + note_type: Optional note type to batch-validate identifier: Optional specific note to validate Returns: @@ -57,8 +57,8 @@ async def validate( ToolError: If the request fails """ params: dict[str, str] = {} - if entity_type: - params["entity_type"] = entity_type + if note_type: + params["note_type"] = note_type if identifier: params["identifier"] = identifier @@ -71,14 +71,14 @@ async def validate( async def infer( self, - entity_type: str, + note_type: str, *, threshold: float = 0.25, ) -> InferenceReport: """Infer a schema from existing notes of a given type. Args: - entity_type: The entity type to analyze + note_type: The note type to analyze threshold: Minimum frequency for optional fields (0-1) Returns: @@ -90,15 +90,15 @@ async def infer( response = await call_post( self.http_client, f"{self._base_path}/infer", - params={"entity_type": entity_type, "threshold": threshold}, + params={"note_type": note_type, "threshold": threshold}, ) return InferenceReport.model_validate(response.json()) - async def diff(self, entity_type: str) -> DriftReport: + async def diff(self, note_type: str) -> DriftReport: """Show drift between schema definition and actual usage. Args: - entity_type: The entity type to check for drift + note_type: The note type to check for drift Returns: DriftReport with detected differences @@ -108,6 +108,6 @@ async def diff(self, entity_type: str) -> DriftReport: """ response = await call_get( self.http_client, - f"{self._base_path}/diff/{entity_type}", + f"{self._base_path}/diff/{note_type}", ) return DriftReport.model_validate(response.json()) diff --git a/src/basic_memory/mcp/tools/schema.py b/src/basic_memory/mcp/tools/schema.py index a266419f0..a5c2f8f85 100644 --- a/src/basic_memory/mcp/tools/schema.py +++ b/src/basic_memory/mcp/tools/schema.py @@ -128,7 +128,7 @@ async def schema_validate( schema_client = SchemaClient(client, active_project.external_id) result = await schema_client.validate( - entity_type=note_type, + note_type=note_type, identifier=identifier, ) @@ -285,7 +285,7 @@ async def schema_infer( f"Error inferring schema for type '{note_type}': {e}\n\n" f"## Troubleshooting\n" f"1. Ensure notes of type '{note_type}' exist in the project\n" - f'2. Try searching: `search_notes("{note_type}", types=["{note_type}"])`\n' + f'2. Try searching: `search_notes("{note_type}", note_types=["{note_type}"])`\n' f"3. Verify the project has been synced: `basic-memory status`\n" ) diff --git a/src/basic_memory/mcp/tools/search.py b/src/basic_memory/mcp/tools/search.py index addb37a41..ea0245faa 100644 --- a/src/basic_memory/mcp/tools/search.py +++ b/src/basic_memory/mcp/tools/search.py @@ -113,7 +113,7 @@ def _format_search_error_response( ## Alternative search strategies: - Break into simpler terms: `search_notes("{project}", "{" ".join(clean_query.split()[:2])}")` - Try different search types: `search_notes("{project}","{clean_query}", search_type="title")` - - Use filtering: `search_notes("{project}","{clean_query}", types=["entity"])` + - Use filtering: `search_notes("{project}","{clean_query}", note_types=["note"])` """).strip() # Project not found errors (check before general "not found") @@ -164,7 +164,7 @@ def _format_search_error_response( - Remove restrictive terms: Focus on the most important keywords 5. **Use filtering to narrow scope**: - - By content type: `search_notes("{project}","{query}", types=["entity"])` + - By content type: `search_notes("{project}","{query}", note_types=["note"])` - By recent content: `search_notes("{project}","{query}", after_date="1 week")` - By entity type: `search_notes("{project}","{query}", entity_types=["observation"])` @@ -233,7 +233,7 @@ def _format_search_error_response( - **Different search types**: - Title only: `search_notes("{project}","{query}", search_type="title")` - Permalink patterns: `search_notes("{project}","{query}*", search_type="permalink")` -- **With filters**: `search_notes("{project}","{query}", types=["entity"])` +- **With filters**: `search_notes("{project}","{query}", note_types=["note"])` - **Recent content**: `search_notes("{project}","{query}", after_date="1 week")` - **Boolean variations**: `search_notes("{project}","{" OR ".join(query.split()[:2])}")` @@ -264,7 +264,7 @@ async def search_notes( page_size: int = 10, search_type: str | None = None, output_format: Literal["text", "json"] = "text", - types: List[str] | None = None, + note_types: List[str] | None = None, entity_types: List[str] | None = None, after_date: Optional[str] = None, metadata_filters: Optional[Dict[str, Any]] = None, @@ -309,8 +309,8 @@ async def search_notes( text when disabled) ### Filtering Options - - `search_notes("my-project", "query", types=["entity"])` - Search only entities - - `search_notes("work-docs", "query", types=["note", "person"])` - Multiple content types + - `search_notes("my-project", "query", note_types=["note"])` - Search only notes + - `search_notes("work-docs", "query", note_types=["note", "person"])` - Multiple note types - `search_notes("research", "query", entity_types=["observation"])` - Filter by entity type - `search_notes("team-docs", "query", after_date="2024-01-01")` - Recent content only - `search_notes("my-project", "query", after_date="1 week")` - Relative date filtering @@ -353,7 +353,7 @@ async def search_notes( Default is dynamic: "hybrid" when semantic search is enabled, otherwise "text". output_format: "text" preserves existing structured search response behavior. "json" returns a machine-readable dictionary payload. - types: Optional list of note types to search (e.g., ["note", "person"]) + note_types: Optional list of note types to search (e.g., ["note", "person"]) entity_types: Optional list of entity types to filter by (e.g., ["entity", "observation"]) after_date: Optional date filter for recent content (e.g., "1 week", "2d", "2024-01-01") metadata_filters: Optional structured frontmatter filters (e.g., {"status": "in-progress"}) @@ -387,10 +387,10 @@ async def search_notes( # Exact phrase search results = await search_notes("\"weekly standup meeting\"") - # Search with type filter + # Search with note type filter results = await search_notes( "meeting notes", - types=["entity"], + note_types=["note"], ) # Search with entity type filter @@ -420,7 +420,7 @@ async def search_notes( # Complex search with multiple filters results = await search_notes( "(bug OR issue) AND NOT resolved", - types=["entity"], + note_types=["note"], after_date="2024-01-01" ) @@ -428,7 +428,7 @@ async def search_notes( results = await search_notes("project planning", project="my-project") """ # Avoid mutable-default-argument footguns. Treat None as "no filter". - types = types or [] + note_types = note_types or [] entity_types = entity_types or [] # Detect project from memory URL prefix before routing @@ -477,8 +477,8 @@ async def search_notes( # Add optional filters if provided (empty lists are treated as no filter) if entity_types: search_query.entity_types = [SearchItemType(t) for t in entity_types] - if types: - search_query.types = types + if note_types: + search_query.note_types = note_types if after_date: search_query.after_date = after_date if metadata_filters: diff --git a/src/basic_memory/mcp/tools/ui_sdk.py b/src/basic_memory/mcp/tools/ui_sdk.py index 852c69d50..50fdb1c50 100644 --- a/src/basic_memory/mcp/tools/ui_sdk.py +++ b/src/basic_memory/mcp/tools/ui_sdk.py @@ -28,7 +28,7 @@ async def search_notes_ui( page: int = 1, page_size: int = 10, search_type: Optional[str] = None, - types: List[str] | None = None, + note_types: List[str] | None = None, entity_types: List[str] | None = None, after_date: Optional[str] = None, metadata_filters: Optional[Dict[str, Any]] = None, @@ -44,7 +44,7 @@ async def search_notes_ui( page_size=page_size, search_type=search_type, output_format="json", - types=types, + note_types=note_types, entity_types=entity_types, after_date=after_date, metadata_filters=metadata_filters, diff --git a/src/basic_memory/mcp/tools/write_note.py b/src/basic_memory/mcp/tools/write_note.py index c617796c6..799a2661c 100644 --- a/src/basic_memory/mcp/tools/write_note.py +++ b/src/basic_memory/mcp/tools/write_note.py @@ -174,7 +174,7 @@ async def write_note( entity = Entity( title=title, directory=directory, - entity_type=note_type, + note_type=note_type, content_type="text/markdown", content=content, entity_metadata=entity_metadata or None, diff --git a/src/basic_memory/models/knowledge.py b/src/basic_memory/models/knowledge.py index 03013ba81..c55dc02b3 100644 --- a/src/basic_memory/models/knowledge.py +++ b/src/basic_memory/models/knowledge.py @@ -37,7 +37,7 @@ class Entity(Base): __tablename__ = "entity" __table_args__ = ( # Regular indexes - Index("ix_entity_type", "entity_type"), + Index("ix_note_type", "note_type"), Index("ix_entity_title", "title"), Index("ix_entity_external_id", "external_id", unique=True), Index("ix_entity_created_at", "created_at"), # For timeline queries @@ -64,7 +64,7 @@ class Entity(Base): # External UUID for API references - stable identifier that won't change external_id: Mapped[str] = mapped_column(String, unique=True, default=lambda: str(uuid.uuid4())) title: Mapped[str] = mapped_column(String) - entity_type: Mapped[str] = mapped_column(String) + note_type: Mapped[str] = mapped_column(String) entity_metadata: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) content_type: Mapped[str] = mapped_column(String) @@ -133,7 +133,7 @@ def __getattribute__(self, name): return value def __repr__(self) -> str: - return f"Entity(id={self.id}, external_id='{self.external_id}', name='{self.title}', type='{self.entity_type}', checksum='{self.checksum}')" + return f"Entity(id={self.id}, external_id='{self.external_id}', name='{self.title}', type='{self.note_type}', checksum='{self.checksum}')" class Observation(Base): diff --git a/src/basic_memory/repository/postgres_search_repository.py b/src/basic_memory/repository/postgres_search_repository.py index 31e6b7487..e91a84841 100644 --- a/src/basic_memory/repository/postgres_search_repository.py +++ b/src/basic_memory/repository/postgres_search_repository.py @@ -603,7 +603,7 @@ async def search( permalink: Optional[str] = None, permalink_match: Optional[str] = None, title: Optional[str] = None, - types: Optional[List[str]] = None, + note_types: Optional[List[str]] = None, after_date: Optional[datetime] = None, search_item_types: Optional[List[SearchItemType]] = None, metadata_filters: Optional[dict] = None, @@ -619,7 +619,7 @@ async def search( permalink=permalink, permalink_match=permalink_match, title=title, - types=types, + note_types=note_types, after_date=after_date, search_item_types=search_item_types, metadata_filters=metadata_filters, @@ -682,14 +682,14 @@ async def search( type_list = ", ".join(f"'{t.value}'" for t in search_item_types) conditions.append(f"search_index.type IN ({type_list})") - # Handle entity type filter using JSONB containment - if types: + # Handle note type filter using JSONB containment (frontmatter type field) + if note_types: # Use JSONB @> operator for efficient containment queries type_conditions = [] - for entity_type in types: - # Create JSONB containment condition for each type + for note_type in note_types: + # Create JSONB containment condition for each note type type_conditions.append( - f'search_index.metadata @> \'{{"entity_type": "{entity_type}"}}\'' + f'search_index.metadata @> \'{{"note_type": "{note_type}"}}\'' ) conditions.append(f"({' OR '.join(type_conditions)})") diff --git a/src/basic_memory/repository/search_repository.py b/src/basic_memory/repository/search_repository.py index 5a1214f44..b520e1713 100644 --- a/src/basic_memory/repository/search_repository.py +++ b/src/basic_memory/repository/search_repository.py @@ -37,7 +37,7 @@ async def search( permalink: Optional[str] = None, permalink_match: Optional[str] = None, title: Optional[str] = None, - types: Optional[List[str]] = None, + note_types: Optional[List[str]] = None, after_date: Optional[datetime] = None, search_item_types: Optional[List[SearchItemType]] = None, metadata_filters: Optional[dict] = None, diff --git a/src/basic_memory/repository/search_repository_base.py b/src/basic_memory/repository/search_repository_base.py index 5a394ff3b..8109df9c6 100644 --- a/src/basic_memory/repository/search_repository_base.py +++ b/src/basic_memory/repository/search_repository_base.py @@ -108,7 +108,7 @@ async def search( permalink: Optional[str] = None, permalink_match: Optional[str] = None, title: Optional[str] = None, - types: Optional[List[str]] = None, + note_types: Optional[List[str]] = None, after_date: Optional[datetime] = None, search_item_types: Optional[List[SearchItemType]] = None, metadata_filters: Optional[Dict[str, Any]] = None, @@ -124,7 +124,7 @@ async def search( permalink: Exact permalink match permalink_match: Permalink pattern match (supports *) title: Title search - types: Filter by entity types (from metadata.entity_type) + note_types: Filter by note types (from metadata.note_type) after_date: Filter by created_at > after_date search_item_types: Filter by SearchItemType (ENTITY, OBSERVATION, RELATION) metadata_filters: Structured frontmatter metadata filters @@ -761,7 +761,7 @@ async def _dispatch_retrieval_mode( permalink: Optional[str], permalink_match: Optional[str], title: Optional[str], - types: Optional[List[str]], + note_types: Optional[List[str]], after_date: Optional[datetime], search_item_types: Optional[List[SearchItemType]], metadata_filters: Optional[dict], @@ -794,7 +794,7 @@ async def _dispatch_retrieval_mode( permalink=permalink, permalink_match=permalink_match, title=title, - types=types, + note_types=note_types, after_date=after_date, search_item_types=search_item_types, metadata_filters=metadata_filters, @@ -813,7 +813,7 @@ async def _dispatch_retrieval_mode( permalink=permalink, permalink_match=permalink_match, title=title, - types=types, + note_types=note_types, after_date=after_date, search_item_types=search_item_types, metadata_filters=metadata_filters, @@ -842,7 +842,7 @@ async def _search_vector_only( permalink: Optional[str], permalink_match: Optional[str], title: Optional[str], - types: Optional[List[str]], + note_types: Optional[List[str]], after_date: Optional[datetime], search_item_types: Optional[List[SearchItemType]], metadata_filters: Optional[dict], @@ -911,7 +911,7 @@ async def _search_vector_only( permalink, permalink_match, title, - types, + note_types, after_date, search_item_types, metadata_filters, @@ -924,7 +924,7 @@ async def _search_vector_only( permalink=permalink, permalink_match=permalink_match, title=title, - types=types, + note_types=note_types, after_date=after_date, search_item_types=search_item_types, metadata_filters=metadata_filters, @@ -1054,7 +1054,7 @@ async def _search_hybrid( permalink: Optional[str], permalink_match: Optional[str], title: Optional[str], - types: Optional[List[str]], + note_types: Optional[List[str]], after_date: Optional[datetime], search_item_types: Optional[List[SearchItemType]], metadata_filters: Optional[dict], @@ -1074,7 +1074,7 @@ async def _search_hybrid( permalink=permalink, permalink_match=permalink_match, title=title, - types=types, + note_types=note_types, after_date=after_date, search_item_types=search_item_types, metadata_filters=metadata_filters, @@ -1087,7 +1087,7 @@ async def _search_hybrid( permalink=permalink, permalink_match=permalink_match, title=title, - types=types, + note_types=note_types, after_date=after_date, search_item_types=search_item_types, metadata_filters=metadata_filters, diff --git a/src/basic_memory/repository/sqlite_search_repository.py b/src/basic_memory/repository/sqlite_search_repository.py index 0afe59697..11aa6e441 100644 --- a/src/basic_memory/repository/sqlite_search_repository.py +++ b/src/basic_memory/repository/sqlite_search_repository.py @@ -588,7 +588,7 @@ async def search( permalink: Optional[str] = None, permalink_match: Optional[str] = None, title: Optional[str] = None, - types: Optional[List[str]] = None, + note_types: Optional[List[str]] = None, after_date: Optional[datetime] = None, search_item_types: Optional[List[SearchItemType]] = None, metadata_filters: Optional[dict] = None, @@ -604,7 +604,7 @@ async def search( permalink=permalink, permalink_match=permalink_match, title=title, - types=types, + note_types=note_types, after_date=after_date, search_item_types=search_item_types, metadata_filters=metadata_filters, @@ -671,11 +671,11 @@ async def search( type_list = ", ".join(f"'{t.value}'" for t in search_item_types) conditions.append(f"search_index.type IN ({type_list})") - # Handle type filter - if types: - type_list = ", ".join(f"'{t}'" for t in types) + # Handle note type filter (frontmatter type field) + if note_types: + type_list = ", ".join(f"'{t}'" for t in note_types) conditions.append( - f"json_extract(search_index.metadata, '$.entity_type') IN ({type_list})" + f"json_extract(search_index.metadata, '$.note_type') IN ({type_list})" ) # Handle date filter using datetime() for proper comparison diff --git a/src/basic_memory/schema/inference.py b/src/basic_memory/schema/inference.py index 1bf56b31c..d458aa3cf 100644 --- a/src/basic_memory/schema/inference.py +++ b/src/basic_memory/schema/inference.py @@ -30,14 +30,14 @@ class FieldFrequency: percentage: float sample_values: list[str] = field(default_factory=list) is_array: bool = False # True if typically appears multiple times per note - target_type: str | None = None # For relations, the most common target entity type + target_type: str | None = None # For relations, the most common target note type @dataclass class InferenceResult: """Complete inference result with frequency analysis and suggested schema.""" - entity_type: str + note_type: str notes_analyzed: int field_frequencies: list[FieldFrequency] suggested_schema: dict # Ready-to-use Picoschema YAML dict @@ -65,7 +65,7 @@ class RelationData: relation_type: str target_name: str - target_entity_type: str | None = None + target_note_type: str | None = None @dataclass @@ -85,7 +85,7 @@ class NoteData: def infer_schema( - entity_type: str, + note_type: str, notes: list[NoteData], required_threshold: float = 0.95, optional_threshold: float = 0.25, @@ -98,7 +98,7 @@ def infer_schema( appear less frequently become optional. Args: - entity_type: The entity type being analyzed (e.g., "Person"). + note_type: The note type being analyzed (e.g., "person"). notes: List of NoteData objects to analyze. required_threshold: Frequency at or above which a field is required (default 0.95). optional_threshold: Frequency at or above which a field is optional (default 0.25). @@ -110,7 +110,7 @@ def infer_schema( total = len(notes) if total == 0: return InferenceResult( - entity_type=entity_type, + note_type=note_type, notes_analyzed=0, field_frequencies=[], suggested_schema={}, @@ -145,7 +145,7 @@ def infer_schema( ) return InferenceResult( - entity_type=entity_type, + note_type=note_type, notes_analyzed=total, field_frequencies=all_frequencies, suggested_schema=suggested_schema, @@ -255,8 +255,8 @@ def analyze_relations( # Track target entity types from individual relations (not the source note) target_counter = rel_target_types.setdefault(rel_type, Counter()) for rel in note_rel_objects[rel_type]: - if rel.target_entity_type: - target_counter[rel.target_entity_type] += 1 + if rel.target_note_type: + target_counter[rel.target_note_type] += 1 frequencies: list[FieldFrequency] = [] for rel_type, count in rel_note_count.most_common(): diff --git a/src/basic_memory/schemas/__init__.py b/src/basic_memory/schemas/__init__.py index 0717d72c8..6d4feeccc 100644 --- a/src/basic_memory/schemas/__init__.py +++ b/src/basic_memory/schemas/__init__.py @@ -8,7 +8,7 @@ # Base types and models from basic_memory.schemas.base import ( Observation, - EntityType, + NoteType, RelationType, Relation, Entity, @@ -56,7 +56,7 @@ __all__ = [ # Base "Observation", - "EntityType", + "NoteType", "RelationType", "Relation", "Entity", diff --git a/src/basic_memory/schemas/base.py b/src/basic_memory/schemas/base.py index b4e38e01f..6ba6f5f00 100644 --- a/src/basic_memory/schemas/base.py +++ b/src/basic_memory/schemas/base.py @@ -156,8 +156,8 @@ def validate_timeframe(timeframe: str) -> str: """Unique identifier in format '{path}/{normalized_name}'.""" -EntityType = Annotated[str, BeforeValidator(to_snake_case), MinLen(1), MaxLen(200)] -"""Classification of entity (e.g., 'person', 'project', 'concept'). """ +NoteType = Annotated[str, BeforeValidator(to_snake_case), MinLen(1), MaxLen(200)] +"""Classification of note (e.g., 'note', 'person', 'spec', 'schema'). """ ALLOWED_CONTENT_TYPES = { "text/markdown", @@ -228,7 +228,7 @@ class Entity(BaseModel): title: str content: Optional[str] = None directory: str - entity_type: EntityType = "note" + note_type: NoteType = "note" entity_metadata: Optional[Dict] = Field(default=None, description="Optional metadata") content_type: ContentType = Field( description="MIME type of the content (e.g. text/markdown, image/jpeg)", diff --git a/src/basic_memory/schemas/directory.py b/src/basic_memory/schemas/directory.py index 389c88350..5760f6c22 100644 --- a/src/basic_memory/schemas/directory.py +++ b/src/basic_memory/schemas/directory.py @@ -18,7 +18,7 @@ class DirectoryNode(BaseModel): permalink: Optional[str] = None external_id: Optional[str] = None # UUID (primary API identifier for v2) entity_id: Optional[int] = None # Internal numeric ID - entity_type: Optional[str] = None + note_type: Optional[str] = None content_type: Optional[str] = None updated_at: Optional[datetime] = None diff --git a/src/basic_memory/schemas/project_info.py b/src/basic_memory/schemas/project_info.py index c1dd18f29..80dd81228 100644 --- a/src/basic_memory/schemas/project_info.py +++ b/src/basic_memory/schemas/project_info.py @@ -22,8 +22,8 @@ class ProjectStatistics(BaseModel): ) # Entity counts by type - entity_types: Dict[str, int] = Field( - description="Count of entities by type (e.g., note, conversation)" + note_types: Dict[str, int] = Field( + description="Count of entities by note type (e.g., note, conversation)" ) # Observation counts by category diff --git a/src/basic_memory/schemas/response.py b/src/basic_memory/schemas/response.py index c5b302375..2c7c64d45 100644 --- a/src/basic_memory/schemas/response.py +++ b/src/basic_memory/schemas/response.py @@ -16,7 +16,7 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator -from basic_memory.schemas.base import Relation, Permalink, EntityType, ContentType, Observation +from basic_memory.schemas.base import Relation, Permalink, NoteType, ContentType, Observation class SQLAlchemyModel(BaseModel): @@ -162,7 +162,7 @@ class EntityResponse(SQLAlchemyModel): { "permalink": "component/memory-service", "file_path": "MemoryService", - "entity_type": "component", + "note_type": "component", "entity_metadata": {} "content_type: "text/markdown" "observations": [ @@ -191,7 +191,7 @@ class EntityResponse(SQLAlchemyModel): permalink: Optional[Permalink] title: str file_path: str - entity_type: EntityType + note_type: NoteType entity_metadata: Optional[Dict] = None checksum: Optional[str] = None content_type: ContentType @@ -215,7 +215,7 @@ class EntityListResponse(SQLAlchemyModel): { "permalink": "component/search_service", "title": "SearchService", - "entity_type": "component", + "note_type": "component", "description": "Knowledge graph search", "observations": [ { @@ -227,7 +227,7 @@ class EntityListResponse(SQLAlchemyModel): { "permalink": "document/api_docs", "title": "API_Documentation", - "entity_type": "document", + "note_type": "document", "description": "API Reference", "observations": [ { @@ -255,7 +255,7 @@ class SearchNodesResponse(SQLAlchemyModel): { "permalink": "component/memory-service", "title": "MemoryService", - "entity_type": "component", + "note_type": "component", "description": "Core service", "observations": [...], "relations": [...] diff --git a/src/basic_memory/schemas/schema.py b/src/basic_memory/schemas/schema.py index bb94a2ac6..0194d437a 100644 --- a/src/basic_memory/schemas/schema.py +++ b/src/basic_memory/schemas/schema.py @@ -45,7 +45,7 @@ class NoteValidationResponse(BaseModel): class ValidationReport(BaseModel): """Full validation report for one or more notes.""" - entity_type: str | None = None + note_type: str | None = None total_notes: int = 0 total_entities: int = 0 valid_count: int = 0 @@ -72,14 +72,14 @@ class FieldFrequencyResponse(BaseModel): ) target_type: str | None = Field( default=None, - description="For relations, the most common target entity type", + description="For relations, the most common target note type", ) class InferenceReport(BaseModel): """Inference result with suggested schema definition.""" - entity_type: str + note_type: str notes_analyzed: int field_frequencies: list[FieldFrequencyResponse] = Field(default_factory=list) suggested_schema: dict = Field( @@ -110,7 +110,7 @@ class DriftFieldResponse(BaseModel): class DriftReport(BaseModel): """Schema drift analysis comparing schema definition to actual usage.""" - entity_type: str + note_type: str schema_found: bool = Field( default=True, description="Whether a schema was found for this type", diff --git a/src/basic_memory/schemas/search.py b/src/basic_memory/schemas/search.py index a20945e3b..e26329ff3 100644 --- a/src/basic_memory/schemas/search.py +++ b/src/basic_memory/schemas/search.py @@ -40,7 +40,7 @@ class SearchQuery(BaseModel): - title: Title only search Optionally filter results by: - - types: Limit to specific entity types (frontmatter "type") + - note_types: Limit to specific note types (frontmatter "type") - entity_types: Limit to search item types (entity/observation/relation) - after_date: Only items after date - metadata_filters: Structured frontmatter filters (field -> value) @@ -61,7 +61,7 @@ class SearchQuery(BaseModel): title: Optional[str] = None # title only search # Optional filters - types: Optional[List[str]] = None # Filter by type + note_types: Optional[List[str]] = None # Filter by note type (frontmatter "type") entity_types: Optional[List[SearchItemType]] = None # Filter by entity type after_date: Optional[Union[datetime, str]] = None # Time-based filter metadata_filters: Optional[dict[str, Any]] = None # Structured frontmatter filters @@ -83,7 +83,7 @@ def no_criteria(self) -> bool: metadata_is_empty = not self.metadata_filters tags_is_empty = not self.tags status_is_empty = self.status is None or (isinstance(self.status, str) and not self.status) - types_is_empty = not self.types + note_types_is_empty = not self.note_types entity_types_is_empty = not self.entity_types return ( self.permalink is None @@ -91,7 +91,7 @@ def no_criteria(self) -> bool: and self.title is None and text_is_empty and self.after_date is None - and types_is_empty + and note_types_is_empty and entity_types_is_empty and metadata_is_empty and tags_is_empty diff --git a/src/basic_memory/schemas/v2/entity.py b/src/basic_memory/schemas/v2/entity.py index 86b497df5..a51d07b6f 100644 --- a/src/basic_memory/schemas/v2/entity.py +++ b/src/basic_memory/schemas/v2/entity.py @@ -118,7 +118,7 @@ class EntityResponseV2(BaseModel): # Core entity fields title: str = Field(..., description="Entity title") - entity_type: str = Field(..., description="Entity type") + note_type: str = Field(..., description="Note type (from frontmatter 'type' field)") content_type: str = Field(default="text/markdown", description="Content MIME type") # Secondary identifiers (for compatibility and convenience) diff --git a/src/basic_memory/services/directory_service.py b/src/basic_memory/services/directory_service.py index 45cdd628e..2ebb77106 100644 --- a/src/basic_memory/services/directory_service.py +++ b/src/basic_memory/services/directory_service.py @@ -89,7 +89,7 @@ async def get_directory_tree(self) -> DirectoryNode: permalink=file.permalink, external_id=file.external_id, # UUID for v2 API entity_id=file.id, - entity_type=file.entity_type, + note_type=file.note_type, content_type=file.content_type, updated_at=_mtime_to_datetime(file), ) @@ -254,7 +254,7 @@ def _build_directory_tree_from_entities( permalink=file.permalink, external_id=file.external_id, # UUID for v2 API entity_id=file.id, - entity_type=file.entity_type, + note_type=file.note_type, content_type=file.content_type, updated_at=_mtime_to_datetime(file), ) diff --git a/src/basic_memory/services/entity_service.py b/src/basic_memory/services/entity_service.py index e7c4f4e00..745cb104f 100644 --- a/src/basic_memory/services/entity_service.py +++ b/src/basic_memory/services/entity_service.py @@ -206,14 +206,14 @@ async def _get_project_permalink(self) -> Optional[str]: return self._project_permalink def _build_frontmatter_markdown( - self, title: str, entity_type: str, permalink: str + self, title: str, note_type: str, permalink: str ) -> EntityMarkdown: """Build a minimal EntityMarkdown object for permalink resolution.""" from basic_memory.markdown.schemas import EntityFrontmatter frontmatter_metadata = { "title": title, - "type": entity_type, + "type": note_type, "permalink": permalink, } frontmatter_obj = EntityFrontmatter(metadata=frontmatter_metadata) @@ -257,18 +257,18 @@ async def create_entity(self, schema: EntitySchema) -> EntityModel: f"file for entity {schema.directory}/{schema.title} already exists: {file_path}" ) - # Parse content frontmatter to check for user-specified permalink and entity_type + # Parse content frontmatter to check for user-specified permalink and note_type content_markdown = None if schema.content and has_frontmatter(schema.content): content_frontmatter = parse_frontmatter(schema.content) - # If content has entity_type/type, use it to override the schema entity_type + # If content has type, use it to override the schema note_type if "type" in content_frontmatter: - schema.entity_type = content_frontmatter["type"] + schema.note_type = content_frontmatter["type"] if "permalink" in content_frontmatter: content_markdown = self._build_frontmatter_markdown( - schema.title, schema.entity_type, content_frontmatter["permalink"] + schema.title, schema.note_type, content_frontmatter["permalink"] ) # Get unique permalink (prioritizing content frontmatter) unless disabled @@ -315,18 +315,18 @@ async def update_entity(self, entity: EntityModel, schema: EntitySchema) -> Enti content=existing_content, ) - # Parse content frontmatter to check for user-specified permalink and entity_type + # Parse content frontmatter to check for user-specified permalink and note_type content_markdown = None if schema.content and has_frontmatter(schema.content): content_frontmatter = parse_frontmatter(schema.content) - # If content has entity_type/type, use it to override the schema entity_type + # If content has type, use it to override the schema note_type if "type" in content_frontmatter: - schema.entity_type = content_frontmatter["type"] + schema.note_type = content_frontmatter["type"] if "permalink" in content_frontmatter: content_markdown = self._build_frontmatter_markdown( - schema.title, schema.entity_type, content_frontmatter["permalink"] + schema.title, schema.note_type, content_frontmatter["permalink"] ) # Check if we need to update the permalink based on content frontmatter (unless disabled) @@ -406,11 +406,11 @@ async def fast_write_entity( content_frontmatter = parse_frontmatter(schema.content) if "type" in content_frontmatter: - schema.entity_type = content_frontmatter["type"] + schema.note_type = content_frontmatter["type"] if "permalink" in content_frontmatter: content_markdown = self._build_frontmatter_markdown( - schema.title, schema.entity_type, content_frontmatter["permalink"] + schema.title, schema.note_type, content_frontmatter["permalink"] ) # --- Permalink Resolution --- @@ -436,7 +436,7 @@ async def fast_write_entity( entity_metadata = {k: v for k, v in metadata.items() if v is not None} update_data = { "title": schema.title, - "entity_type": schema.entity_type, + "note_type": schema.note_type, "file_path": file_path.as_posix(), "content_type": schema.content_type, "entity_metadata": entity_metadata or None, @@ -488,12 +488,12 @@ async def fast_edit_entity( if "title" in content_frontmatter: update_data["title"] = content_frontmatter["title"] if "type" in content_frontmatter: - update_data["entity_type"] = content_frontmatter["type"] + update_data["note_type"] = content_frontmatter["type"] if "permalink" in content_frontmatter: content_markdown = self._build_frontmatter_markdown( update_data.get("title", entity.title), - update_data.get("entity_type", entity.entity_type), + update_data.get("note_type", entity.note_type), content_frontmatter["permalink"], ) diff --git a/src/basic_memory/services/project_service.py b/src/basic_memory/services/project_service.py index 93d4fb99a..36d072cbb 100644 --- a/src/basic_memory/services/project_service.py +++ b/src/basic_memory/services/project_service.py @@ -692,14 +692,14 @@ async def get_statistics(self, project_id: int) -> ProjectStatistics: ) total_unresolved = unresolved_count_result.scalar() or 0 - # Get entity counts by type - entity_types_result = await self.repository.execute_query( + # Get entity counts by note type + note_types_result = await self.repository.execute_query( text( - "SELECT entity_type, COUNT(*) FROM entity WHERE project_id = :project_id GROUP BY entity_type" + "SELECT note_type, COUNT(*) FROM entity WHERE project_id = :project_id GROUP BY note_type" ), {"project_id": project_id}, ) - entity_types = {row[0]: row[1] for row in entity_types_result.fetchall()} + note_types = {row[0]: row[1] for row in note_types_result.fetchall()} # Get observation counts by category category_result = await self.repository.execute_query( @@ -761,7 +761,7 @@ async def get_statistics(self, project_id: int) -> ProjectStatistics: total_observations=total_observations, total_relations=total_relations, total_unresolved_relations=total_unresolved, - entity_types=entity_types, + note_types=note_types, observation_categories=observation_categories, relation_types=relation_types, most_connected_entities=most_connected, @@ -780,7 +780,7 @@ async def get_activity_metrics(self, project_id: int) -> ActivityMetrics: # Get recently created entities (project filtered) created_result = await self.repository.execute_query( text(""" - SELECT id, title, permalink, entity_type, created_at, file_path + SELECT id, title, permalink, note_type, created_at, file_path FROM entity WHERE project_id = :project_id ORDER BY created_at DESC @@ -793,7 +793,7 @@ async def get_activity_metrics(self, project_id: int) -> ActivityMetrics: "id": row[0], "title": row[1], "permalink": row[2], - "entity_type": row[3], + "note_type": row[3], "created_at": row[4], "file_path": row[5], } @@ -803,7 +803,7 @@ async def get_activity_metrics(self, project_id: int) -> ActivityMetrics: # Get recently updated entities (project filtered) updated_result = await self.repository.execute_query( text(""" - SELECT id, title, permalink, entity_type, updated_at, file_path + SELECT id, title, permalink, note_type, updated_at, file_path FROM entity WHERE project_id = :project_id ORDER BY updated_at DESC @@ -816,7 +816,7 @@ async def get_activity_metrics(self, project_id: int) -> ActivityMetrics: "id": row[0], "title": row[1], "permalink": row[2], - "entity_type": row[3], + "note_type": row[3], "updated_at": row[4], "file_path": row[5], } diff --git a/src/basic_memory/services/search_service.py b/src/basic_memory/services/search_service.py index b9f1937fb..97d4d4db2 100644 --- a/src/basic_memory/services/search_service.py +++ b/src/basic_memory/services/search_service.py @@ -178,7 +178,7 @@ async def search(self, query: SearchQuery, limit=10, offset=0) -> List[SearchInd permalink=query.permalink, permalink_match=query.permalink_match, title=query.title, - types=query.types, + note_types=query.note_types, search_item_types=query.entity_types, after_date=after_date, metadata_filters=metadata_filters, @@ -210,7 +210,7 @@ async def search(self, query: SearchQuery, limit=10, offset=0) -> List[SearchInd permalink=query.permalink, permalink_match=query.permalink_match, title=query.title, - types=query.types, + note_types=query.note_types, search_item_types=query.entity_types, after_date=after_date, metadata_filters=metadata_filters, @@ -415,7 +415,7 @@ async def index_entity_file( permalink=entity.permalink, # Required for Postgres NOT NULL constraint file_path=entity.file_path, metadata={ - "entity_type": entity.entity_type, + "note_type": entity.note_type, }, created_at=entity.created_at, updated_at=_mtime_to_datetime(entity), @@ -500,7 +500,7 @@ async def index_entity_markdown( file_path=entity.file_path, entity_id=entity.id, metadata={ - "entity_type": entity.entity_type, + "note_type": entity.note_type, }, created_at=entity.created_at, updated_at=_mtime_to_datetime(entity), diff --git a/src/basic_memory/sync/sync_service.py b/src/basic_memory/sync/sync_service.py index faa759829..bf7cd734d 100644 --- a/src/basic_memory/sync/sync_service.py +++ b/src/basic_memory/sync/sync_service.py @@ -761,7 +761,7 @@ async def sync_regular_file(self, path: str, new: bool = True) -> Tuple[Optional try: entity = await self.entity_repository.add( Entity( - entity_type="file", + note_type="file", file_path=path, checksum=checksum, title=file_path.name, diff --git a/test-int/cli/test_routing_integration.py b/test-int/cli/test_routing_integration.py index ea36d9825..c204dec8a 100644 --- a/test-int/cli/test_routing_integration.py +++ b/test-int/cli/test_routing_integration.py @@ -250,9 +250,9 @@ def test_project_sync_config_accepts_local_flag(self, app_config): assert "No such option: --local" not in result.output def test_project_move_local_only(self, app_config): - """project move should accept --local flag.""" + """project move should reject --cloud flag.""" result = runner.invoke(cli_app, ["project", "move", "test", "/tmp/dest", "--cloud"]) - assert "No such option: --cloud" in result.output + assert result.exit_code == 2 def test_project_ls_accepts_local_flag(self, app_config): """project ls should accept --local flag.""" diff --git a/test-int/semantic/corpus.py b/test-int/semantic/corpus.py index 2804a23cd..b71d913ce 100644 --- a/test-int/semantic/corpus.py +++ b/test-int/semantic/corpus.py @@ -398,7 +398,7 @@ async def seed_benchmark_notes(search_service, note_count: int = 240): entity = await search_service.entity_repository.create( { "title": f"{topic.title()} Benchmark Note {note_index}", - "entity_type": "benchmark", + "note_type": "benchmark", "entity_metadata": {"tags": ["benchmark", topic], "status": "active"}, "content_type": "text/markdown", "permalink": permalink, diff --git a/test-int/semantic/test_search_diagnostics.py b/test-int/semantic/test_search_diagnostics.py index 14887eef3..813a6fc1e 100644 --- a/test-int/semantic/test_search_diagnostics.py +++ b/test-int/semantic/test_search_diagnostics.py @@ -109,7 +109,7 @@ async def seed_diagnostic_notes(search_service): entity = await search_service.entity_repository.create( { "title": note["title"], - "entity_type": "note", + "note_type": "note", "entity_metadata": {"tags": note.get("tags", [])}, "content_type": "text/markdown", "permalink": note["permalink"], diff --git a/test-int/semantic/test_semantic_coverage.py b/test-int/semantic/test_semantic_coverage.py index 9ef061c1e..ff2204183 100644 --- a/test-int/semantic/test_semantic_coverage.py +++ b/test-int/semantic/test_semantic_coverage.py @@ -200,7 +200,7 @@ async def test_postgres_vector_dimension_detection(postgres_engine_factory, tmp_ entity = await search_service.entity_repository.create( { "title": "Dimension Test Note", - "entity_type": "benchmark", + "note_type": "benchmark", "entity_metadata": {"tags": ["test"]}, "content_type": "text/markdown", "permalink": "bench/dim-test", @@ -243,7 +243,7 @@ async def test_postgres_incremental_vector_update(postgres_engine_factory, tmp_p entity = await search_service.entity_repository.create( { "title": "Update Test Note", - "entity_type": "benchmark", + "note_type": "benchmark", "entity_metadata": {"tags": ["test"]}, "content_type": "text/markdown", "permalink": "bench/update-test", diff --git a/test-int/test_disable_permalinks_integration.py b/test-int/test_disable_permalinks_integration.py index bf40f8443..f99e082db 100644 --- a/test-int/test_disable_permalinks_integration.py +++ b/test-int/test_disable_permalinks_integration.py @@ -62,7 +62,7 @@ async def test_disable_permalinks_create_entity(tmp_path, engine_factory, app_co entity_data = EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Test content", ) diff --git a/test-int/test_search_performance_benchmark.py b/test-int/test_search_performance_benchmark.py index d9dab651a..2f381ba8e 100644 --- a/test-int/test_search_performance_benchmark.py +++ b/test-int/test_search_performance_benchmark.py @@ -251,7 +251,7 @@ async def _seed_benchmark_notes(search_service, note_count: int): entity = await search_service.entity_repository.create( { "title": f"{topic.title()} Benchmark Note {note_index}", - "entity_type": "benchmark", + "note_type": "benchmark", "entity_metadata": {"tags": ["benchmark", topic], "status": "active"}, "content_type": "text/markdown", "permalink": permalink, diff --git a/tests/api/v2/test_knowledge_router.py b/tests/api/v2/test_knowledge_router.py index 4bf37b5a4..5b61f8c08 100644 --- a/tests/api/v2/test_knowledge_router.py +++ b/tests/api/v2/test_knowledge_router.py @@ -154,7 +154,7 @@ async def test_create_entity(client: AsyncClient, file_service, v2_project_url): data = { "title": "TestV2Entity", "directory": "test", - "entity_type": "test", + "note_type": "test", "content_type": "text/markdown", "content": "TestContent for V2", } @@ -173,7 +173,7 @@ async def test_create_entity(client: AsyncClient, file_service, v2_project_url): assert entity.permalink == "test-project/test/test-v2-entity" assert entity.file_path == "test/TestV2Entity.md" - assert entity.entity_type == data["entity_type"] + assert entity.note_type == data["note_type"] # Verify file was created file_path = file_service.get_entity_path(entity) @@ -187,7 +187,7 @@ async def test_create_entity_conflict_returns_409(client: AsyncClient, v2_projec data = { "title": "TestV2EntityConflict", "directory": "conflict", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "content": "Original content for conflict", } @@ -215,7 +215,7 @@ async def test_create_entity_returns_content(client: AsyncClient, file_service, data = { "title": "TestContentReturn", "directory": "test", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "content": "Body content for return test", } diff --git a/tests/api/v2/test_memory_router.py b/tests/api/v2/test_memory_router.py index 1c07b3924..6ad87e8a1 100644 --- a/tests/api/v2/test_memory_router.py +++ b/tests/api/v2/test_memory_router.py @@ -38,7 +38,7 @@ async def test_get_recent_context( """Test getting recent activity context.""" entity_data = { "title": "Recent Test Entity", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "recent_test.md", "checksum": "abc123", @@ -74,7 +74,7 @@ async def test_get_recent_context_with_pagination( for i in range(5): entity_data = { "title": f"Entity {i}", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": f"entity_{i}.md", "checksum": f"checksum{i}", @@ -108,7 +108,7 @@ async def test_get_recent_context_with_type_filter( # Create a test entity entity_data = { "title": "Filtered Entity", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "filtered.md", "checksum": "xyz789", @@ -162,7 +162,7 @@ async def test_get_memory_context_by_permalink( # Create a test entity entity_data = { "title": "Context Test", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "context_test.md", "checksum": "def456", @@ -193,7 +193,7 @@ async def test_get_memory_context_by_id( # Create a test entity entity_data = { "title": "ID Context Test", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "id_context_test.md", "checksum": "ghi789", @@ -223,7 +223,7 @@ async def test_get_memory_context_with_depth( # Create a test entity entity_data = { "title": "Depth Test", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "depth_test.md", "checksum": "jkl012", @@ -268,7 +268,7 @@ async def test_get_memory_context_with_timeframe( # Create a test entity entity_data = { "title": "Timeframe Test", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "timeframe_test.md", "checksum": "mno345", @@ -302,7 +302,7 @@ async def test_recent_context_has_more( for i in range(4): entity_data = { "title": f"HasMore Memory {i}", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": f"hasmore_mem_{i}.md", "checksum": f"hasmoremem{i}", diff --git a/tests/api/v2/test_schema_router.py b/tests/api/v2/test_schema_router.py index f3114842e..da4ca9807 100644 --- a/tests/api/v2/test_schema_router.py +++ b/tests/api/v2/test_schema_router.py @@ -28,7 +28,7 @@ async def create_person_entities(entity_service, search_service): EntitySchema( title="Alice", directory="people", - entity_type="person", + note_type="person", content=dedent("""\ ## Observations - [name] Alice Smith @@ -42,7 +42,7 @@ async def create_person_entities(entity_service, search_service): EntitySchema( title="Bob", directory="people", - entity_type="person", + note_type="person", content=dedent("""\ ## Observations - [name] Bob Jones @@ -56,7 +56,7 @@ async def create_person_entities(entity_service, search_service): EntitySchema( title="Carol", directory="people", - entity_type="person", + note_type="person", content=dedent("""\ ## Observations - [name] Carol Lee @@ -93,13 +93,13 @@ async def test_infer_schema( response = await client.post( f"{v2_project_url}/schema/infer", - params={"entity_type": "person"}, + params={"note_type": "person"}, ) assert response.status_code == 200 data = response.json() - assert data["entity_type"] == "person" + assert data["note_type"] == "person" assert data["notes_analyzed"] == 3 assert isinstance(data["field_frequencies"], list) assert isinstance(data["suggested_schema"], dict) @@ -125,7 +125,7 @@ async def test_infer_schema_no_matching_notes( """Infer returns empty result when no notes of the given type exist.""" response = await client.post( f"{v2_project_url}/schema/infer", - params={"entity_type": "nonexistent"}, + params={"note_type": "nonexistent"}, ) assert response.status_code == 200 @@ -150,7 +150,7 @@ async def test_infer_schema_includes_relations( response = await client.post( f"{v2_project_url}/schema/infer", - params={"entity_type": "person"}, + params={"note_type": "person"}, ) assert response.status_code == 200 @@ -184,7 +184,7 @@ async def test_validate_with_inline_schema( EntitySchema( title="Dave", directory="people", - entity_type="person", + note_type="person", entity_metadata={ "schema": {"name": "string", "role": "string"}, }, @@ -199,7 +199,7 @@ async def test_validate_with_inline_schema( response = await client.post( f"{v2_project_url}/schema/validate", - params={"entity_type": "person"}, + params={"note_type": "person"}, ) assert response.status_code == 200 @@ -233,7 +233,7 @@ async def test_validate_with_explicit_schema_reference_by_permalink_slug( EntitySchema( title="Strict Person V2", directory="schemas", - entity_type="schema", + note_type="schema", entity_metadata={ "entity": "person", "schema": {"name": "string", "role": "string"}, @@ -250,7 +250,7 @@ async def test_validate_with_explicit_schema_reference_by_permalink_slug( EntitySchema( title="Frank", directory="people", - entity_type="person", + note_type="person", entity_metadata={ # Explicit schema identifier does not equal entity metadata field ("person") # and must resolve by schema identifier, not fuzzy text search. @@ -292,7 +292,7 @@ async def test_validate_missing_required_field( EntitySchema( title="Eve", directory="people", - entity_type="person", + note_type="person", entity_metadata={ "schema": {"name": "string", "role": "string"}, }, @@ -306,7 +306,7 @@ async def test_validate_missing_required_field( response = await client.post( f"{v2_project_url}/schema/validate", - params={"entity_type": "person"}, + params={"note_type": "person"}, ) assert response.status_code == 200 @@ -329,7 +329,7 @@ async def test_validate_no_matching_notes( """Validate returns empty result when no notes of the given type exist.""" response = await client.post( f"{v2_project_url}/schema/validate", - params={"entity_type": "nonexistent"}, + params={"note_type": "nonexistent"}, ) assert response.status_code == 200 @@ -352,7 +352,7 @@ async def test_validate_total_entities_without_schema( response = await client.post( f"{v2_project_url}/schema/validate", - params={"entity_type": "person"}, + params={"note_type": "person"}, ) assert response.status_code == 200 @@ -381,7 +381,7 @@ async def test_validate_with_frontmatter_rules_passes( EntitySchema( title="Person Schema FM", directory="schemas", - entity_type="schema", + note_type="schema", entity_metadata={ "entity": "person_fm", "schema": {"name": "string"}, @@ -406,7 +406,7 @@ async def test_validate_with_frontmatter_rules_passes( EntitySchema( title="Grace", directory="people", - entity_type="person_fm", + note_type="person_fm", entity_metadata={ "tags": ["engineer", "python"], "status": "published", @@ -449,7 +449,7 @@ async def test_validate_frontmatter_missing_required_key_warns( EntitySchema( title="Person Schema FM2", directory="schemas", - entity_type="schema", + note_type="schema", entity_metadata={ "entity": "person_fm2", "schema": {"name": "string"}, @@ -473,7 +473,7 @@ async def test_validate_frontmatter_missing_required_key_warns( EntitySchema( title="Hank", directory="people", - entity_type="person_fm2", + note_type="person_fm2", content=dedent("""\ ## Observations - [name] Hank Pym @@ -507,7 +507,7 @@ async def test_validate_frontmatter_enum_mismatch_warns( EntitySchema( title="Person Schema FM3", directory="schemas", - entity_type="schema", + note_type="schema", entity_metadata={ "entity": "person_fm3", "schema": {"name": "string"}, @@ -531,7 +531,7 @@ async def test_validate_frontmatter_enum_mismatch_warns( EntitySchema( title="Iris", directory="people", - entity_type="person_fm3", + note_type="person_fm3", entity_metadata={ "status": "archived", # not in [draft, published] }, @@ -575,7 +575,7 @@ async def test_diff_no_schema_found( assert response.status_code == 200 data = response.json() - assert data["entity_type"] == "nonexistent" + assert data["note_type"] == "nonexistent" assert data["new_fields"] == [] assert data["dropped_fields"] == [] assert data["cardinality_changes"] == [] @@ -597,7 +597,7 @@ async def test_diff_with_schema_note( EntitySchema( title="Person Schema", directory="schemas", - entity_type="schema", + note_type="schema", entity_metadata={ "entity": "person", "version": 1, @@ -620,7 +620,7 @@ async def test_diff_with_schema_note( assert response.status_code == 200 data = response.json() - assert data["entity_type"] == "person" + assert data["note_type"] == "person" assert isinstance(data["new_fields"], list) assert isinstance(data["dropped_fields"], list) assert isinstance(data["cardinality_changes"], list) diff --git a/tests/api/v2/test_search_router.py b/tests/api/v2/test_search_router.py index e2f2969ea..5394c9dfa 100644 --- a/tests/api/v2/test_search_router.py +++ b/tests/api/v2/test_search_router.py @@ -44,7 +44,7 @@ async def test_search_entities( # Create a test entity entity_data = { "title": "Searchable Entity", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "searchable.md", "checksum": "search123", @@ -79,7 +79,7 @@ async def test_search_with_pagination( for i in range(5): entity_data = { "title": f"Search Entity {i}", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": f"search_{i}.md", "checksum": f"searchsum{i}", @@ -114,7 +114,7 @@ async def test_search_by_permalink( # Create a test entity with permalink entity_data = { "title": "Permalink Search", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "permalink_search.md", "checksum": "perm123", @@ -147,7 +147,7 @@ async def test_search_by_title( # Create a test entity entity_data = { "title": "Unique Title For Search", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "unique_title.md", "checksum": "title123", @@ -175,13 +175,13 @@ async def test_search_with_type_filter( ): """Test searching with entity type filter.""" # Create test entities of different types - for entity_type in ["note", "document"]: + for note_type in ["note", "document"]: entity_data = { - "title": f"Type {entity_type}", - "entity_type": entity_type, + "title": f"Type {note_type}", + "note_type": note_type, "content_type": "text/markdown", - "file_path": f"type_{entity_type}.md", - "checksum": f"type{entity_type}", + "file_path": f"type_{note_type}.md", + "checksum": f"type{note_type}", } await create_test_entity( test_project, entity_data, entity_repository, search_service, file_service @@ -189,7 +189,7 @@ async def test_search_with_type_filter( # Search with type filter response = await client.post( - f"{v2_project_url}/search/", json={"search_text": "Type", "types": ["note"]} + f"{v2_project_url}/search/", json={"search_text": "Type", "note_types": ["note"]} ) assert response.status_code == 200 @@ -210,7 +210,7 @@ async def test_search_with_date_filter( # Create a test entity entity_data = { "title": "Date Filtered", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "date_filtered.md", "checksum": "date123", @@ -377,7 +377,7 @@ async def test_search_has_more_when_more_results_exist( for i in range(4): entity_data = { "title": f"HasMore Entity {i}", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": f"hasmore_{i}.md", "checksum": f"hasmore{i}", @@ -411,7 +411,7 @@ async def test_search_has_more_false_on_last_page( """has_more should be False when all results fit on the current page.""" entity_data = { "title": "Solo Search Entity", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "file_path": "solo_search.md", "checksum": "solo123", diff --git a/tests/cli/test_cli_schema.py b/tests/cli/test_cli_schema.py index f6d69cec9..e928e5d84 100644 --- a/tests/cli/test_cli_schema.py +++ b/tests/cli/test_cli_schema.py @@ -15,7 +15,7 @@ # --- Shared mock data --- VALIDATE_REPORT = { - "entity_type": "person", + "note_type": "person", "total_notes": 2, "total_entities": 2, "valid_count": 1, @@ -40,7 +40,7 @@ } INFER_REPORT = { - "entity_type": "person", + "note_type": "person", "notes_analyzed": 5, "field_frequencies": [ {"name": "name", "source": "observation", "count": 5, "total": 5, "percentage": 1.0}, @@ -53,7 +53,7 @@ } DIFF_REPORT_WITH_DRIFT = { - "entity_type": "person", + "note_type": "person", "schema_found": True, "new_fields": [ {"name": "email", "source": "observation", "count": 3, "total": 5, "percentage": 0.6} @@ -65,7 +65,7 @@ } DIFF_REPORT_NO_DRIFT = { - "entity_type": "person", + "note_type": "person", "schema_found": True, "new_fields": [], "dropped_fields": [], @@ -214,7 +214,7 @@ def test_infer_error_response(mock_mcp, mock_config_cls): "basic_memory.cli.commands.schema.mcp_schema_infer", new_callable=AsyncMock, return_value={ - "entity_type": "person", + "note_type": "person", "notes_analyzed": 0, "field_frequencies": [], "suggested_schema": {}, diff --git a/tests/cli/test_cli_tool_json_output.py b/tests/cli/test_cli_tool_json_output.py index 816b4fdfd..e4a54de18 100644 --- a/tests/cli/test_cli_tool_json_output.py +++ b/tests/cli/test_cli_tool_json_output.py @@ -475,7 +475,7 @@ def test_routing_both_flags_error(): # --- schema-validate --- SCHEMA_VALIDATE_RESULT = { - "entity_type": "person", + "note_type": "person", "total_notes": 2, "total_entities": 2, "valid_count": 1, @@ -514,7 +514,7 @@ def test_schema_validate_json_output(mock_mcp): assert result.exit_code == 0, f"CLI failed: {result.output}" data = json.loads(result.output) - assert data["entity_type"] == "person" + assert data["note_type"] == "person" assert data["total_notes"] == 2 mock_mcp.assert_called_once() assert mock_mcp.call_args.kwargs["output_format"] == "json" @@ -557,7 +557,7 @@ def test_schema_validate_error_response(mock_mcp): # --- schema-infer --- SCHEMA_INFER_RESULT = { - "entity_type": "person", + "note_type": "person", "notes_analyzed": 5, "field_frequencies": [ {"name": "name", "source": "observation", "count": 5, "total": 5, "percentage": 1.0}, @@ -584,7 +584,7 @@ def test_schema_infer_json_output(mock_mcp): assert result.exit_code == 0, f"CLI failed: {result.output}" data = json.loads(result.output) - assert data["entity_type"] == "person" + assert data["note_type"] == "person" assert data["notes_analyzed"] == 5 mock_mcp.assert_called_once() assert mock_mcp.call_args.kwargs["output_format"] == "json" @@ -609,7 +609,7 @@ def test_schema_infer_threshold_passthrough(mock_mcp): # --- schema-diff --- SCHEMA_DIFF_RESULT = { - "entity_type": "person", + "note_type": "person", "schema_found": True, "new_fields": [ {"name": "email", "source": "observation", "count": 3, "total": 5, "percentage": 0.6} @@ -633,7 +633,7 @@ def test_schema_diff_json_output(mock_mcp): assert result.exit_code == 0, f"CLI failed: {result.output}" data = json.loads(result.output) - assert data["entity_type"] == "person" + assert data["note_type"] == "person" assert data["schema_found"] is True assert len(data["new_fields"]) == 1 mock_mcp.assert_called_once() diff --git a/tests/conftest.py b/tests/conftest.py index 662a56495..2e0dfa862 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -453,7 +453,7 @@ async def sample_entity(entity_repository: EntityRepository) -> Entity: entity_data = { "project_id": entity_repository.project_id, "title": "Test Entity", - "entity_type": "test", + "note_type": "test", "permalink": "test/test-entity", "file_path": "test/test_entity.md", "content_type": "text/markdown", @@ -481,7 +481,7 @@ async def full_entity(sample_entity, entity_repository, file_service, entity_ser EntitySchema( title="Search_Entity", directory="test", - entity_type="test", + note_type="test", content=dedent(""" ## Observations - [tech] Tech note @@ -511,7 +511,7 @@ async def test_graph( deeper, _ = await entity_service.create_or_update_entity( EntitySchema( title="Deeper Entity", - entity_type="deeper", + note_type="deeper", directory="test", content=dedent(""" # Deeper Entity @@ -522,7 +522,7 @@ async def test_graph( deep, _ = await entity_service.create_or_update_entity( EntitySchema( title="Deep Entity", - entity_type="deep", + note_type="deep", directory="test", content=dedent(""" # Deep Entity @@ -534,7 +534,7 @@ async def test_graph( connected_2, _ = await entity_service.create_or_update_entity( EntitySchema( title="Connected Entity 2", - entity_type="test", + note_type="test", directory="test", content=dedent(""" # Connected Entity 2 @@ -546,7 +546,7 @@ async def test_graph( connected_1, _ = await entity_service.create_or_update_entity( EntitySchema( title="Connected Entity 1", - entity_type="test", + note_type="test", directory="test", content=dedent(""" # Connected Entity 1 @@ -559,7 +559,7 @@ async def test_graph( root, _ = await entity_service.create_or_update_entity( EntitySchema( title="Root", - entity_type="test", + note_type="test", directory="test", content=dedent(""" # Root Entity diff --git a/tests/db/test_issue_254_foreign_key_constraints.py b/tests/db/test_issue_254_foreign_key_constraints.py index d9cfa64af..58cf528da 100644 --- a/tests/db/test_issue_254_foreign_key_constraints.py +++ b/tests/db/test_issue_254_foreign_key_constraints.py @@ -65,7 +65,7 @@ async def test_issue_254_foreign_key_constraint_fix(project_service: ProjectServ # Create entity entity_data = { "title": "Issue 254 Test Entity", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "project_id": project.id, "permalink": "issue-254-entity", @@ -146,7 +146,7 @@ async def test_issue_254_reproduction(project_service: ProjectService): entity_data = { "title": "Reproduction Entity", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "project_id": project.id, "permalink": "reproduction-entity", diff --git a/tests/markdown/test_entity_parser_error_handling.py b/tests/markdown/test_entity_parser_error_handling.py index 62d53eeab..5b3528f3f 100644 --- a/tests/markdown/test_entity_parser_error_handling.py +++ b/tests/markdown/test_entity_parser_error_handling.py @@ -79,12 +79,12 @@ async def test_parse_file_with_completely_invalid_yaml(tmp_path): @pytest.mark.asyncio -async def test_parse_file_without_entity_type(tmp_path): - """Test that files without entity_type get a default value (issue #184). +async def test_parse_file_without_note_type(tmp_path): + """Test that files without note_type get a default value (issue #184). - This reproduces the NOT NULL constraint error where entity_type was missing. + This reproduces the NOT NULL constraint error where note_type was missing. """ - # Create a file without entity_type in frontmatter + # Create a file without note_type in frontmatter test_file = tmp_path / "no_type.md" content = dedent( """ @@ -102,7 +102,7 @@ async def test_parse_file_without_entity_type(tmp_path): parser = EntityParser(tmp_path) result = await parser.parse_file(test_file) - # Should have default entity_type + # Should have default note_type assert result is not None assert result.frontmatter.type == "note" # Default type applied assert result.frontmatter.title == "The Invisible Weight of Mental Habits" @@ -159,9 +159,9 @@ async def test_parse_file_without_frontmatter(tmp_path): @pytest.mark.asyncio -async def test_parse_file_with_null_entity_type(tmp_path): - """Test that files with explicit null entity_type get default (issue #184).""" - # Create a file with null/None entity_type +async def test_parse_file_with_null_note_type(tmp_path): + """Test that files with explicit null note_type get default (issue #184).""" + # Create a file with null/None note_type test_file = tmp_path / "null_type.md" content = dedent( """ @@ -402,7 +402,7 @@ async def test_schema_to_markdown_empty_metadata_no_metadata_key(): schema = SimpleNamespace( title="Empty Metadata Test", - entity_type="note", + note_type="note", permalink="empty-metadata-test", content="# Empty Metadata Test\n\nSome content.", entity_metadata={}, diff --git a/tests/markdown/test_markdown_utils.py b/tests/markdown/test_markdown_utils.py index 678674d66..2d0a568a8 100644 --- a/tests/markdown/test_markdown_utils.py +++ b/tests/markdown/test_markdown_utils.py @@ -16,7 +16,7 @@ class TestEntityModelFromMarkdown: def _create_markdown( self, title: str = "Test Entity", - entity_type: str = "note", + note_type: str = "note", permalink: str = "test/test-entity", created: datetime | None = None, modified: datetime | None = None, @@ -27,7 +27,7 @@ def _create_markdown( return EntityMarkdown( frontmatter=EntityFrontmatter( title=title, - type=entity_type, + type=note_type, permalink=permalink, ), content=f"# {title}\n\nTest content.", @@ -127,7 +127,7 @@ def test_entity_model_fields_populated_with_external_id(self): """ markdown = self._create_markdown( title="My Test Entity", - entity_type="component", + note_type="component", permalink="components/my-test", ) file_path = Path("components/my-test.md") diff --git a/tests/mcp/clients/test_clients.py b/tests/mcp/clients/test_clients.py index 265a50d3e..6d74d58ee 100644 --- a/tests/mcp/clients/test_clients.py +++ b/tests/mcp/clients/test_clients.py @@ -34,7 +34,7 @@ async def test_create_entity(self, monkeypatch): "permalink": "test", "title": "Test", "file_path": "test.md", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "observations": [], "relations": [], diff --git a/tests/mcp/conftest.py b/tests/mcp/conftest.py index 5d2f153e2..64366b9c8 100644 --- a/tests/mcp/conftest.py +++ b/tests/mcp/conftest.py @@ -43,7 +43,7 @@ def test_entity_data(): "entities": [ { "title": "Test Entity", - "entity_type": "test", + "note_type": "test", "summary": "", # Empty string instead of None } ] diff --git a/tests/mcp/test_client_schema.py b/tests/mcp/test_client_schema.py index a32515f33..b6b85cdc3 100644 --- a/tests/mcp/test_client_schema.py +++ b/tests/mcp/test_client_schema.py @@ -45,9 +45,9 @@ class TestSchemaClientValidate: @pytest.mark.asyncio async def test_validate_no_params(self, schema_client, monkeypatch): - """Validate with no entity_type or identifier sends empty params.""" + """Validate with no note_type or identifier sends empty params.""" report_data = { - "entity_type": None, + "note_type": None, "total_notes": 0, "valid_count": 0, "warning_count": 0, @@ -70,10 +70,10 @@ async def mock_call_post(client, url, **kwargs): assert result.total_notes == 0 @pytest.mark.asyncio - async def test_validate_with_entity_type(self, schema_client, monkeypatch): - """Validate sends entity_type as query param.""" + async def test_validate_with_note_type(self, schema_client, monkeypatch): + """Validate sends note_type as query param.""" report_data = { - "entity_type": "person", + "note_type": "person", "total_notes": 5, "valid_count": 4, "warning_count": 1, @@ -85,20 +85,20 @@ async def test_validate_with_entity_type(self, schema_client, monkeypatch): mock_response = Response(200, json=report_data, request=request) async def mock_call_post(client, url, **kwargs): - assert kwargs["params"]["entity_type"] == "person" + assert kwargs["params"]["note_type"] == "person" return mock_response monkeypatch.setattr("basic_memory.mcp.clients.schema.call_post", mock_call_post) - result = await schema_client.validate(entity_type="person") - assert result.entity_type == "person" + result = await schema_client.validate(note_type="person") + assert result.note_type == "person" assert result.total_notes == 5 @pytest.mark.asyncio async def test_validate_with_identifier(self, schema_client, monkeypatch): """Validate sends identifier as query param.""" report_data = { - "entity_type": None, + "note_type": None, "total_notes": 1, "valid_count": 1, "warning_count": 0, @@ -124,9 +124,9 @@ class TestSchemaClientInfer: @pytest.mark.asyncio async def test_infer_default_threshold(self, schema_client, monkeypatch): - """Infer sends entity_type and default threshold.""" + """Infer sends note_type and default threshold.""" report_data = { - "entity_type": "person", + "note_type": "person", "notes_analyzed": 10, "field_frequencies": [], "suggested_schema": {}, @@ -140,7 +140,7 @@ async def test_infer_default_threshold(self, schema_client, monkeypatch): async def mock_call_post(client, url, **kwargs): assert url == "/v2/projects/test-project-id/schema/infer" - assert kwargs["params"]["entity_type"] == "person" + assert kwargs["params"]["note_type"] == "person" assert kwargs["params"]["threshold"] == 0.25 return mock_response @@ -155,7 +155,7 @@ async def mock_call_post(client, url, **kwargs): async def test_infer_custom_threshold(self, schema_client, monkeypatch): """Infer passes custom threshold.""" report_data = { - "entity_type": "meeting", + "note_type": "meeting", "notes_analyzed": 5, "field_frequencies": [], "suggested_schema": {}, @@ -174,7 +174,7 @@ async def mock_call_post(client, url, **kwargs): monkeypatch.setattr("basic_memory.mcp.clients.schema.call_post", mock_call_post) result = await schema_client.infer("meeting", threshold=0.5) - assert result.entity_type == "meeting" + assert result.note_type == "meeting" class TestSchemaClientDiff: @@ -182,9 +182,9 @@ class TestSchemaClientDiff: @pytest.mark.asyncio async def test_diff(self, schema_client, monkeypatch): - """Diff calls GET with entity_type in path.""" + """Diff calls GET with note_type in path.""" report_data = { - "entity_type": "person", + "note_type": "person", "new_fields": [], "dropped_fields": [], "cardinality_changes": [], @@ -201,13 +201,13 @@ async def mock_call_get(client, url, **kwargs): result = await schema_client.diff("person") assert isinstance(result, DriftReport) - assert result.entity_type == "person" + assert result.note_type == "person" @pytest.mark.asyncio async def test_diff_with_drift(self, schema_client, monkeypatch): """Diff returns populated drift report.""" report_data = { - "entity_type": "person", + "note_type": "person", "new_fields": [ { "name": "role", diff --git a/tests/mcp/test_tool_contracts.py b/tests/mcp/test_tool_contracts.py index c50818a0c..de1960c3b 100644 --- a/tests/mcp/test_tool_contracts.py +++ b/tests/mcp/test_tool_contracts.py @@ -82,7 +82,7 @@ "page_size", "search_type", "output_format", - "types", + "note_types", "entity_types", "after_date", "metadata_filters", diff --git a/tests/mcp/test_tool_schema.py b/tests/mcp/test_tool_schema.py index a1d75891e..a97760d11 100644 --- a/tests/mcp/test_tool_schema.py +++ b/tests/mcp/test_tool_schema.py @@ -129,7 +129,7 @@ async def test_schema_infer(app, test_project, sync_service): ) assert isinstance(result, InferenceReport) - assert result.entity_type == "person" + assert result.note_type == "person" assert result.notes_analyzed >= 3 @@ -168,7 +168,7 @@ async def test_schema_diff(app, test_project, sync_service): ) assert isinstance(result, DriftReport) - assert result.entity_type == "person" + assert result.note_type == "person" # --- write_note metadata → schema workflow --- diff --git a/tests/mcp/test_tool_search.py b/tests/mcp/test_tool_search.py index 0773c11cb..72644e7ee 100644 --- a/tests/mcp/test_tool_search.py +++ b/tests/mcp/test_tool_search.py @@ -186,17 +186,17 @@ async def test_search_pagination(client, test_project): @pytest.mark.asyncio async def test_search_with_type_filter(client, test_project): - """Test search with entity type filter.""" + """Test search with note type filter.""" # Create test content await write_note( project=test_project.name, - title="Entity Type Test", + title="Note Type Test", directory="test", content="# Test\nFiltered by type", ) - # Search with type filter - response = await search_notes(project=test_project.name, query="type", types=["note"]) + # Search with note type filter + response = await search_notes(project=test_project.name, query="type", note_types=["note"]) # Verify results - handle both success and error cases if isinstance(response, SearchResponse): @@ -209,7 +209,7 @@ async def test_search_with_type_filter(client, test_project): @pytest.mark.asyncio async def test_search_with_entity_type_filter(client, test_project): - """Test search with entity type filter.""" + """Test search with entity_types (SearchItemType) filter.""" # Create test content await write_note( project=test_project.name, @@ -218,7 +218,7 @@ async def test_search_with_entity_type_filter(client, test_project): content="# Test\nFiltered by type", ) - # Search with entity type filter + # Search with entity_types (SearchItemType) filter response = await search_notes(project=test_project.name, query="type", entity_types=["entity"]) # Verify results - handle both success and error cases diff --git a/tests/mcp/test_tool_write_note.py b/tests/mcp/test_tool_write_note.py index 1d56f5abd..744183100 100644 --- a/tests/mcp/test_tool_write_note.py +++ b/tests/mcp/test_tool_write_note.py @@ -533,10 +533,10 @@ async def test_write_note_permalink_collision_fix_issue_139(app, test_project): @pytest.mark.asyncio -async def test_write_note_with_custom_entity_type(app, test_project): - """Test creating a note with custom entity_type parameter. +async def test_write_note_with_custom_note_type(app, test_project): + """Test creating a note with custom note_type parameter. - This test verifies the fix for Issue #144 where entity_type parameter + This test verifies the fix for Issue #144 where note_type parameter was hardcoded to "note" instead of allowing custom types. """ result = await write_note( @@ -557,7 +557,7 @@ async def test_write_note_with_custom_entity_type(app, test_project): assert "- guide, documentation" in result assert f"[Session: Using project '{test_project.name}']" in result - # Verify the entity type is correctly set in the frontmatter + # Verify the note type is correctly set in the frontmatter content = await read_note("guides/test-guide", project=test_project.name) expected = normalize_newlines( dedent(""" @@ -580,7 +580,7 @@ async def test_write_note_with_custom_entity_type(app, test_project): @pytest.mark.asyncio -async def test_write_note_with_report_entity_type(app, test_project): +async def test_write_note_with_report_note_type(app, test_project): """Test creating a note with note_type="report".""" result = await write_note( project=test_project.name, @@ -598,14 +598,14 @@ async def test_write_note_with_report_entity_type(app, test_project): assert f"permalink: {test_project.name}/reports/monthly-report" in result assert f"[Session: Using project '{test_project.name}']" in result - # Verify the entity type is correctly set in the frontmatter + # Verify the note type is correctly set in the frontmatter content = await read_note("reports/monthly-report", project=test_project.name) assert "type: report" in content assert "# Monthly Report" in content @pytest.mark.asyncio -async def test_write_note_with_config_entity_type(app, test_project): +async def test_write_note_with_config_note_type(app, test_project): """Test creating a note with note_type="config".""" result = await write_note( project=test_project.name, @@ -622,18 +622,18 @@ async def test_write_note_with_config_entity_type(app, test_project): assert f"permalink: {test_project.name}/config/system-config" in result assert f"[Session: Using project '{test_project.name}']" in result - # Verify the entity type is correctly set in the frontmatter + # Verify the note type is correctly set in the frontmatter content = await read_note("config/system-config", project=test_project.name) assert "type: config" in content assert "# System Configuration" in content @pytest.mark.asyncio -async def test_write_note_entity_type_default_behavior(app, test_project): - """Test that the entity_type parameter defaults to "note" when not specified. +async def test_write_note_note_type_default_behavior(app, test_project): + """Test that the note_type parameter defaults to "note" when not specified. This ensures backward compatibility - existing code that doesn't specify - entity_type should continue to work as before. + note_type should continue to work as before. """ result = await write_note( project=test_project.name, @@ -650,15 +650,15 @@ async def test_write_note_entity_type_default_behavior(app, test_project): assert f"permalink: {test_project.name}/test/default-type-test" in result assert f"[Session: Using project '{test_project.name}']" in result - # Verify the entity type defaults to "note" + # Verify the note type defaults to "note" content = await read_note("test/default-type-test", project=test_project.name) assert "type: note" in content assert "# Default Type Test" in content @pytest.mark.asyncio -async def test_write_note_update_existing_with_different_entity_type(app, test_project): - """Test updating an existing note with a different entity_type.""" +async def test_write_note_update_existing_with_different_note_type(app, test_project): + """Test updating an existing note with a different note_type.""" # Create initial note as "note" type result1 = await write_note( project=test_project.name, @@ -687,7 +687,7 @@ async def test_write_note_update_existing_with_different_entity_type(app, test_p assert "# Updated note" in result2 assert f"project: {test_project.name}" in result2 - # Verify the entity type was updated + # Verify the note type was updated content = await read_note("test/changeable-type", project=test_project.name) assert "type: guide" in content assert "# Updated Content" in content @@ -695,10 +695,10 @@ async def test_write_note_update_existing_with_different_entity_type(app, test_p @pytest.mark.asyncio -async def test_write_note_respects_frontmatter_entity_type(app, test_project): - """Test that entity_type in frontmatter is respected when parameter is not provided. +async def test_write_note_respects_frontmatter_note_type(app, test_project): + """Test that note_type in frontmatter is respected when parameter is not provided. - This verifies that when write_note is called without entity_type parameter, + This verifies that when write_note is called without note_type parameter, but the content includes frontmatter with a 'type' field, that type is respected instead of defaulting to 'note'. """ @@ -716,7 +716,7 @@ async def test_write_note_respects_frontmatter_entity_type(app, test_project): This is a guide """).strip() - # Call write_note without entity_type parameter - it should respect frontmatter type + # Call write_note without note_type parameter - it should respect frontmatter type result = await write_note( project=test_project.name, title="Test Guide", directory="guides", content=note ) @@ -728,7 +728,7 @@ async def test_write_note_respects_frontmatter_entity_type(app, test_project): assert "permalink: guides/test-guide" in result assert f"[Session: Using project '{test_project.name}']" in result - # Verify the entity type from frontmatter is respected (should be "guide", not "note") + # Verify the note type from frontmatter is respected (should be "guide", not "note") content = await read_note("guides/test-guide", project=test_project.name) assert "type: guide" in content assert "# Guide Content" in content diff --git a/tests/mcp/test_tool_write_note_metadata.py b/tests/mcp/test_tool_write_note_metadata.py index 4e1dda478..f548d3562 100644 --- a/tests/mcp/test_tool_write_note_metadata.py +++ b/tests/mcp/test_tool_write_note_metadata.py @@ -1,7 +1,7 @@ """Tests for the write_note `metadata` parameter. Covers positive, negative, and edge-case scenarios for passing arbitrary -frontmatter fields through entity_metadata. +frontmatter fields through note_metadata. """ import pytest @@ -192,7 +192,7 @@ async def test_metadata_empty_dict(app, test_project): async def test_metadata_title_key_stripped(app, test_project): """A 'title' key in metadata does not override the title parameter. - schema_to_markdown pops 'title' from entity_metadata so the Entity.title wins. + schema_to_markdown pops 'title' from note_metadata so the Entity.title wins. """ result = await write_note( project=test_project.name, @@ -213,7 +213,7 @@ async def test_metadata_title_key_stripped(app, test_project): async def test_metadata_type_key_stripped(app, test_project): """A 'type' key in metadata does not override note_type parameter. - schema_to_markdown pops 'type' from entity_metadata so Entity.entity_type wins. + schema_to_markdown pops 'type' from note_metadata so Entity.note_type wins. """ result = await write_note( project=test_project.name, @@ -235,7 +235,7 @@ async def test_metadata_type_key_stripped(app, test_project): async def test_metadata_permalink_key_stripped(app, test_project): """A 'permalink' key in metadata does not hijack the canonical permalink. - schema_to_markdown pops 'permalink' from entity_metadata. + schema_to_markdown pops 'permalink' from note_metadata. """ result = await write_note( project=test_project.name, diff --git a/tests/repository/test_entity_repository.py b/tests/repository/test_entity_repository.py index bf01f7c95..f2c9fcf0e 100644 --- a/tests/repository/test_entity_repository.py +++ b/tests/repository/test_entity_repository.py @@ -39,7 +39,7 @@ async def related_results(session_maker, test_project: Project): source = Entity( project_id=test_project.id, title="source", - entity_type="test", + note_type="test", permalink="source/source", file_path="source/source.md", content_type="text/markdown", @@ -49,7 +49,7 @@ async def related_results(session_maker, test_project: Project): target = Entity( project_id=test_project.id, title="target", - entity_type="test", + note_type="test", permalink="target/target", file_path="target/target.md", content_type="text/markdown", @@ -78,7 +78,7 @@ async def test_create_entity(entity_repository: EntityRepository): entity_data = { "project_id": entity_repository.project_id, "title": "Test", - "entity_type": "test", + "note_type": "test", "permalink": "test/test", "file_path": "test/test.md", "content_type": "text/markdown", @@ -112,7 +112,7 @@ async def test_create_all(entity_repository: EntityRepository): { "project_id": entity_repository.project_id, "title": "Test_1", - "entity_type": "test", + "note_type": "test", "permalink": "test/test-1", "file_path": "test/test_1.md", "content_type": "text/markdown", @@ -122,7 +122,7 @@ async def test_create_all(entity_repository: EntityRepository): { "project_id": entity_repository.project_id, "title": "Test-2", - "entity_type": "test", + "note_type": "test", "permalink": "test/test-2", "file_path": "test/test_2.md", "content_type": "text/markdown", @@ -191,7 +191,7 @@ async def test_update_entity_returns_with_relations_and_observations( target = Entity( project_id=test_project.id, title="target", - entity_type="test", + note_type="test", permalink="target/target", file_path="target/target.md", content_type="text/markdown", @@ -264,12 +264,12 @@ async def test_delete_entity_with_observations( @pytest.mark.asyncio async def test_delete_entities_by_type(entity_repository: EntityRepository, sample_entity): """Test deleting entities by type.""" - result = await entity_repository.delete_by_fields(entity_type=sample_entity.entity_type) + result = await entity_repository.delete_by_fields(note_type=sample_entity.note_type) assert result is True # Verify deletion async with db.scoped_session(entity_repository.session_maker) as session: - query = select(Entity).filter(Entity.entity_type == sample_entity.entity_type) + query = select(Entity).filter(Entity.note_type == sample_entity.note_type) result = await session.execute(query) remaining = result.scalars().all() assert len(remaining) == 0 @@ -311,7 +311,7 @@ async def test_entities(session_maker, test_project: Project): Entity( project_id=test_project.id, title="entity1", - entity_type="test", + note_type="test", permalink="type1/entity1", file_path="type1/entity1.md", content_type="text/markdown", @@ -321,7 +321,7 @@ async def test_entities(session_maker, test_project: Project): Entity( project_id=test_project.id, title="entity2", - entity_type="test", + note_type="test", permalink="type1/entity2", file_path="type1/entity2.md", content_type="text/markdown", @@ -331,7 +331,7 @@ async def test_entities(session_maker, test_project: Project): Entity( project_id=test_project.id, title="entity3", - entity_type="test", + note_type="test", permalink="type2/entity3", file_path="type2/entity3.md", content_type="text/markdown", @@ -390,7 +390,7 @@ async def test_generate_permalink_from_file_path(): # Verify the result passes validation Entity( title="test", - entity_type="test", + note_type="test", permalink=result, file_path=input_path, content_type="text/markdown", @@ -406,7 +406,7 @@ async def test_get_by_title(entity_repository: EntityRepository, session_maker): Entity( project_id=entity_repository.project_id, title="Unique Title", - entity_type="test", + note_type="test", permalink="test/unique-title", file_path="test/unique-title.md", content_type="text/markdown", @@ -416,7 +416,7 @@ async def test_get_by_title(entity_repository: EntityRepository, session_maker): Entity( project_id=entity_repository.project_id, title="Another Title", - entity_type="test", + note_type="test", permalink="test/another-title", file_path="test/another-title.md", content_type="text/markdown", @@ -426,7 +426,7 @@ async def test_get_by_title(entity_repository: EntityRepository, session_maker): Entity( project_id=entity_repository.project_id, title="Another Title", - entity_type="test", + note_type="test", permalink="test/another-title-1", file_path="test/another-title-1.md", content_type="text/markdown", @@ -473,7 +473,7 @@ async def test_get_by_title_returns_shortest_path_first( Entity( project_id=entity_repository.project_id, title="My Note", - entity_type="note", + note_type="note", permalink="archive/old/2024/my-note", file_path="archive/old/2024/My Note.md", # longest path content_type="text/markdown", @@ -483,7 +483,7 @@ async def test_get_by_title_returns_shortest_path_first( Entity( project_id=entity_repository.project_id, title="My Note", - entity_type="note", + note_type="note", permalink="docs/my-note", file_path="docs/My Note.md", # medium path content_type="text/markdown", @@ -493,7 +493,7 @@ async def test_get_by_title_returns_shortest_path_first( Entity( project_id=entity_repository.project_id, title="My Note", - entity_type="note", + note_type="note", permalink="my-note", file_path="My Note.md", # shortest path (root) content_type="text/markdown", @@ -525,7 +525,7 @@ async def test_get_by_file_path(entity_repository: EntityRepository, session_mak Entity( project_id=entity_repository.project_id, title="Unique Title", - entity_type="test", + note_type="test", permalink="test/unique-title", file_path="test/unique-title.md", content_type="text/markdown", @@ -555,7 +555,7 @@ async def test_get_distinct_directories(entity_repository: EntityRepository, ses Entity( project_id=entity_repository.project_id, title="File 1", - entity_type="test", + note_type="test", permalink="docs/guides/file1", file_path="docs/guides/file1.md", content_type="text/markdown", @@ -565,7 +565,7 @@ async def test_get_distinct_directories(entity_repository: EntityRepository, ses Entity( project_id=entity_repository.project_id, title="File 2", - entity_type="test", + note_type="test", permalink="docs/guides/file2", file_path="docs/guides/file2.md", content_type="text/markdown", @@ -575,7 +575,7 @@ async def test_get_distinct_directories(entity_repository: EntityRepository, ses Entity( project_id=entity_repository.project_id, title="File 3", - entity_type="test", + note_type="test", permalink="docs/api/file3", file_path="docs/api/file3.md", content_type="text/markdown", @@ -585,7 +585,7 @@ async def test_get_distinct_directories(entity_repository: EntityRepository, ses Entity( project_id=entity_repository.project_id, title="File 4", - entity_type="test", + note_type="test", permalink="specs/file4", file_path="specs/file4.md", content_type="text/markdown", @@ -595,7 +595,7 @@ async def test_get_distinct_directories(entity_repository: EntityRepository, ses Entity( project_id=entity_repository.project_id, title="File 5", - entity_type="test", + note_type="test", permalink="notes/2024/q1/file5", file_path="notes/2024/q1/file5.md", content_type="text/markdown", @@ -649,7 +649,7 @@ async def test_find_by_directory_prefix(entity_repository: EntityRepository, ses Entity( project_id=entity_repository.project_id, title="File 1", - entity_type="test", + note_type="test", permalink="docs/file1", file_path="docs/file1.md", content_type="text/markdown", @@ -659,7 +659,7 @@ async def test_find_by_directory_prefix(entity_repository: EntityRepository, ses Entity( project_id=entity_repository.project_id, title="File 2", - entity_type="test", + note_type="test", permalink="docs/guides/file2", file_path="docs/guides/file2.md", content_type="text/markdown", @@ -669,7 +669,7 @@ async def test_find_by_directory_prefix(entity_repository: EntityRepository, ses Entity( project_id=entity_repository.project_id, title="File 3", - entity_type="test", + note_type="test", permalink="docs/api/file3", file_path="docs/api/file3.md", content_type="text/markdown", @@ -679,7 +679,7 @@ async def test_find_by_directory_prefix(entity_repository: EntityRepository, ses Entity( project_id=entity_repository.project_id, title="File 4", - entity_type="test", + note_type="test", permalink="specs/file4", file_path="specs/file4.md", content_type="text/markdown", @@ -734,7 +734,7 @@ async def test_find_by_directory_prefix_basic_fields_only( entity = Entity( project_id=entity_repository.project_id, title="Test Entity", - entity_type="test", + note_type="test", permalink="docs/test", file_path="docs/test.md", content_type="text/markdown", @@ -753,7 +753,7 @@ async def test_find_by_directory_prefix_basic_fields_only( assert entity.title == "Test Entity" assert entity.file_path == "docs/test.md" assert entity.permalink == "docs/test" - assert entity.entity_type == "test" + assert entity.note_type == "test" assert entity.content_type == "text/markdown" assert entity.updated_at is not None @@ -767,7 +767,7 @@ async def test_get_all_file_paths(entity_repository: EntityRepository, session_m Entity( project_id=entity_repository.project_id, title="File 1", - entity_type="test", + note_type="test", permalink="docs/file1", file_path="docs/file1.md", content_type="text/markdown", @@ -777,7 +777,7 @@ async def test_get_all_file_paths(entity_repository: EntityRepository, session_m Entity( project_id=entity_repository.project_id, title="File 2", - entity_type="test", + note_type="test", permalink="specs/file2", file_path="specs/file2.md", content_type="text/markdown", @@ -787,7 +787,7 @@ async def test_get_all_file_paths(entity_repository: EntityRepository, session_m Entity( project_id=entity_repository.project_id, title="File 3", - entity_type="test", + note_type="test", permalink="notes/file3", file_path="notes/file3.md", content_type="text/markdown", @@ -827,7 +827,7 @@ async def test_get_all_file_paths_performance(entity_repository: EntityRepositor entity1 = Entity( project_id=entity_repository.project_id, title="Entity 1", - entity_type="test", + note_type="test", permalink="test/entity1", file_path="test/entity1.md", content_type="text/markdown", @@ -837,7 +837,7 @@ async def test_get_all_file_paths_performance(entity_repository: EntityRepositor entity2 = Entity( project_id=entity_repository.project_id, title="Entity 2", - entity_type="test", + note_type="test", permalink="test/entity2", file_path="test/entity2.md", content_type="text/markdown", @@ -889,7 +889,7 @@ async def test_get_all_file_paths_project_isolation( entity1 = Entity( project_id=entity_repository.project_id, title="Project 1 File", - entity_type="test", + note_type="test", permalink="test/file1", file_path="test/file1.md", content_type="text/markdown", @@ -908,7 +908,7 @@ async def test_get_all_file_paths_project_isolation( entity2 = Entity( project_id=project2.id, title="Project 2 File", - entity_type="test", + note_type="test", permalink="test/file2", file_path="test/file2.md", content_type="text/markdown", @@ -951,7 +951,7 @@ async def test_permalink_exists_project_isolation( entity1 = Entity( project_id=entity_repository.project_id, title="Project 1 Entity", - entity_type="test", + note_type="test", permalink="test/entity1", file_path="test/entity1.md", content_type="text/markdown", @@ -968,7 +968,7 @@ async def test_permalink_exists_project_isolation( entity2 = Entity( project_id=project2.id, title="Project 2 Entity", - entity_type="test", + note_type="test", permalink="test/entity2", file_path="test/entity2.md", content_type="text/markdown", @@ -1019,7 +1019,7 @@ async def test_get_all_permalinks(entity_repository: EntityRepository, session_m entity1 = Entity( project_id=entity_repository.project_id, title="Entity 1", - entity_type="test", + note_type="test", permalink="test/entity1", file_path="test/entity1.md", content_type="text/markdown", @@ -1029,7 +1029,7 @@ async def test_get_all_permalinks(entity_repository: EntityRepository, session_m entity2 = Entity( project_id=entity_repository.project_id, title="Entity 2", - entity_type="test", + note_type="test", permalink="test/entity2", file_path="test/entity2.md", content_type="text/markdown", @@ -1055,7 +1055,7 @@ async def test_get_permalink_to_file_path_map(entity_repository: EntityRepositor entity1 = Entity( project_id=entity_repository.project_id, title="Entity 1", - entity_type="test", + note_type="test", permalink="test/entity1", file_path="test/entity1.md", content_type="text/markdown", @@ -1065,7 +1065,7 @@ async def test_get_permalink_to_file_path_map(entity_repository: EntityRepositor entity2 = Entity( project_id=entity_repository.project_id, title="Entity 2", - entity_type="test", + note_type="test", permalink="test/entity2", file_path="test/entity2.md", content_type="text/markdown", @@ -1088,7 +1088,7 @@ async def test_get_file_path_to_permalink_map(entity_repository: EntityRepositor entity1 = Entity( project_id=entity_repository.project_id, title="Entity 1", - entity_type="test", + note_type="test", permalink="test/entity1", file_path="test/entity1.md", content_type="text/markdown", @@ -1098,7 +1098,7 @@ async def test_get_file_path_to_permalink_map(entity_repository: EntityRepositor entity2 = Entity( project_id=entity_repository.project_id, title="Entity 2", - entity_type="test", + note_type="test", permalink="test/entity2", file_path="test/entity2.md", content_type="text/markdown", diff --git a/tests/repository/test_entity_repository_upsert.py b/tests/repository/test_entity_repository_upsert.py index 072416e78..351179d34 100644 --- a/tests/repository/test_entity_repository_upsert.py +++ b/tests/repository/test_entity_repository_upsert.py @@ -15,7 +15,7 @@ async def test_upsert_entity_new_entity(entity_repository: EntityRepository): entity = Entity( project_id=entity_repository.project_id, title="Test Entity", - entity_type="note", + note_type="note", permalink="test/test-entity", file_path="test/test-entity.md", content_type="text/markdown", @@ -38,7 +38,7 @@ async def test_upsert_entity_same_file_update(entity_repository: EntityRepositor entity1 = Entity( project_id=entity_repository.project_id, title="Original Title", - entity_type="note", + note_type="note", permalink="test/test-entity", file_path="test/test-entity.md", content_type="text/markdown", @@ -53,7 +53,7 @@ async def test_upsert_entity_same_file_update(entity_repository: EntityRepositor entity2 = Entity( project_id=entity_repository.project_id, title="Updated Title", - entity_type="note", + note_type="note", permalink="test/test-entity", # Same permalink file_path="test/test-entity.md", # Same file_path content_type="text/markdown", @@ -77,7 +77,7 @@ async def test_upsert_entity_permalink_conflict_different_file(entity_repository entity1 = Entity( project_id=entity_repository.project_id, title="First Entity", - entity_type="note", + note_type="note", permalink="test/shared-permalink", file_path="test/first-file.md", content_type="text/markdown", @@ -92,7 +92,7 @@ async def test_upsert_entity_permalink_conflict_different_file(entity_repository entity2 = Entity( project_id=entity_repository.project_id, title="Second Entity", - entity_type="note", + note_type="note", permalink="test/shared-permalink", # Same permalink file_path="test/second-file.md", # Different file_path content_type="text/markdown", @@ -126,7 +126,7 @@ async def test_upsert_entity_multiple_permalink_conflicts(entity_repository: Ent entity = Entity( project_id=entity_repository.project_id, title=f"Entity {i + 1}", - entity_type="note", + note_type="note", permalink=base_permalink, # All try to use same permalink file_path=f"test/file-{i + 1}.md", # Different file paths content_type="text/markdown", @@ -160,7 +160,7 @@ async def test_upsert_entity_race_condition_file_path(entity_repository: EntityR entity1 = Entity( project_id=entity_repository.project_id, title="Original Entity", - entity_type="note", + note_type="note", permalink="test/original", file_path="test/race-file.md", content_type="text/markdown", @@ -176,7 +176,7 @@ async def test_upsert_entity_race_condition_file_path(entity_repository: EntityR entity2 = Entity( project_id=entity_repository.project_id, title="Race Condition Test", - entity_type="note", + note_type="note", permalink="test/race-entity", file_path="test/race-file.md", # Same file path as entity1 content_type="text/markdown", @@ -208,7 +208,7 @@ async def test_upsert_entity_gap_in_suffixes(entity_repository: EntityRepository entity = Entity( project_id=entity_repository.project_id, title=f"Entity {i + 1}", - entity_type="note", + note_type="note", permalink=permalink, file_path=f"test/gap-file-{i + 1}.md", content_type="text/markdown", @@ -221,7 +221,7 @@ async def test_upsert_entity_gap_in_suffixes(entity_repository: EntityRepository new_entity = Entity( project_id=entity_repository.project_id, title="Gap Filler", - entity_type="note", + note_type="note", permalink=base_permalink, # Will conflict file_path="test/gap-new-file.md", content_type="text/markdown", @@ -275,7 +275,7 @@ async def test_upsert_entity_project_scoping_isolation(session_maker): entity1 = Entity( project_id=project1.id, title="Shared Entity", - entity_type="note", + note_type="note", permalink="docs/shared-name", file_path="docs/shared-name.md", content_type="text/markdown", @@ -286,7 +286,7 @@ async def test_upsert_entity_project_scoping_isolation(session_maker): entity2 = Entity( project_id=project2.id, title="Shared Entity", - entity_type="note", + note_type="note", permalink="docs/shared-name", # Same permalink file_path="docs/shared-name.md", # Same file_path content_type="text/markdown", @@ -311,7 +311,7 @@ async def test_upsert_entity_project_scoping_isolation(session_maker): entity1_update = Entity( project_id=project1.id, title="Updated Shared Entity", - entity_type="note", + note_type="note", permalink="docs/shared-name", file_path="docs/shared-name.md", content_type="text/markdown", @@ -322,7 +322,7 @@ async def test_upsert_entity_project_scoping_isolation(session_maker): entity2_update = Entity( project_id=project2.id, title="Also Updated Shared Entity", - entity_type="note", + note_type="note", permalink="docs/shared-name", file_path="docs/shared-name.md", content_type="text/markdown", @@ -389,7 +389,7 @@ async def test_upsert_entity_permalink_conflict_within_project_only(session_make entity1 = Entity( project_id=project1.id, title="Original Entity", - entity_type="note", + note_type="note", permalink="test/conflict-permalink", file_path="test/original.md", content_type="text/markdown", @@ -404,7 +404,7 @@ async def test_upsert_entity_permalink_conflict_within_project_only(session_make entity2 = Entity( project_id=project2.id, title="Cross-Project Entity", - entity_type="note", + note_type="note", permalink="test/conflict-permalink", # Same permalink, different project file_path="test/cross-project.md", content_type="text/markdown", @@ -420,7 +420,7 @@ async def test_upsert_entity_permalink_conflict_within_project_only(session_make entity3 = Entity( project_id=project1.id, title="Conflict Entity", - entity_type="note", + note_type="note", permalink="test/conflict-permalink", # Same permalink, same project file_path="test/conflict.md", content_type="text/markdown", @@ -449,7 +449,7 @@ async def test_upsert_entity_with_invalid_project_id(entity_repository: EntityRe # Create entity with non-existent project_id entity = Entity( title="Test Entity", - entity_type="note", + note_type="note", file_path="test.md", permalink="test", project_id=99999, # This project doesn't exist diff --git a/tests/repository/test_entity_upsert_issue_187.py b/tests/repository/test_entity_upsert_issue_187.py index 47772d000..7c8a4e9cb 100644 --- a/tests/repository/test_entity_upsert_issue_187.py +++ b/tests/repository/test_entity_upsert_issue_187.py @@ -18,7 +18,7 @@ async def test_upsert_entity_with_observations_conflict(entity_repository: Entit entity1 = Entity( project_id=entity_repository.project_id, title="Original Title", - entity_type="note", + note_type="note", permalink="debugging/backup-system/coderabbit-feedback-resolution", file_path="debugging/backup-system/CodeRabbit Feedback Resolution - Backup System Issues.md", content_type="text/markdown", @@ -47,7 +47,7 @@ async def test_upsert_entity_with_observations_conflict(entity_repository: Entit entity2 = Entity( project_id=entity_repository.project_id, title="Updated Title", - entity_type="note", + note_type="note", permalink="debugging/backup-system/coderabbit-feedback-resolution", # Same permalink file_path="debugging/backup-system/CodeRabbit Feedback Resolution - Backup System Issues.md", # Same file_path content_type="text/markdown", @@ -99,7 +99,7 @@ async def test_upsert_entity_repeated_sync_same_file(entity_repository: EntityRe entity1 = Entity( project_id=entity_repository.project_id, title="Complete Process for Uploading New Training Videos", - entity_type="note", + note_type="note", permalink=permalink, file_path=file_path, content_type="text/markdown", @@ -116,7 +116,7 @@ async def test_upsert_entity_repeated_sync_same_file(entity_repository: EntityRe entity_new = Entity( project_id=entity_repository.project_id, title="Complete Process for Uploading New Training Videos", - entity_type="note", + note_type="note", permalink=permalink, file_path=file_path, content_type="text/markdown", diff --git a/tests/repository/test_hybrid_rrf.py b/tests/repository/test_hybrid_rrf.py index 0e992af14..6723eff73 100644 --- a/tests/repository/test_hybrid_rrf.py +++ b/tests/repository/test_hybrid_rrf.py @@ -86,7 +86,7 @@ def _distance_to_similarity(self, distance: float) -> float: permalink=None, permalink_match=None, title=None, - types=None, + note_types=None, after_date=None, search_item_types=None, metadata_filters=None, diff --git a/tests/repository/test_metadata_filters_edge_cases.py b/tests/repository/test_metadata_filters_edge_cases.py index 809b0fb3b..e6e8e884b 100644 --- a/tests/repository/test_metadata_filters_edge_cases.py +++ b/tests/repository/test_metadata_filters_edge_cases.py @@ -26,7 +26,7 @@ async def _index_entity_with_metadata(search_repository, session_maker, title, e entity = Entity( project_id=search_repository.project_id, title=title, - entity_type="note", + note_type="note", permalink=permalink, file_path=file_path, content_type="text/markdown", @@ -46,7 +46,7 @@ async def _index_entity_with_metadata(search_repository, session_maker, title, e permalink=entity.permalink, file_path=entity.file_path, entity_id=entity.id, - metadata={"entity_type": entity.entity_type}, + metadata={"note_type": entity.note_type}, created_at=entity.created_at, updated_at=entity.updated_at, project_id=search_repository.project_id, diff --git a/tests/repository/test_observation_repository.py b/tests/repository/test_observation_repository.py index 3bf1b499c..27b863995 100644 --- a/tests/repository/test_observation_repository.py +++ b/tests/repository/test_observation_repository.py @@ -95,7 +95,7 @@ async def test_delete_observations(session_maker: async_sessionmaker, repo, test entity = Entity( project_id=test_project.id, title="test_entity", - entity_type="test", + note_type="test", permalink="test/test-entity", file_path="test/test_entity.md", content_type="text/markdown", @@ -137,7 +137,7 @@ async def test_delete_observation_by_id( entity = Entity( project_id=test_project.id, title="test_entity", - entity_type="test", + note_type="test", permalink="test/test-entity", file_path="test/test_entity.md", content_type="text/markdown", @@ -174,7 +174,7 @@ async def test_delete_observation_by_content( entity = Entity( project_id=test_project.id, title="test_entity", - entity_type="test", + note_type="test", permalink="test/test-entity", file_path="test/test_entity.md", content_type="text/markdown", @@ -215,7 +215,7 @@ async def test_find_by_category(session_maker: async_sessionmaker, repo, test_pr entity = Entity( project_id=test_project.id, title="test_entity", - entity_type="test", + note_type="test", permalink="test/test-entity", file_path="test/test_entity.md", content_type="text/markdown", @@ -276,7 +276,7 @@ async def test_observation_categories( entity = Entity( project_id=test_project.id, title="test_entity", - entity_type="test", + note_type="test", permalink="test/test-entity", file_path="test/test_entity.md", content_type="text/markdown", @@ -344,7 +344,7 @@ async def test_find_by_category_case_sensitivity( entity = Entity( project_id=test_project.id, title="test_entity", - entity_type="test", + note_type="test", permalink="test/test-entity", file_path="test/test_entity.md", content_type="text/markdown", @@ -389,7 +389,7 @@ async def test_observation_permalink_truncates_long_content( entity = Entity( project_id=test_project.id, title="test_entity", - entity_type="test", + note_type="test", permalink="test/test-entity", file_path="test/test_entity.md", content_type="text/markdown", @@ -440,7 +440,7 @@ async def test_observation_permalink_short_content_unchanged( entity = Entity( project_id=test_project.id, title="test_entity", - entity_type="test", + note_type="test", permalink="test/test-entity", file_path="test/test_entity.md", content_type="text/markdown", diff --git a/tests/repository/test_postgres_search_repository.py b/tests/repository/test_postgres_search_repository.py index f521b7f9e..fe6eabe8b 100644 --- a/tests/repository/test_postgres_search_repository.py +++ b/tests/repository/test_postgres_search_repository.py @@ -79,7 +79,7 @@ async def test_postgres_search_repository_index_and_search(session_maker, test_p permalink="docs/coffee-brewing", file_path="docs/coffee-brewing.md", type="entity", - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=now, updated_at=now, ) @@ -109,8 +109,8 @@ async def test_postgres_search_repository_index_and_search(session_maker, test_p results = await repo.search(search_item_types=[SearchItemType.ENTITY]) assert any(r.permalink == "docs/coffee-brewing" for r in results) - # Entity type filter via metadata JSONB containment - results = await repo.search(types=["note"]) + # Note type filter via metadata JSONB containment + results = await repo.search(note_types=["note"]) assert any(r.permalink == "docs/coffee-brewing" for r in results) # Date filter (also exercises order_by_clause) @@ -150,7 +150,7 @@ async def test_postgres_search_repository_bulk_index_items_and_prepare_terms( permalink="docs/pour-over", file_path="docs/pour-over.md", type="entity", - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=now, updated_at=now, ), @@ -163,7 +163,7 @@ async def test_postgres_search_repository_bulk_index_items_and_prepare_terms( permalink="docs/french-press", file_path="docs/french-press.md", type="entity", - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=now, updated_at=now, ), @@ -194,7 +194,7 @@ async def test_postgres_search_repository_wildcard_text_and_permalink_match_exac permalink="docs/x", file_path="docs/x.md", type="entity", - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=now, updated_at=now, ) @@ -254,7 +254,7 @@ async def test_bulk_index_items_strips_nul_bytes(session_maker, test_project): permalink="test/nul-row", file_path="test/nul.md", type="entity", - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=now, updated_at=now, ) @@ -280,7 +280,7 @@ async def test_index_item_strips_nul_bytes(session_maker, test_project): permalink="test/nul-single", file_path="test/nul-single.md", type="entity", - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=now, updated_at=now, ) @@ -342,7 +342,7 @@ async def test_postgres_semantic_vector_search_returns_ranked_entities(session_m file_path="specs/authentication.md", type=SearchItemType.ENTITY.value, entity_id=401, - metadata={"entity_type": "spec"}, + metadata={"note_type": "spec"}, created_at=now, updated_at=now, ), @@ -356,7 +356,7 @@ async def test_postgres_semantic_vector_search_returns_ranked_entities(session_m file_path="specs/migrations.md", type=SearchItemType.ENTITY.value, entity_id=402, - metadata={"entity_type": "spec"}, + metadata={"note_type": "spec"}, created_at=now, updated_at=now, ), @@ -408,7 +408,7 @@ async def test_postgres_semantic_hybrid_search_combines_fts_and_vector(session_m file_path="specs/task-queue-worker.md", type=SearchItemType.ENTITY.value, entity_id=411, - metadata={"entity_type": "spec"}, + metadata={"note_type": "spec"}, created_at=now, updated_at=now, ), @@ -422,7 +422,7 @@ async def test_postgres_semantic_hybrid_search_combines_fts_and_vector(session_m file_path="specs/search-index.md", type=SearchItemType.ENTITY.value, entity_id=412, - metadata={"entity_type": "spec"}, + metadata={"note_type": "spec"}, created_at=now, updated_at=now, ), diff --git a/tests/repository/test_relation_repository.py b/tests/repository/test_relation_repository.py index 01e08ccc2..1bfc07cdd 100644 --- a/tests/repository/test_relation_repository.py +++ b/tests/repository/test_relation_repository.py @@ -17,7 +17,7 @@ async def source_entity(session_maker, test_project: Project): entity = Entity( project_id=test_project.id, title="test_source", - entity_type="test", + note_type="test", permalink="source/test-source", file_path="source/test_source.md", content_type="text/markdown", @@ -36,7 +36,7 @@ async def target_entity(session_maker, test_project: Project): entity = Entity( project_id=test_project.id, title="test_target", - entity_type="test", + note_type="test", permalink="target/test-target", file_path="target/test_target.md", content_type="text/markdown", @@ -79,7 +79,7 @@ async def related_entity(entity_repository): """Create a second entity for testing relations""" entity_data = { "title": "Related Entity", - "entity_type": "test", + "note_type": "test", "permalink": "test/related-entity", "file_path": "test/related_entity.md", "summary": "A related test entity", diff --git a/tests/repository/test_search_repository.py b/tests/repository/test_search_repository.py index 1466b126d..8fe4820d7 100644 --- a/tests/repository/test_search_repository.py +++ b/tests/repository/test_search_repository.py @@ -26,7 +26,7 @@ async def search_entity(session_maker, test_project: Project): entity = Entity( project_id=test_project.id, title="Search Test Entity", - entity_type="test", + note_type="test", permalink="test/search-test-entity", file_path="test/search_test_entity.md", content_type="text/markdown", @@ -68,7 +68,7 @@ async def second_entity(session_maker, second_project: Project): entity = Entity( project_id=second_project.id, title="Second Project Entity", - entity_type="test", + note_type="test", permalink="test/second-project-entity", file_path="test/second_project_entity.md", content_type="text/markdown", @@ -128,7 +128,7 @@ async def test_init_search_index_preserves_data(search_repository, search_entity permalink=search_entity.permalink, file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -162,7 +162,7 @@ async def test_index_item(search_repository, search_entity): permalink=search_entity.permalink, file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -197,7 +197,7 @@ async def test_index_item_upsert_on_duplicate_permalink(search_repository, searc permalink=search_entity.permalink, file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -220,7 +220,7 @@ async def test_index_item_upsert_on_duplicate_permalink(search_repository, searc permalink=search_entity.permalink, # Same permalink! file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -254,7 +254,7 @@ async def test_bulk_index_items_upsert_on_duplicate_permalink(search_repository, permalink=search_entity.permalink, file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -276,7 +276,7 @@ async def test_bulk_index_items_upsert_on_duplicate_permalink(search_repository, permalink=search_entity.permalink, # Same permalink! file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -305,7 +305,7 @@ async def test_project_isolation( permalink=search_entity.permalink, file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -320,7 +320,7 @@ async def test_project_isolation( permalink=second_entity.permalink, file_path=second_entity.file_path, entity_id=second_entity.id, - metadata={"entity_type": second_entity.entity_type}, + metadata={"note_type": second_entity.note_type}, created_at=second_entity.created_at, updated_at=second_entity.updated_at, project_id=second_project_repository.project_id, @@ -364,7 +364,7 @@ async def test_delete_by_permalink(search_repository, search_entity): permalink=search_entity.permalink, file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -397,7 +397,7 @@ async def test_delete_by_entity_id(search_repository, search_entity): permalink=search_entity.permalink, file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -725,7 +725,7 @@ async def test_version_string_search_integration(self, search_repository, search permalink=search_entity.permalink, file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -759,7 +759,7 @@ async def test_wildcard_only_search(self, search_repository, search_entity): permalink=search_entity.permalink, file_path=search_entity.file_path, entity_id=search_entity.id, - metadata={"entity_type": search_entity.entity_type}, + metadata={"note_type": search_entity.note_type}, created_at=search_entity.created_at, updated_at=search_entity.updated_at, project_id=search_repository.project_id, @@ -847,7 +847,7 @@ async def _index_entity_with_metadata(search_repository, session_maker, title, e entity = Entity( project_id=search_repository.project_id, title=title, - entity_type="note", + note_type="note", permalink=permalink, file_path=file_path, content_type="text/markdown", @@ -867,7 +867,7 @@ async def _index_entity_with_metadata(search_repository, session_maker, title, e permalink=entity.permalink, file_path=entity.file_path, entity_id=entity.id, - metadata={"entity_type": entity.entity_type}, + metadata={"note_type": entity.note_type}, created_at=entity.created_at, updated_at=entity.updated_at, project_id=search_repository.project_id, diff --git a/tests/repository/test_search_repository_edit_bug_fix.py b/tests/repository/test_search_repository_edit_bug_fix.py index e33dc0f9f..864a8f3c2 100644 --- a/tests/repository/test_search_repository_edit_bug_fix.py +++ b/tests/repository/test_search_repository_edit_bug_fix.py @@ -99,7 +99,7 @@ async def test_index_item_respects_project_isolation_during_edit(): permalink=same_permalink, file_path="notes/test_note.md", entity_id=1, - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), project_id=project1_id, @@ -114,7 +114,7 @@ async def test_index_item_respects_project_isolation_during_edit(): permalink=same_permalink, # SAME permalink as project 1 file_path="notes/test_note.md", entity_id=2, - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), project_id=project2_id, @@ -146,7 +146,7 @@ async def test_index_item_respects_project_isolation_during_edit(): permalink=same_permalink, file_path="notes/test_note.md", entity_id=1, - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), project_id=project1_id, @@ -222,7 +222,7 @@ async def test_index_item_updates_existing_record_same_project(): permalink=permalink, file_path="test/my_note.md", entity_id=1, - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), project_id=project_id, @@ -246,7 +246,7 @@ async def test_index_item_updates_existing_record_same_project(): permalink=permalink, # Same permalink file_path="test/my_note.md", entity_id=1, - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), project_id=project_id, diff --git a/tests/repository/test_search_text_with_metadata_filters.py b/tests/repository/test_search_text_with_metadata_filters.py index 0d746562a..7ac878ab3 100644 --- a/tests/repository/test_search_text_with_metadata_filters.py +++ b/tests/repository/test_search_text_with_metadata_filters.py @@ -20,7 +20,7 @@ async def _index_entity(search_repository, session_maker, title: str, status: st entity = Entity( project_id=search_repository.project_id, title=title, - entity_type="note", + note_type="note", permalink=permalink, file_path=file_path, content_type="text/markdown", @@ -42,7 +42,7 @@ async def _index_entity(search_repository, session_maker, title: str, status: st permalink=entity.permalink, file_path=entity.file_path, entity_id=entity.id, - metadata={"entity_type": entity.entity_type}, + metadata={"note_type": entity.note_type}, created_at=entity.created_at, updated_at=entity.updated_at, ) diff --git a/tests/repository/test_sqlite_vector_search_repository.py b/tests/repository/test_sqlite_vector_search_repository.py index a7e85a5f5..e032e2a1c 100644 --- a/tests/repository/test_sqlite_vector_search_repository.py +++ b/tests/repository/test_sqlite_vector_search_repository.py @@ -52,7 +52,7 @@ def _entity_row( title=title, permalink=permalink, file_path=f"{permalink}.md", - metadata={"entity_type": "spec"}, + metadata={"note_type": "spec"}, entity_id=entity_id, content_stems=content_stems, content_snippet=content_stems, diff --git a/tests/repository/test_vector_pagination.py b/tests/repository/test_vector_pagination.py index 4832e0792..842502396 100644 --- a/tests/repository/test_vector_pagination.py +++ b/tests/repository/test_vector_pagination.py @@ -116,7 +116,7 @@ async def run_page(offset, limit): permalink=None, permalink_match=None, title=None, - types=None, + note_types=None, after_date=None, search_item_types=None, metadata_filters=None, diff --git a/tests/repository/test_vector_threshold.py b/tests/repository/test_vector_threshold.py index 7ea3d48b5..41a73d99a 100644 --- a/tests/repository/test_vector_threshold.py +++ b/tests/repository/test_vector_threshold.py @@ -89,7 +89,7 @@ async def fake_scoped_session(session_maker): permalink=None, permalink_match=None, title=None, - types=None, + note_types=None, after_date=None, search_item_types=None, metadata_filters=None, diff --git a/tests/schema/test_inference.py b/tests/schema/test_inference.py index 91f07933a..2fd6e2b05 100644 --- a/tests/schema/test_inference.py +++ b/tests/schema/test_inference.py @@ -236,14 +236,14 @@ def test_excluded_fields_not_in_schema(self): class TestInferRelationTargetType: """Verify that relation target type comes from the individual relation, - not from the source note's entity_type.""" + not from the source note's note_type.""" def test_target_type_from_relation_data(self): - """Relations with target_entity_type produce correct schema suggestions.""" + """Relations with target_note_type produce correct schema suggestions.""" notes = [ _note( f"n{i}", - relations=[Rel("works_at", f"Org{i}", target_entity_type="organization")], + relations=[Rel("works_at", f"Org{i}", target_note_type="organization")], ) for i in range(3) ] @@ -252,7 +252,7 @@ def test_target_type_from_relation_data(self): assert works_at.target_type == "organization" def test_target_type_none_when_unresolved(self): - """Relations without target_entity_type produce None target_type.""" + """Relations without target_note_type produce None target_type.""" notes = [_note(f"n{i}", relations=[Rel("knows", f"P{i}")]) for i in range(3)] result = infer_schema("Person", notes) knows = next(f for f in result.field_frequencies if f.name == "knows") @@ -261,9 +261,9 @@ def test_target_type_none_when_unresolved(self): def test_mixed_target_types_uses_most_common(self): """When relations point to different types, the most common wins.""" notes = [ - _note("n0", relations=[Rel("works_at", "OrgA", target_entity_type="organization")]), - _note("n1", relations=[Rel("works_at", "OrgB", target_entity_type="organization")]), - _note("n2", relations=[Rel("works_at", "SchoolC", target_entity_type="school")]), + _note("n0", relations=[Rel("works_at", "OrgA", target_note_type="organization")]), + _note("n1", relations=[Rel("works_at", "OrgB", target_note_type="organization")]), + _note("n2", relations=[Rel("works_at", "SchoolC", target_note_type="school")]), ] result = infer_schema("Person", notes) works_at = next(f for f in result.field_frequencies if f.name == "works_at") diff --git a/tests/schemas/test_schemas.py b/tests/schemas/test_schemas.py index 42f9e7db0..6d7c44048 100644 --- a/tests/schemas/test_schemas.py +++ b/tests/schemas/test_schemas.py @@ -19,20 +19,20 @@ def test_entity_project_name(): """Test creating EntityIn with minimal required fields.""" - data = {"title": "Test Entity", "directory": "test", "entity_type": "knowledge"} + data = {"title": "Test Entity", "directory": "test", "note_type": "knowledge"} entity = Entity.model_validate(data) assert entity.file_path == os.path.join("test", "Test Entity.md") assert entity.permalink == "test/test-entity" - assert entity.entity_type == "knowledge" + assert entity.note_type == "knowledge" def test_entity_project_id(): """Test creating EntityIn with minimal required fields.""" - data = {"project": 2, "title": "Test Entity", "directory": "test", "entity_type": "knowledge"} + data = {"project": 2, "title": "Test Entity", "directory": "test", "note_type": "knowledge"} entity = Entity.model_validate(data) assert entity.file_path == os.path.join("test", "Test Entity.md") assert entity.permalink == "test/test-entity" - assert entity.entity_type == "knowledge" + assert entity.note_type == "knowledge" def test_entity_non_markdown(): @@ -40,19 +40,19 @@ def test_entity_non_markdown(): data = { "title": "Test Entity.txt", "directory": "test", - "entity_type": "file", + "note_type": "file", "content_type": "text/plain", } entity = Entity.model_validate(data) assert entity.file_path == os.path.join("test", "Test Entity.txt") assert entity.permalink == "test/test-entity" - assert entity.entity_type == "file" + assert entity.note_type == "file" def test_entity_in_validation(): """Test validation errors for EntityIn.""" with pytest.raises(ValidationError): - Entity.model_validate({"entity_type": "test"}) # Missing required fields + Entity.model_validate({"note_type": "test"}) # Missing required fields def test_relation_in_validation(): @@ -141,7 +141,7 @@ def test_entity_out_from_attributes(): "title": "Test Entity", "permalink": "test/test", "file_path": "test", - "entity_type": "knowledge", + "note_type": "knowledge", "content_type": "text/markdown", "observations": [ { @@ -183,7 +183,7 @@ def test_entity_response_with_none_permalink(): "title": "Test Entity", "permalink": None, # This should not cause validation errors "file_path": "test/test-entity.md", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "observations": [], "relations": [], @@ -196,7 +196,7 @@ def test_entity_response_with_none_permalink(): assert entity.permalink is None assert entity.title == "Test Entity" assert entity.file_path == "test/test-entity.md" - assert entity.entity_type == "note" + assert entity.note_type == "note" assert len(entity.observations) == 0 assert len(entity.relations) == 0 diff --git a/tests/schemas/test_search.py b/tests/schemas/test_search.py index dc3111f69..b1fe51e89 100644 --- a/tests/schemas/test_search.py +++ b/tests/schemas/test_search.py @@ -34,11 +34,11 @@ def test_search_filters(): query = SearchQuery( text="search", entity_types=[SearchItemType.ENTITY], - types=["component"], + note_types=["component"], after_date=datetime(2024, 1, 1), ) assert query.entity_types == [SearchItemType.ENTITY] - assert query.types == ["component"] + assert query.note_types == ["component"] assert query.after_date == "2024-01-01T00:00:00" @@ -58,13 +58,13 @@ def test_search_result(): type=SearchItemType.ENTITY, entity="some_entity", score=0.8, - metadata={"entity_type": "component"}, + metadata={"note_type": "component"}, permalink="specs/search", file_path="specs/search.md", ) assert result.type == SearchItemType.ENTITY assert result.score == 0.8 - assert result.metadata == {"entity_type": "component"} + assert result.metadata == {"note_type": "component"} def test_observation_result(): diff --git a/tests/services/test_context_service.py b/tests/services/test_context_service.py index 72e618891..1b37dacd0 100644 --- a/tests/services/test_context_service.py +++ b/tests/services/test_context_service.py @@ -250,7 +250,7 @@ async def test_project_isolation_in_find_related(session_maker, app_config): # Create entities in project1 entity1_p1 = Entity( title="Entity1_P1", - entity_type="document", + note_type="document", content_type="text/markdown", project_id=project1.id, permalink="project1/entity1", @@ -260,7 +260,7 @@ async def test_project_isolation_in_find_related(session_maker, app_config): ) entity2_p1 = Entity( title="Entity2_P1", - entity_type="document", + note_type="document", content_type="text/markdown", project_id=project1.id, permalink="project1/entity2", @@ -272,7 +272,7 @@ async def test_project_isolation_in_find_related(session_maker, app_config): # Create entities in project2 entity1_p2 = Entity( title="Entity1_P2", - entity_type="document", + note_type="document", content_type="text/markdown", project_id=project2.id, permalink="project2/entity1", diff --git a/tests/services/test_directory_service.py b/tests/services/test_directory_service.py index 4610f0fe1..65203355c 100644 --- a/tests/services/test_directory_service.py +++ b/tests/services/test_directory_service.py @@ -39,7 +39,7 @@ async def test_directory_tree(directory_service: DirectoryService, test_graph): assert node_0.type == "directory" assert node_0.content_type is None assert node_0.entity_id is None - assert node_0.entity_type is None + assert node_0.note_type is None assert node_0.title is None assert node_0.directory_path == "/test" assert node_0.has_children is True @@ -51,7 +51,7 @@ async def test_directory_tree(directory_service: DirectoryService, test_graph): assert node_file.type == "file" assert node_file.content_type == "text/markdown" assert node_file.entity_id == 1 - assert node_file.entity_type == "deeper" + assert node_file.note_type == "deeper" assert node_file.title == "Deeper Entity" assert node_file.permalink == "test-project/test/deeper-entity" assert node_file.directory_path == "/test/Deeper Entity.md" @@ -250,7 +250,7 @@ async def test_directory_structure(directory_service: DirectoryService, test_gra # Verify no file metadata is present assert node_0.content_type is None assert node_0.entity_id is None - assert node_0.entity_type is None + assert node_0.note_type is None assert node_0.title is None assert node_0.permalink is None diff --git a/tests/services/test_entity_service.py b/tests/services/test_entity_service.py index e08126134..28d42cdec 100644 --- a/tests/services/test_entity_service.py +++ b/tests/services/test_entity_service.py @@ -27,7 +27,7 @@ async def test_create_entity( entity_data = EntitySchema( title="Test Entity", directory="", - entity_type="test", + note_type="test", ) # Save expected permalink before create_entity mutates entity_data._permalink expected_permalink = f"{generate_permalink(project_config.name)}/{entity_data.permalink}" @@ -39,14 +39,14 @@ async def test_create_entity( assert isinstance(entity, EntityModel) assert entity.permalink == expected_permalink assert entity.file_path == entity_data.file_path - assert entity.entity_type == "test" + assert entity.note_type == "test" assert entity.created_at is not None assert len(entity.relations) == 0 # Verify we can retrieve it using permalink retrieved = await entity_service.get_by_permalink(entity.permalink) assert retrieved.title == "Test Entity" - assert retrieved.entity_type == "test" + assert retrieved.note_type == "test" assert retrieved.created_at is not None # Verify file was written @@ -59,7 +59,7 @@ async def test_create_entity( # Verify frontmatter contents assert metadata["permalink"] == entity.permalink - assert metadata["type"] == entity.entity_type + assert metadata["type"] == entity.note_type @pytest.mark.asyncio @@ -70,7 +70,7 @@ async def test_create_entity_file_exists( entity_data = EntitySchema( title="Test Entity", directory="", - entity_type="test", + note_type="test", content="first", ) @@ -90,7 +90,7 @@ async def test_create_entity_file_exists( entity_data = EntitySchema( title="Test Entity", directory="", - entity_type="test", + note_type="test", content="second", ) @@ -109,7 +109,7 @@ async def test_create_entity_unique_permalink( entity_data = EntitySchema( title="Test Entity", directory="test", - entity_type="test", + note_type="test", ) entity = await entity_service.create_entity(entity_data) @@ -143,14 +143,14 @@ async def test_get_by_permalink(entity_service: EntityService): entity1_data = EntitySchema( title="TestEntity1", directory="test", - entity_type="test", + note_type="test", ) entity1 = await entity_service.create_entity(entity1_data) entity2_data = EntitySchema( title="TestEntity2", directory="test", - entity_type="test", + note_type="test", ) entity2 = await entity_service.create_entity(entity2_data) @@ -158,13 +158,13 @@ async def test_get_by_permalink(entity_service: EntityService): found = await entity_service.get_by_permalink(entity1_data.permalink) assert found is not None assert found.id == entity1.id - assert found.entity_type == entity1.entity_type + assert found.note_type == entity1.note_type # Find by type2 and name found = await entity_service.get_by_permalink(entity2_data.permalink) assert found is not None assert found.id == entity2.id - assert found.entity_type == entity2.entity_type + assert found.note_type == entity2.note_type # Test not found case with pytest.raises(EntityNotFoundError): @@ -177,7 +177,7 @@ async def test_get_entity_success(entity_service: EntityService): entity_data = EntitySchema( title="TestEntity", directory="test", - entity_type="test", + note_type="test", ) await entity_service.create_entity(entity_data) @@ -186,7 +186,7 @@ async def test_get_entity_success(entity_service: EntityService): assert isinstance(retrieved, EntityModel) assert retrieved.title == "TestEntity" - assert retrieved.entity_type == "test" + assert retrieved.note_type == "test" @pytest.mark.asyncio @@ -195,7 +195,7 @@ async def test_delete_entity_success(entity_service: EntityService): entity_data = EntitySchema( title="TestEntity", directory="test", - entity_type="test", + note_type="test", ) await entity_service.create_entity(entity_data) @@ -214,7 +214,7 @@ async def test_delete_entity_by_id(entity_service: EntityService): entity_data = EntitySchema( title="TestEntity", directory="test", - entity_type="test", + note_type="test", ) created = await entity_service.create_entity(entity_data) @@ -247,7 +247,7 @@ async def test_create_entity_with_special_chars(entity_service: EntityService): entity_data = EntitySchema( title=name, directory="test", - entity_type="test", + note_type="test", ) entity = await entity_service.create_entity(entity_data) @@ -264,12 +264,12 @@ async def test_get_entities_by_permalinks(entity_service: EntityService): entity1_data = EntitySchema( title="Entity1", directory="test", - entity_type="test", + note_type="test", ) entity2_data = EntitySchema( title="Entity2", directory="test", - entity_type="test", + note_type="test", ) await entity_service.create_entity(entity1_data) await entity_service.create_entity(entity2_data) @@ -297,7 +297,7 @@ async def test_get_entities_some_not_found(entity_service: EntityService): entity_data = EntitySchema( title="Entity1", directory="test", - entity_type="test", + note_type="test", ) await entity_service.create_entity(entity_data) @@ -315,7 +315,7 @@ async def test_get_entity_path(entity_service: EntityService): entity = EntityModel( permalink="test-entity", file_path="test-entity.md", - entity_type="test", + note_type="test", ) path = entity_service.file_service.get_entity_path(entity) assert path == Path(entity_service.file_service.base_path / "test-entity.md") @@ -328,7 +328,7 @@ async def test_update_note_entity_content(entity_service: EntityService, file_se schema = EntitySchema( title="test", directory="test", - entity_type="note", + note_type="note", entity_metadata={"status": "draft"}, ) @@ -383,7 +383,7 @@ async def test_fast_write_and_reindex_entity( schema = EntitySchema( title="Reindex Target", directory="test", - entity_type="note", + note_type="note", content=dedent(""" # Reindex Target @@ -413,7 +413,7 @@ async def test_fast_write_entity_generates_external_id(entity_service: EntitySer schema = EntitySchema( title=title, directory="test", - entity_type="note", + note_type="note", ) fast_entity = await entity_service.fast_write_entity(schema) @@ -428,7 +428,7 @@ async def test_create_or_update_new(entity_service: EntityService, file_service: EntitySchema( title="test", directory="test", - entity_type="test", + note_type="test", entity_metadata={"status": "draft"}, ) ) @@ -444,7 +444,7 @@ async def test_create_or_update_existing(entity_service: EntityService, file_ser EntitySchema( title="test", directory="test", - entity_type="test", + note_type="test", content="Test entity", entity_metadata={"status": "final"}, ) @@ -487,14 +487,14 @@ async def test_create_with_content(entity_service: EntityService, file_service: EntitySchema( title="Git Workflow Guide", directory="test", - entity_type="test", + note_type="test", content=content, ) ) assert created is True assert entity.title == "Git Workflow Guide" - assert entity.entity_type == "test" + assert entity.note_type == "test" assert entity.permalink == "git-workflow-guide" assert entity.file_path == "test/Git Workflow Guide.md" @@ -556,7 +556,7 @@ async def test_update_with_content( entity, created = await entity_service.create_or_update_entity( EntitySchema( title="Git Workflow Guide", - entity_type="test", + note_type="test", directory="test", content=content, ) @@ -617,7 +617,7 @@ async def test_update_with_content( EntitySchema( title="Git Workflow Guide", directory="test", - entity_type="test", + note_type="test", content=update_content, ) ) @@ -673,7 +673,7 @@ async def test_create_with_no_frontmatter( assert file_path.as_posix() == created.file_path assert created.title == "Git Workflow Guide" - assert created.entity_type == "note" + assert created.note_type == "note" assert created.permalink is None # assert file @@ -691,7 +691,7 @@ async def test_edit_entity_append(entity_service: EntityService, file_service: F EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -717,7 +717,7 @@ async def test_edit_entity_prepend(entity_service: EntityService, file_service: EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -743,7 +743,7 @@ async def test_edit_entity_find_replace(entity_service: EntityService, file_serv EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="This is old content that needs updating", ) ) @@ -783,7 +783,7 @@ async def test_edit_entity_replace_section( EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content=content, ) ) @@ -814,7 +814,7 @@ async def test_edit_entity_replace_section_create_new( EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="# Main Title\n\nSome content", ) ) @@ -851,7 +851,7 @@ async def test_edit_entity_invalid_operation(entity_service: EntityService): EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -870,7 +870,7 @@ async def test_edit_entity_find_replace_missing_find_text(entity_service: Entity EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -889,7 +889,7 @@ async def test_edit_entity_replace_section_missing_section(entity_service: Entit EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -919,7 +919,7 @@ async def test_edit_entity_with_observations_and_relations( EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content=content, ) ) @@ -1028,7 +1028,7 @@ async def test_edit_entity_find_replace_not_found(entity_service: EntityService) EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="This is some content", ) ) @@ -1053,7 +1053,7 @@ async def test_edit_entity_find_replace_multiple_occurrences_expected_one( EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content="The word banana appears here. Another banana word here.", ) ) @@ -1079,7 +1079,7 @@ async def test_edit_entity_find_replace_multiple_occurrences_success( EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content="The word banana appears here. Another banana word here.", ) ) @@ -1107,7 +1107,7 @@ async def test_edit_entity_find_replace_empty_find_text(entity_service: EntitySe EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Some content", ) ) @@ -1142,7 +1142,7 @@ async def test_edit_entity_find_replace_multiline( EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content=content, ) ) @@ -1184,7 +1184,7 @@ async def test_edit_entity_replace_section_multiple_sections_error(entity_servic EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content=content, ) ) @@ -1207,7 +1207,7 @@ async def test_edit_entity_replace_section_empty_section(entity_service: EntityS EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Some content", ) ) @@ -1242,7 +1242,7 @@ async def test_edit_entity_replace_section_header_variations( EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content=content, ) ) @@ -1282,7 +1282,7 @@ async def test_edit_entity_replace_section_at_end_of_document( EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content=content, ) ) @@ -1329,7 +1329,7 @@ async def test_edit_entity_replace_section_with_subsections( EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content=content, ) ) @@ -1373,7 +1373,7 @@ async def test_edit_entity_replace_section_strips_duplicate_header( EntitySchema( title="Sample Note", directory="docs", - entity_type="note", + note_type="note", content=content, ) ) @@ -1415,7 +1415,7 @@ async def test_move_entity_success( EntitySchema( title="Test Note", directory="original", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -1464,7 +1464,7 @@ async def test_move_entity_with_permalink_update( EntitySchema( title="Test Note", directory="original", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -1505,7 +1505,7 @@ async def test_move_entity_creates_destination_directory( EntitySchema( title="Test Note", directory="original", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -1555,7 +1555,7 @@ async def test_move_entity_source_file_missing( EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -1587,7 +1587,7 @@ async def test_move_entity_destination_exists( EntitySchema( title="Test Note 1", directory="test", - entity_type="note", + note_type="note", content="Content 1", ) ) @@ -1596,7 +1596,7 @@ async def test_move_entity_destination_exists( EntitySchema( title="Test Note 2", directory="test", - entity_type="note", + note_type="note", content="Content 2", ) ) @@ -1624,7 +1624,7 @@ async def test_move_entity_invalid_destination_path( EntitySchema( title="Test Note", directory="test", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -1663,7 +1663,7 @@ async def test_move_entity_by_title( EntitySchema( title="Test Note", directory="original", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -1708,7 +1708,7 @@ async def test_move_entity_preserves_observations_and_relations( EntitySchema( title="Test Note", directory="original", - entity_type="note", + note_type="note", content=content, ) ) @@ -1755,7 +1755,7 @@ async def test_move_entity_rollback_on_database_failure( EntitySchema( title="Test Note", directory="original", - entity_type="note", + note_type="note", content="Original content", ) ) @@ -1815,7 +1815,7 @@ async def test_move_entity_with_complex_observations( EntitySchema( title="Complex Note", directory="docs", - entity_type="note", + note_type="note", content=content, ) ) @@ -1878,7 +1878,7 @@ async def test_move_entity_with_null_permalink_generates_permalink( entity_data = { "title": "Test Entity", "file_path": "test/null-permalink-entity.md", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "permalink": None, # This is the key - null permalink from migration "created_at": datetime.now(timezone.utc), @@ -1947,7 +1947,7 @@ async def test_create_or_update_entity_fuzzy_search_bug( entity_a = EntitySchema( title="Node A", directory="edge-cases", - entity_type="note", + note_type="note", content="# Node A\n\nOriginal content for Node A", ) @@ -1971,7 +1971,7 @@ async def test_create_or_update_entity_fuzzy_search_bug( entity_b = EntitySchema( title="Node B", directory="edge-cases", - entity_type="note", + note_type="note", content="# Node B\n\nContent for Node B", ) @@ -1984,7 +1984,7 @@ async def test_create_or_update_entity_fuzzy_search_bug( entity_c = EntitySchema( title="Node C", directory="edge-cases", - entity_type="note", + note_type="note", content="# Node C\n\nContent for Node C", ) diff --git a/tests/services/test_entity_service_disable_permalinks.py b/tests/services/test_entity_service_disable_permalinks.py index 67ed37833..9ac684374 100644 --- a/tests/services/test_entity_service_disable_permalinks.py +++ b/tests/services/test_entity_service_disable_permalinks.py @@ -35,7 +35,7 @@ async def test_create_entity_with_permalinks_disabled( entity_data = EntitySchema( title="Test Entity", directory="test", - entity_type="note", + note_type="note", content="Test content", ) @@ -81,7 +81,7 @@ async def test_update_entity_with_permalinks_disabled( entity_data = EntitySchema( title="Test Entity", directory="test", - entity_type="note", + note_type="note", content="Original content", ) @@ -151,7 +151,7 @@ async def test_create_entity_with_content_frontmatter_permalinks_disabled( entity_data = EntitySchema( title="Test Entity", directory="test", - entity_type="note", + note_type="note", content=content, ) @@ -197,7 +197,7 @@ async def test_move_entity_with_permalinks_disabled( entity_data = EntitySchema( title="Test Entity", directory="test", - entity_type="note", + note_type="note", content="Test content", ) diff --git a/tests/services/test_initialization.py b/tests/services/test_initialization.py index 469c6970e..6fc7e4219 100644 --- a/tests/services/test_initialization.py +++ b/tests/services/test_initialization.py @@ -301,7 +301,7 @@ async def test_semantic_embedding_backfill_syncs_each_entity( entity = await entity_repository.create( { "title": f"Backfill Entity {i}", - "entity_type": "note", + "note_type": "note", "entity_metadata": {}, "content_type": "text/markdown", "file_path": f"test/backfill-{i}.md", diff --git a/tests/services/test_link_resolver.py b/tests/services/test_link_resolver.py index 009a59208..38ba44bb7 100644 --- a/tests/services/test_link_resolver.py +++ b/tests/services/test_link_resolver.py @@ -30,7 +30,7 @@ async def test_entities(entity_service, file_service): e1, _ = await entity_service.create_or_update_entity( EntitySchema( title="Core Service", - entity_type="component", + note_type="component", directory="components", project=entity_service.repository.project_id, ) @@ -38,7 +38,7 @@ async def test_entities(entity_service, file_service): e2, _ = await entity_service.create_or_update_entity( EntitySchema( title="Service Config", - entity_type="config", + note_type="config", directory="config", project=entity_service.repository.project_id, ) @@ -46,7 +46,7 @@ async def test_entities(entity_service, file_service): e3, _ = await entity_service.create_or_update_entity( EntitySchema( title="Auth Service", - entity_type="component", + note_type="component", directory="components", project=entity_service.repository.project_id, ) @@ -54,7 +54,7 @@ async def test_entities(entity_service, file_service): e4, _ = await entity_service.create_or_update_entity( EntitySchema( title="Core Features", - entity_type="specs", + note_type="specs", directory="specs", project=entity_service.repository.project_id, ) @@ -62,7 +62,7 @@ async def test_entities(entity_service, file_service): e5, _ = await entity_service.create_or_update_entity( EntitySchema( title="Sub Features 1", - entity_type="specs", + note_type="specs", directory="specs/subspec", project=entity_service.repository.project_id, ) @@ -70,7 +70,7 @@ async def test_entities(entity_service, file_service): e6, _ = await entity_service.create_or_update_entity( EntitySchema( title="Sub Features 2", - entity_type="specs", + note_type="specs", directory="specs/subspec", project=entity_service.repository.project_id, ) @@ -80,7 +80,7 @@ async def test_entities(entity_service, file_service): e7 = await entity_service.repository.add( EntityModel( title="Image.png", - entity_type="file", + note_type="file", content_type="image/png", file_path="Image.png", permalink="image", # Required for Postgres NOT NULL constraint @@ -93,7 +93,7 @@ async def test_entities(entity_service, file_service): e8 = await entity_service.create_entity( # duplicate title EntitySchema( title="Core Service", - entity_type="component", + note_type="component", directory="components2", project=entity_service.repository.project_id, ) @@ -186,7 +186,7 @@ async def test_resolve_file(link_resolver): # Basic new entity resolved = await link_resolver.resolve_link("Image.png") assert resolved is not None - assert resolved.entity_type == "file" + assert resolved.note_type == "file" assert resolved.title == "Image.png" @@ -392,7 +392,7 @@ async def test_cross_project_link_resolution( target = await other_entity_repo.add( EntityModel( title="Cross Project Note", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="docs/Cross Project Note.md", permalink=f"{other_project.permalink}/docs/cross-project-note", @@ -440,7 +440,7 @@ async def context_aware_entities(entity_repository): e1 = await entity_repository.add( EntityModel( title="testing", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="testing.md", permalink="testing", @@ -455,7 +455,7 @@ async def context_aware_entities(entity_repository): e2 = await entity_repository.add( EntityModel( title="testing", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="main/testing/testing.md", permalink="main/testing/testing", @@ -470,7 +470,7 @@ async def context_aware_entities(entity_repository): e3 = await entity_repository.add( EntityModel( title="another-test", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="main/testing/another-test.md", permalink="main/testing/another-test", @@ -485,7 +485,7 @@ async def context_aware_entities(entity_repository): e4 = await entity_repository.add( EntityModel( title="testing", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="other/testing.md", permalink="other/testing", @@ -500,7 +500,7 @@ async def context_aware_entities(entity_repository): e5 = await entity_repository.add( EntityModel( title="note", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="deep/nested/folder/note.md", permalink="deep/nested/folder/note", @@ -515,7 +515,7 @@ async def context_aware_entities(entity_repository): e6 = await entity_repository.add( EntityModel( title="note", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="deep/note.md", permalink="deep/note", @@ -530,7 +530,7 @@ async def context_aware_entities(entity_repository): e7 = await entity_repository.add( EntityModel( title="note", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="note.md", permalink="note", @@ -739,7 +739,7 @@ async def relative_path_entities(entity_repository): e1 = await entity_repository.add( EntityModel( title="link-test", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="testing/link-test.md", permalink="testing/link-test", @@ -754,7 +754,7 @@ async def relative_path_entities(entity_repository): e2 = await entity_repository.add( EntityModel( title="deep-note", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="testing/nested/deep-note.md", permalink="testing/nested/deep-note", @@ -769,7 +769,7 @@ async def relative_path_entities(entity_repository): e3 = await entity_repository.add( EntityModel( title="deep-note", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="nested/deep-note.md", permalink="nested/deep-note", @@ -784,7 +784,7 @@ async def relative_path_entities(entity_repository): e4 = await entity_repository.add( EntityModel( title="file", - entity_type="note", + note_type="note", content_type="text/markdown", file_path="other/file.md", permalink="other/file", diff --git a/tests/services/test_project_removal_bug.py b/tests/services/test_project_removal_bug.py index b3263a588..a41da7c46 100644 --- a/tests/services/test_project_removal_bug.py +++ b/tests/services/test_project_removal_bug.py @@ -45,7 +45,7 @@ async def test_remove_project_with_related_entities(project_service: ProjectServ entity_data = { "title": "Test Entity for Deletion", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "project_id": project.id, "permalink": "test-deletion-entity", diff --git a/tests/services/test_project_service.py b/tests/services/test_project_service.py index f90551fc5..c5a86e86f 100644 --- a/tests/services/test_project_service.py +++ b/tests/services/test_project_service.py @@ -135,7 +135,7 @@ async def test_get_statistics(project_service: ProjectService, test_graph, test_ # Assert it returns a valid ProjectStatistics object assert isinstance(statistics, ProjectStatistics) assert statistics.total_entities > 0 - assert "test" in statistics.entity_types + assert "test" in statistics.note_types @pytest.mark.asyncio diff --git a/tests/services/test_search_service.py b/tests/services/test_search_service.py index 7b6be29c7..263e66857 100644 --- a/tests/services/test_search_service.py +++ b/tests/services/test_search_service.py @@ -152,12 +152,12 @@ async def test_filters(search_service, test_graph): """Test search filters.""" # Combined filters results = await search_service.search( - SearchQuery(text="Deep", entity_types=[SearchItemType.ENTITY], types=["deep"]) + SearchQuery(text="Deep", entity_types=[SearchItemType.ENTITY], note_types=["deep"]) ) assert len(results) == 1 for r in results: assert r.type == SearchItemType.ENTITY - assert r.metadata.get("entity_type") == "deep" + assert r.metadata.get("note_type") == "deep" @pytest.mark.asyncio @@ -197,7 +197,7 @@ async def test_search_type(search_service, test_graph): """Test search filters.""" # Should find only type - results = await search_service.search(SearchQuery(types=["test"])) + results = await search_service.search(SearchQuery(note_types=["test"])) assert len(results) > 0 for r in results: assert r.type == SearchItemType.ENTITY @@ -222,7 +222,7 @@ async def test_extract_entity_tags_exception_handling(search_service): # Create entity with string tags that will cause parsing to fail and fall back to single tag entity_with_invalid_tags = Entity( title="Test Entity", - entity_type="test", + note_type="test", entity_metadata={"tags": "just a string"}, # This will fail ast.literal_eval content_type="text/markdown", file_path="test/test-entity.md", @@ -236,7 +236,7 @@ async def test_extract_entity_tags_exception_handling(search_service): # Test with empty string (should return empty list) - covers line 149 entity_with_empty_tags = Entity( title="Test Entity Empty", - entity_type="test", + note_type="test", entity_metadata={"tags": ""}, content_type="text/markdown", file_path="test/test-entity-empty.md", @@ -429,7 +429,7 @@ async def test_plain_multiterm_fts_retries_with_relaxed_or_when_strict_empty( created_at=now, updated_at=now, permalink="test/fallback", - metadata={"entity_type": "note"}, + metadata={"note_type": "note"}, title="Fallback Match", score=1.0, ) @@ -560,7 +560,7 @@ async def test_extract_entity_tags_list_format(search_service, session_maker): entity = Entity( title="Test Entity", - entity_type="note", + note_type="note", entity_metadata={"tags": ["business", "strategy", "planning"]}, content_type="text/markdown", file_path="test/business-strategy.md", @@ -578,7 +578,7 @@ async def test_extract_entity_tags_string_format(search_service, session_maker): entity = Entity( title="Test Entity", - entity_type="note", + note_type="note", entity_metadata={"tags": "['documentation', 'tools', 'best-practices']"}, content_type="text/markdown", file_path="test/docs.md", @@ -596,7 +596,7 @@ async def test_extract_entity_tags_empty_list(search_service, session_maker): entity = Entity( title="Test Entity", - entity_type="note", + note_type="note", entity_metadata={"tags": []}, content_type="text/markdown", file_path="test/empty-tags.md", @@ -614,7 +614,7 @@ async def test_extract_entity_tags_empty_string(search_service, session_maker): entity = Entity( title="Test Entity", - entity_type="note", + note_type="note", entity_metadata={"tags": "[]"}, content_type="text/markdown", file_path="test/empty-string-tags.md", @@ -632,7 +632,7 @@ async def test_extract_entity_tags_no_metadata(search_service, session_maker): entity = Entity( title="Test Entity", - entity_type="note", + note_type="note", entity_metadata=None, content_type="text/markdown", file_path="test/no-metadata.md", @@ -650,7 +650,7 @@ async def test_extract_entity_tags_no_tags_key(search_service, session_maker): entity = Entity( title="Test Entity", - entity_type="note", + note_type="note", entity_metadata={"title": "Some Title", "type": "note"}, content_type="text/markdown", file_path="test/no-tags-key.md", @@ -670,7 +670,7 @@ async def test_search_tag_prefix_maps_to_tags_filter(search_service, entity_serv EntitySchema( title="Tagged Note Missing", directory="tags", - entity_type="note", + note_type="note", content="# Tagged Note", entity_metadata={"tags": ["tier1", "alpha"]}, ) @@ -692,7 +692,7 @@ async def test_search_tag_prefix_with_nonexistent_tag_returns_empty(search_servi EntitySchema( title="Tagged Note", directory="tags", - entity_type="note", + note_type="note", content="# Tagged Note", entity_metadata={"tags": ["tier1", "alpha"]}, ) @@ -714,7 +714,7 @@ async def test_search_tag_prefix_multiple_tags_requires_all(search_service, enti EntitySchema( title="Multi Tagged Note", directory="tags/multi", - entity_type="note", + note_type="note", content="# Tagged Note", entity_metadata={"tags": ["tier1", "alpha"]}, ) @@ -739,7 +739,7 @@ async def test_search_by_frontmatter_tags(search_service, session_maker, test_pr entity_data = { "title": "Business Strategy Guide", - "entity_type": "note", + "note_type": "note", "entity_metadata": {"tags": ["business", "strategy", "planning", "organization"]}, "content_type": "text/markdown", "file_path": "guides/business-strategy.md", @@ -791,7 +791,7 @@ async def test_search_by_frontmatter_tags_string_format( entity_data = { "title": "Documentation Guidelines", - "entity_type": "note", + "note_type": "note", "entity_metadata": {"tags": "['documentation', 'tools', 'best-practices']"}, "content_type": "text/markdown", "file_path": "guides/documentation.md", @@ -844,7 +844,7 @@ async def test_search_special_characters_in_title(search_service, session_maker, entity_data = { "title": title, - "entity_type": "note", + "note_type": "note", "entity_metadata": {"tags": ["special", "characters"]}, "content_type": "text/markdown", "file_path": f"special/{title}.md", @@ -887,7 +887,7 @@ async def test_search_title_with_parentheses_specific(search_service, session_ma entity_data = { "title": "Note (with parentheses)", - "entity_type": "note", + "note_type": "note", "entity_metadata": {"tags": ["test"]}, "content_type": "text/markdown", "file_path": "special/Note (with parentheses).md", @@ -923,7 +923,7 @@ async def test_search_title_via_repository_direct(search_service, session_maker, entity_data = { "title": "Note (with parentheses)", - "entity_type": "note", + "note_type": "note", "entity_metadata": {"tags": ["test"]}, "content_type": "text/markdown", "file_path": "special/Note (with parentheses).md", @@ -971,7 +971,7 @@ async def test_index_entity_with_duplicate_observations( # Create entity entity_data = { "title": "Entity With Duplicate Observations", - "entity_type": "note", + "note_type": "note", "entity_metadata": {}, "content_type": "text/markdown", "file_path": "test/duplicate-obs.md", @@ -1026,7 +1026,7 @@ async def test_index_entity_dedupes_observations_by_permalink( # Create entity entity_data = { "title": "Dedupe Test Entity", - "entity_type": "note", + "note_type": "note", "entity_metadata": {}, "content_type": "text/markdown", "file_path": "test/dedupe-test.md", @@ -1084,7 +1084,7 @@ async def test_index_entity_multiple_categories_same_content( # Create entity entity_data = { "title": "Multi Category Entity", - "entity_type": "note", + "note_type": "note", "entity_metadata": {}, "content_type": "text/markdown", "file_path": "test/multi-category.md", @@ -1145,7 +1145,7 @@ async def test_index_entity_markdown_strips_nul_bytes(search_service, session_ma entity_data = { "title": "NUL Test Entity", - "entity_type": "note", + "note_type": "note", "entity_metadata": {}, "content_type": "text/markdown", "file_path": "test/nul-test.md", @@ -1184,7 +1184,7 @@ async def test_reindex_vectors(search_service, session_maker, test_project): entity = await entity_repo.create( { "title": f"Vector Test Entity {i}", - "entity_type": "note", + "note_type": "note", "entity_metadata": {}, "content_type": "text/markdown", "file_path": f"test/vector-test-{i}.md", @@ -1226,7 +1226,7 @@ async def test_reindex_vectors_no_callback(search_service, session_maker, test_p entity = await entity_repo.create( { "title": "No Callback Entity", - "entity_type": "note", + "note_type": "note", "entity_metadata": {}, "content_type": "text/markdown", "file_path": "test/no-callback.md", diff --git a/tests/sync/test_sync_service.py b/tests/sync/test_sync_service.py index 389f09195..71eeb5bcf 100644 --- a/tests/sync/test_sync_service.py +++ b/tests/sync/test_sync_service.py @@ -227,7 +227,7 @@ async def test_sync( other = Entity( permalink="concept/other", title="Other", - entity_type="test", + note_type="test", file_path="concept/other.md", checksum="12345678", content_type="text/markdown", @@ -245,7 +245,7 @@ async def test_sync( # Find new entity test_concept = next(e for e in entities if e.permalink == "concept/test-concept") - assert test_concept.entity_type == "knowledge" + assert test_concept.note_type == "knowledge" # Verify relation was created # with forward link @@ -907,7 +907,7 @@ async def test_sync_null_checksum_cleanup( entity = Entity( permalink="concept/incomplete", title="Incomplete", - entity_type="test", + note_type="test", file_path="concept/incomplete.md", checksum=None, # Null checksum content_type="text/markdown", @@ -1417,7 +1417,7 @@ async def test_sync_regular_file_race_condition_handling( # on the "add" call (same effect as the race-condition branch). await sync_service.entity_repository.add( Entity( - entity_type="file", + note_type="file", file_path=rel_path, checksum="old_checksum", title="Test Race Condition", diff --git a/tests/test_production_cascade_delete.py b/tests/test_production_cascade_delete.py index 98bfd9be4..455f2e128 100644 --- a/tests/test_production_cascade_delete.py +++ b/tests/test_production_cascade_delete.py @@ -123,9 +123,9 @@ async def create_test_data(self) -> tuple[int, int]: # Create test entity linked to project entity_sql = """ - INSERT INTO entity (title, entity_type, content_type, project_id, permalink, file_path, + INSERT INTO entity (title, note_type, content_type, project_id, permalink, file_path, checksum, created_at, updated_at) - VALUES (:title, :entity_type, :content_type, :project_id, :permalink, :file_path, + VALUES (:title, :note_type, :content_type, :project_id, :permalink, :file_path, :checksum, :created_at, :updated_at) """ @@ -133,7 +133,7 @@ async def create_test_data(self) -> tuple[int, int]: text(entity_sql), { "title": "Cascade Test Entity", - "entity_type": "note", + "note_type": "note", "content_type": "text/markdown", "project_id": project_id, "permalink": "cascade-test-entity", diff --git a/uv.lock b/uv.lock index d5673c8d5..23042c3a6 100644 --- a/uv.lock +++ b/uv.lock @@ -198,9 +198,11 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "cst-lsp" }, { name = "freezegun" }, { name = "gevent" }, { name = "icecream" }, + { name = "libcst" }, { name = "psycopg" }, { name = "pyright" }, { name = "pytest" }, @@ -258,9 +260,11 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "cst-lsp", specifier = ">=0.1.3" }, { name = "freezegun", specifier = ">=1.5.5" }, { name = "gevent", specifier = ">=24.11.1" }, { name = "icecream", specifier = ">=2.1.3" }, + { name = "libcst", specifier = ">=1.8.6" }, { name = "psycopg", specifier = ">=3.2.0" }, { name = "pyright", specifier = ">=1.1.408" }, { name = "pytest", specifier = ">=8.3.4" }, @@ -307,6 +311,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/93/1f76c8d1bafe3b0614e06b2195784a3765bbf7b0a067661af9e2dd47fc33/caio-0.9.25-py3-none-any.whl", hash = "sha256:06c0bb02d6b929119b1cfbe1ca403c768b2013a369e2db46bfa2a5761cf82e40", size = 19087, upload-time = "2025-12-26T15:22:00.221Z" }, ] +[[package]] +name = "cattrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/ec/ba18945e7d6e55a58364d9fb2e46049c1c2998b3d805f19b703f14e81057/cattrs-26.1.0.tar.gz", hash = "sha256:fa239e0f0ec0715ba34852ce813986dfed1e12117e209b816ab87401271cdd40", size = 495672, upload-time = "2026-02-18T22:15:19.406Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/56/60547f7801b97c67e97491dc3d9ade9fbccbd0325058fd3dfcb2f5d98d90/cattrs-26.1.0-py3-none-any.whl", hash = "sha256:d1e0804c42639494d469d08d4f26d6b9de9b8ab26b446db7b5f8c2e97f7c3096", size = 73054, upload-time = "2026-02-18T22:15:17.958Z" }, +] + [[package]] name = "certifi" version = "2026.1.4" @@ -581,6 +598,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, ] +[[package]] +name = "cst-lsp" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "libcst" }, + { name = "pygls" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/00/7c2a1357e34e78fb18dc3905ea5a32a6ebe2aaa13b696eafce9c8b2fccdc/cst_lsp-0.1.3.tar.gz", hash = "sha256:43df05f53bf76932766d01cad89081a06e3ef7f74a0c7bfab7d37409a884b2c8", size = 13881, upload-time = "2024-09-04T00:53:48.299Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/3e/98423e2dcb855c21544fbb40600159d81d553f45991123b20b52ad23f696/cst_lsp-0.1.3-py3-none-any.whl", hash = "sha256:251d0958b9943d6031424e859740cdfd7e3f8b817943387e68c2c48d9bdc1bd0", size = 16339, upload-time = "2024-09-04T00:53:47.446Z" }, +] + [[package]] name = "cyclopts" version = "4.4.4" @@ -1365,6 +1395,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, ] +[[package]] +name = "libcst" +version = "1.8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml", marker = "python_full_version != '3.13.*'" }, + { name = "pyyaml-ft", marker = "python_full_version == '3.13.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/cd/337df968b38d94c5aabd3e1b10630f047a2b345f6e1d4456bd9fe7417537/libcst-1.8.6.tar.gz", hash = "sha256:f729c37c9317126da9475bdd06a7208eb52fcbd180a6341648b45a56b4ba708b", size = 891354, upload-time = "2025-11-03T22:33:30.621Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/3c/93365c17da3d42b055a8edb0e1e99f1c60c776471db6c9b7f1ddf6a44b28/libcst-1.8.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c13d5bd3d8414a129e9dccaf0e5785108a4441e9b266e1e5e9d1f82d1b943c9", size = 2206166, upload-time = "2025-11-03T22:32:16.012Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cb/7530940e6ac50c6dd6022349721074e19309eb6aa296e942ede2213c1a19/libcst-1.8.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1472eeafd67cdb22544e59cf3bfc25d23dc94058a68cf41f6654ff4fcb92e09", size = 2083726, upload-time = "2025-11-03T22:32:17.312Z" }, + { url = "https://files.pythonhosted.org/packages/1b/cf/7e5eaa8c8f2c54913160671575351d129170db757bb5e4b7faffed022271/libcst-1.8.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:089c58e75cb142ec33738a1a4ea7760a28b40c078ab2fd26b270dac7d2633a4d", size = 2235755, upload-time = "2025-11-03T22:32:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/55/54/570ec2b0e9a3de0af9922e3bb1b69a5429beefbc753a7ea770a27ad308bd/libcst-1.8.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c9d7aeafb1b07d25a964b148c0dda9451efb47bbbf67756e16eeae65004b0eb5", size = 2301473, upload-time = "2025-11-03T22:32:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/11/4c/163457d1717cd12181c421a4cca493454bcabd143fc7e53313bc6a4ad82a/libcst-1.8.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:207481197afd328aa91d02670c15b48d0256e676ce1ad4bafb6dc2b593cc58f1", size = 2298899, upload-time = "2025-11-03T22:32:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/35/1d/317ddef3669883619ef3d3395ea583305f353ef4ad87d7a5ac1c39be38e3/libcst-1.8.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:375965f34cc6f09f5f809244d3ff9bd4f6cb6699f571121cebce53622e7e0b86", size = 2408239, upload-time = "2025-11-03T22:32:23.275Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a1/f47d8cccf74e212dd6044b9d6dbc223636508da99acff1d54786653196bc/libcst-1.8.6-cp312-cp312-win_amd64.whl", hash = "sha256:da95b38693b989eaa8d32e452e8261cfa77fe5babfef1d8d2ac25af8c4aa7e6d", size = 2119660, upload-time = "2025-11-03T22:32:24.822Z" }, + { url = "https://files.pythonhosted.org/packages/19/d0/dd313bf6a7942cdf951828f07ecc1a7695263f385065edc75ef3016a3cb5/libcst-1.8.6-cp312-cp312-win_arm64.whl", hash = "sha256:bff00e1c766658adbd09a175267f8b2f7616e5ee70ce45db3d7c4ce6d9f6bec7", size = 1999824, upload-time = "2025-11-03T22:32:26.131Z" }, + { url = "https://files.pythonhosted.org/packages/90/01/723cd467ec267e712480c772aacc5aa73f82370c9665162fd12c41b0065b/libcst-1.8.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7445479ebe7d1aff0ee094ab5a1c7718e1ad78d33e3241e1a1ec65dcdbc22ffb", size = 2206386, upload-time = "2025-11-03T22:32:27.422Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/b944944f910f24c094f9b083f76f61e3985af5a376f5342a21e01e2d1a81/libcst-1.8.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4fc3fef8a2c983e7abf5d633e1884c5dd6fa0dcb8f6e32035abd3d3803a3a196", size = 2083945, upload-time = "2025-11-03T22:32:28.847Z" }, + { url = "https://files.pythonhosted.org/packages/36/a1/bd1b2b2b7f153d82301cdaddba787f4a9fc781816df6bdb295ca5f88b7cf/libcst-1.8.6-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1a3a5e4ee870907aa85a4076c914ae69066715a2741b821d9bf16f9579de1105", size = 2235818, upload-time = "2025-11-03T22:32:30.504Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ab/f5433988acc3b4d188c4bb154e57837df9488cc9ab551267cdeabd3bb5e7/libcst-1.8.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6609291c41f7ad0bac570bfca5af8fea1f4a27987d30a1fa8b67fe5e67e6c78d", size = 2301289, upload-time = "2025-11-03T22:32:31.812Z" }, + { url = "https://files.pythonhosted.org/packages/5d/57/89f4ba7a6f1ac274eec9903a9e9174890d2198266eee8c00bc27eb45ecf7/libcst-1.8.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25eaeae6567091443b5374b4c7d33a33636a2d58f5eda02135e96fc6c8807786", size = 2299230, upload-time = "2025-11-03T22:32:33.242Z" }, + { url = "https://files.pythonhosted.org/packages/f2/36/0aa693bc24cce163a942df49d36bf47a7ed614a0cd5598eee2623bc31913/libcst-1.8.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04030ea4d39d69a65873b1d4d877def1c3951a7ada1824242539e399b8763d30", size = 2408519, upload-time = "2025-11-03T22:32:34.678Z" }, + { url = "https://files.pythonhosted.org/packages/db/18/6dd055b5f15afa640fb3304b2ee9df8b7f72e79513814dbd0a78638f4a0e/libcst-1.8.6-cp313-cp313-win_amd64.whl", hash = "sha256:8066f1b70f21a2961e96bedf48649f27dfd5ea68be5cd1bed3742b047f14acde", size = 2119853, upload-time = "2025-11-03T22:32:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ed/5ddb2a22f0b0abdd6dcffa40621ada1feaf252a15e5b2733a0a85dfd0429/libcst-1.8.6-cp313-cp313-win_arm64.whl", hash = "sha256:c188d06b583900e662cd791a3f962a8c96d3dfc9b36ea315be39e0a4c4792ebf", size = 1999808, upload-time = "2025-11-03T22:32:38.1Z" }, + { url = "https://files.pythonhosted.org/packages/25/d3/72b2de2c40b97e1ef4a1a1db4e5e52163fc7e7740ffef3846d30bc0096b5/libcst-1.8.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c41c76e034a1094afed7057023b1d8967f968782433f7299cd170eaa01ec033e", size = 2190553, upload-time = "2025-11-03T22:32:39.819Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/983b7b210ccc3ad94a82db54230e92599c4a11b9cfc7ce3bc97c1d2df75c/libcst-1.8.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5432e785322aba3170352f6e72b32bea58d28abd141ac37cc9b0bf6b7c778f58", size = 2074717, upload-time = "2025-11-03T22:32:41.373Z" }, + { url = "https://files.pythonhosted.org/packages/13/f2/9e01678fedc772e09672ed99930de7355757035780d65d59266fcee212b8/libcst-1.8.6-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:85b7025795b796dea5284d290ff69de5089fc8e989b25d6f6f15b6800be7167f", size = 2225834, upload-time = "2025-11-03T22:32:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/4a/0d/7bed847b5c8c365e9f1953da274edc87577042bee5a5af21fba63276e756/libcst-1.8.6-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:536567441182a62fb706e7aa954aca034827b19746832205953b2c725d254a93", size = 2287107, upload-time = "2025-11-03T22:32:44.549Z" }, + { url = "https://files.pythonhosted.org/packages/02/f0/7e51fa84ade26c518bfbe7e2e4758b56d86a114c72d60309ac0d350426c4/libcst-1.8.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f04d3672bde1704f383a19e8f8331521abdbc1ed13abb349325a02ac56e5012", size = 2288672, upload-time = "2025-11-03T22:32:45.867Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cd/15762659a3f5799d36aab1bc2b7e732672722e249d7800e3c5f943b41250/libcst-1.8.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f04febcd70e1e67917be7de513c8d4749d2e09206798558d7fe632134426ea4", size = 2392661, upload-time = "2025-11-03T22:32:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6b/b7f9246c323910fcbe021241500f82e357521495dcfe419004dbb272c7cb/libcst-1.8.6-cp313-cp313t-win_amd64.whl", hash = "sha256:1dc3b897c8b0f7323412da3f4ad12b16b909150efc42238e19cbf19b561cc330", size = 2105068, upload-time = "2025-11-03T22:32:49.145Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0b/4fd40607bc4807ec2b93b054594373d7fa3d31bb983789901afcb9bcebe9/libcst-1.8.6-cp313-cp313t-win_arm64.whl", hash = "sha256:44f38139fa95e488db0f8976f9c7ca39a64d6bc09f2eceef260aa1f6da6a2e42", size = 1985181, upload-time = "2025-11-03T22:32:50.597Z" }, + { url = "https://files.pythonhosted.org/packages/3a/60/4105441989e321f7ad0fd28ffccb83eb6aac0b7cfb0366dab855dcccfbe5/libcst-1.8.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b188e626ce61de5ad1f95161b8557beb39253de4ec74fc9b1f25593324a0279c", size = 2204202, upload-time = "2025-11-03T22:32:52.311Z" }, + { url = "https://files.pythonhosted.org/packages/67/2f/51a6f285c3a183e50cfe5269d4a533c21625aac2c8de5cdf2d41f079320d/libcst-1.8.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:87e74f7d7dfcba9efa91127081e22331d7c42515f0a0ac6e81d4cf2c3ed14661", size = 2083581, upload-time = "2025-11-03T22:32:54.269Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/921b1c19b638860af76cdb28bc81d430056592910b9478eea49e31a7f47a/libcst-1.8.6-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:3a926a4b42015ee24ddfc8ae940c97bd99483d286b315b3ce82f3bafd9f53474", size = 2236495, upload-time = "2025-11-03T22:32:55.723Z" }, + { url = "https://files.pythonhosted.org/packages/12/a8/b00592f9bede618cbb3df6ffe802fc65f1d1c03d48a10d353b108057d09c/libcst-1.8.6-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:3f4fbb7f569e69fd9e89d9d9caa57ca42c577c28ed05062f96a8c207594e75b8", size = 2301466, upload-time = "2025-11-03T22:32:57.337Z" }, + { url = "https://files.pythonhosted.org/packages/af/df/790d9002f31580fefd0aec2f373a0f5da99070e04c5e8b1c995d0104f303/libcst-1.8.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:08bd63a8ce674be431260649e70fca1d43f1554f1591eac657f403ff8ef82c7a", size = 2300264, upload-time = "2025-11-03T22:32:58.852Z" }, + { url = "https://files.pythonhosted.org/packages/21/de/dc3f10e65bab461be5de57850d2910a02c24c3ddb0da28f0e6e4133c3487/libcst-1.8.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e00e275d4ba95d4963431ea3e409aa407566a74ee2bf309a402f84fc744abe47", size = 2408572, upload-time = "2025-11-03T22:33:00.552Z" }, + { url = "https://files.pythonhosted.org/packages/20/3b/35645157a7590891038b077db170d6dd04335cd2e82a63bdaa78c3297dfe/libcst-1.8.6-cp314-cp314-win_amd64.whl", hash = "sha256:fea5c7fa26556eedf277d4f72779c5ede45ac3018650721edd77fd37ccd4a2d4", size = 2193917, upload-time = "2025-11-03T22:33:02.354Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a2/1034a9ba7d3e82f2c2afaad84ba5180f601aed676d92b76325797ad60951/libcst-1.8.6-cp314-cp314-win_arm64.whl", hash = "sha256:bb9b4077bdf8857b2483879cbbf70f1073bc255b057ec5aac8a70d901bb838e9", size = 2078748, upload-time = "2025-11-03T22:33:03.707Z" }, + { url = "https://files.pythonhosted.org/packages/95/a1/30bc61e8719f721a5562f77695e6154e9092d1bdf467aa35d0806dcd6cea/libcst-1.8.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:55ec021a296960c92e5a33b8d93e8ad4182b0eab657021f45262510a58223de1", size = 2188980, upload-time = "2025-11-03T22:33:05.152Z" }, + { url = "https://files.pythonhosted.org/packages/2c/14/c660204532407c5628e3b615015a902ed2d0b884b77714a6bdbe73350910/libcst-1.8.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ba9ab2b012fbd53b36cafd8f4440a6b60e7e487cd8b87428e57336b7f38409a4", size = 2074828, upload-time = "2025-11-03T22:33:06.864Z" }, + { url = "https://files.pythonhosted.org/packages/82/e2/c497c354943dff644749f177ee9737b09ed811b8fc842b05709a40fe0d1b/libcst-1.8.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c0a0cc80aebd8aa15609dd4d330611cbc05e9b4216bcaeabba7189f99ef07c28", size = 2225568, upload-time = "2025-11-03T22:33:08.354Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/45999676d07bd6d0eefa28109b4f97124db114e92f9e108de42ba46a8028/libcst-1.8.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:42a4f68121e2e9c29f49c97f6154e8527cd31021809cc4a941c7270aa64f41aa", size = 2286523, upload-time = "2025-11-03T22:33:10.206Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6c/517d8bf57d9f811862f4125358caaf8cd3320a01291b3af08f7b50719db4/libcst-1.8.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a434c521fadaf9680788b50d5c21f4048fa85ed19d7d70bd40549fbaeeecab1", size = 2288044, upload-time = "2025-11-03T22:33:11.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/24d7d49478ffb61207f229239879845da40a374965874f5ee60f96b02ddb/libcst-1.8.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6a65f844d813ab4ef351443badffa0ae358f98821561d19e18b3190f59e71996", size = 2392605, upload-time = "2025-11-03T22:33:12.962Z" }, + { url = "https://files.pythonhosted.org/packages/39/c3/829092ead738b71e96a4e96896c96f276976e5a8a58b4473ed813d7c962b/libcst-1.8.6-cp314-cp314t-win_amd64.whl", hash = "sha256:bdb14bc4d4d83a57062fed2c5da93ecb426ff65b0dc02ddf3481040f5f074a82", size = 2181581, upload-time = "2025-11-03T22:33:14.514Z" }, + { url = "https://files.pythonhosted.org/packages/98/6d/5d6a790a02eb0d9d36c4aed4f41b277497e6178900b2fa29c35353aa45ed/libcst-1.8.6-cp314-cp314t-win_arm64.whl", hash = "sha256:819c8081e2948635cab60c603e1bbdceccdfe19104a242530ad38a36222cb88f", size = 2065000, upload-time = "2025-11-03T22:33:16.257Z" }, +] + [[package]] name = "loguru" version = "0.7.3" @@ -1378,6 +1460,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, ] +[[package]] +name = "lsprotocol" +version = "2025.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cattrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/26/67b84e6ec1402f0e6764ef3d2a0aaf9a79522cc1d37738f4e5bb0b21521a/lsprotocol-2025.0.0.tar.gz", hash = "sha256:e879da2b9301e82cfc3e60d805630487ac2f7ab17492f4f5ba5aaba94fe56c29", size = 74896, upload-time = "2025-06-17T21:30:18.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/f0/92f2d609d6642b5f30cb50a885d2bf1483301c69d5786286500d15651ef2/lsprotocol-2025.0.0-py3-none-any.whl", hash = "sha256:f9d78f25221f2a60eaa4a96d3b4ffae011b107537facee61d3da3313880995c7", size = 76250, upload-time = "2025-06-17T21:30:19.455Z" }, +] + [[package]] name = "mako" version = "1.3.10" @@ -2125,6 +2220,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, ] +[[package]] +name = "pygls" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cattrs" }, + { name = "lsprotocol" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/94/cce3560f6c7296be43bd67ba342f8972b8adddfe407b62b25d1fb90c514b/pygls-2.0.1.tar.gz", hash = "sha256:2f774a669fbe2ece977d302786f01f9b0c5df7d0204ea0fa371ecb08288d6b86", size = 55796, upload-time = "2026-01-26T23:04:33.8Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/8b/d0d7e51f928ec6e555c943f7c07a712b992e9cf061d52cbf37fbf8622c05/pygls-2.0.1-py3-none-any.whl", hash = "sha256:d29748042cea5bedc98285eb3e2c0c60bf3fc73786319519001bf72bbe8f36cc", size = 69536, upload-time = "2026-01-26T23:04:35.197Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -2391,6 +2500,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "pyyaml-ft" +version = "8.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/eb/5a0d575de784f9a1f94e2b1288c6886f13f34185e13117ed530f32b6f8a8/pyyaml_ft-8.0.0.tar.gz", hash = "sha256:0c947dce03954c7b5d38869ed4878b2e6ff1d44b08a0d84dc83fdad205ae39ab", size = 141057, upload-time = "2025-06-10T15:32:15.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/ba/a067369fe61a2e57fb38732562927d5bae088c73cb9bb5438736a9555b29/pyyaml_ft-8.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c1306282bc958bfda31237f900eb52c9bedf9b93a11f82e1aab004c9a5657a6", size = 187027, upload-time = "2025-06-10T15:31:48.722Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c5/a3d2020ce5ccfc6aede0d45bcb870298652ac0cf199f67714d250e0cdf39/pyyaml_ft-8.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30c5f1751625786c19de751e3130fc345ebcba6a86f6bddd6e1285342f4bbb69", size = 176146, upload-time = "2025-06-10T15:31:50.584Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bb/23a9739291086ca0d3189eac7cd92b4d00e9fdc77d722ab610c35f9a82ba/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fa992481155ddda2e303fcc74c79c05eddcdbc907b888d3d9ce3ff3e2adcfb0", size = 746792, upload-time = "2025-06-10T15:31:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c2/e8825f4ff725b7e560d62a3609e31d735318068e1079539ebfde397ea03e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cec6c92b4207004b62dfad1f0be321c9f04725e0f271c16247d8b39c3bf3ea42", size = 786772, upload-time = "2025-06-10T15:31:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/35/be/58a4dcae8854f2fdca9b28d9495298fd5571a50d8430b1c3033ec95d2d0e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06237267dbcab70d4c0e9436d8f719f04a51123f0ca2694c00dd4b68c338e40b", size = 778723, upload-time = "2025-06-10T15:31:56.093Z" }, + { url = "https://files.pythonhosted.org/packages/86/ed/fed0da92b5d5d7340a082e3802d84c6dc9d5fa142954404c41a544c1cb92/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a7f332bc565817644cdb38ffe4739e44c3e18c55793f75dddb87630f03fc254", size = 758478, upload-time = "2025-06-10T15:31:58.314Z" }, + { url = "https://files.pythonhosted.org/packages/f0/69/ac02afe286275980ecb2dcdc0156617389b7e0c0a3fcdedf155c67be2b80/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d10175a746be65f6feb86224df5d6bc5c049ebf52b89a88cf1cd78af5a367a8", size = 799159, upload-time = "2025-06-10T15:31:59.675Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ac/c492a9da2e39abdff4c3094ec54acac9747743f36428281fb186a03fab76/pyyaml_ft-8.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:58e1015098cf8d8aec82f360789c16283b88ca670fe4275ef6c48c5e30b22a96", size = 158779, upload-time = "2025-06-10T15:32:01.029Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9b/41998df3298960d7c67653669f37710fa2d568a5fc933ea24a6df60acaf6/pyyaml_ft-8.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5f3e2ceb790d50602b2fd4ec37abbd760a8c778e46354df647e7c5a4ebb", size = 191331, upload-time = "2025-06-10T15:32:02.602Z" }, + { url = "https://files.pythonhosted.org/packages/0f/16/2710c252ee04cbd74d9562ebba709e5a284faeb8ada88fcda548c9191b47/pyyaml_ft-8.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d445bf6ea16bb93c37b42fdacfb2f94c8e92a79ba9e12768c96ecde867046d1", size = 182879, upload-time = "2025-06-10T15:32:04.466Z" }, + { url = "https://files.pythonhosted.org/packages/9a/40/ae8163519d937fa7bfa457b6f78439cc6831a7c2b170e4f612f7eda71815/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c56bb46b4fda34cbb92a9446a841da3982cdde6ea13de3fbd80db7eeeab8b49", size = 811277, upload-time = "2025-06-10T15:32:06.214Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/28d82dbff7f87b96f0eeac79b7d972a96b4980c1e445eb6a857ba91eda00/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dab0abb46eb1780da486f022dce034b952c8ae40753627b27a626d803926483b", size = 831650, upload-time = "2025-06-10T15:32:08.076Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/161c4566facac7d75a9e182295c223060373d4116dead9cc53a265de60b9/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd48d639cab5ca50ad957b6dd632c7dd3ac02a1abe0e8196a3c24a52f5db3f7a", size = 815755, upload-time = "2025-06-10T15:32:09.435Z" }, + { url = "https://files.pythonhosted.org/packages/05/10/f42c48fa5153204f42eaa945e8d1fd7c10d6296841dcb2447bf7da1be5c4/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:052561b89d5b2a8e1289f326d060e794c21fa068aa11255fe71d65baf18a632e", size = 810403, upload-time = "2025-06-10T15:32:11.051Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d2/e369064aa51009eb9245399fd8ad2c562bd0bcd392a00be44b2a824ded7c/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3bb4b927929b0cb162fb1605392a321e3333e48ce616cdcfa04a839271373255", size = 835581, upload-time = "2025-06-10T15:32:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/c0/28/26534bed77109632a956977f60d8519049f545abc39215d086e33a61f1f2/pyyaml_ft-8.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:de04cfe9439565e32f178106c51dd6ca61afaa2907d143835d501d84703d3793", size = 171579, upload-time = "2025-06-10T15:32:14.34Z" }, +] + [[package]] name = "referencing" version = "0.36.2"