feat: Support tag: query shorthand in search (#535)

Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
Paul Hernandez
2026-02-02 19:22:16 -06:00
committed by GitHub
parent 8072449a78
commit f1d50c2ba7
2 changed files with 86 additions and 9 deletions
@@ -1,6 +1,7 @@
"""Service for search operations."""
import ast
import re
from datetime import datetime
from typing import List, Optional, Set, Dict, Any
@@ -79,6 +80,16 @@ class SearchService:
2. Pattern match: handles * wildcards in paths
3. Text search: full-text search across title/content
"""
# Support tag:<tag> shorthand by mapping to tags filter
if query.text:
text = query.text.strip()
if text.lower().startswith("tag:"):
tag_values = re.split(r"[,\s]+", text[4:].strip())
tags = [t for t in tag_values if t]
if tags:
query.tags = tags
query.text = None
if query.no_criteria():
logger.debug("no criteria passed to query")
return []
+75 -9
View File
@@ -345,9 +345,9 @@ async def test_boolean_not_search(search_service, test_graph):
# Should find "Root Entity" but not "Connected Entity"
for result in results:
assert "connected" not in result.permalink.lower(), (
"Boolean NOT search returned excluded term"
)
assert (
"connected" not in result.permalink.lower()
), "Boolean NOT search returned excluded term"
@pytest.mark.asyncio
@@ -366,9 +366,9 @@ async def test_boolean_group_search(search_service, test_graph):
"root" in result.title.lower() or "connected" in result.title.lower()
)
assert contains_entity and contains_root_or_connected, (
"Boolean grouped search returned incorrect results"
)
assert (
contains_entity and contains_root_or_connected
), "Boolean grouped search returned incorrect results"
@pytest.mark.asyncio
@@ -398,9 +398,9 @@ async def test_boolean_operators_detection(search_service):
for query_text in non_boolean_queries:
query = SearchQuery(text=query_text)
assert not query.has_boolean_operators(), (
f"Incorrectly detected boolean operators in: {query_text}"
)
assert (
not query.has_boolean_operators()
), f"Incorrectly detected boolean operators in: {query_text}"
# Tests for frontmatter tag search functionality
@@ -514,6 +514,72 @@ async def test_extract_entity_tags_no_tags_key(search_service, session_maker):
assert tags == []
@pytest.mark.asyncio
async def test_search_tag_prefix_maps_to_tags_filter(search_service, entity_service):
"""`tag:foo` prefix should translate to tags filter and return tagged entities."""
from basic_memory.schemas import Entity as EntitySchema
tagged_entity, _ = await entity_service.create_or_update_entity(
EntitySchema(
title="Tagged Note Missing",
directory="tags",
entity_type="note",
content="# Tagged Note",
entity_metadata={"tags": ["tier1", "alpha"]},
)
)
await search_service.index_entity(tagged_entity)
results = await search_service.search(SearchQuery(text="tag:tier1"))
assert any(r.permalink == tagged_entity.permalink for r in results)
@pytest.mark.asyncio
async def test_search_tag_prefix_with_nonexistent_tag_returns_empty(search_service, entity_service):
"""`tag:missing` should return no results when tags do not match."""
from basic_memory.schemas import Entity as EntitySchema
tagged_entity, _ = await entity_service.create_or_update_entity(
EntitySchema(
title="Tagged Note",
directory="tags",
entity_type="note",
content="# Tagged Note",
entity_metadata={"tags": ["tier1", "alpha"]},
)
)
await search_service.index_entity(tagged_entity)
results = await search_service.search(SearchQuery(text="tag:missing"))
assert not results
@pytest.mark.asyncio
async def test_search_tag_prefix_multiple_tags_requires_all(search_service, entity_service):
"""`tag:tier1,alpha` should match entities containing all listed tags."""
from basic_memory.schemas import Entity as EntitySchema
tagged_entity, _ = await entity_service.create_or_update_entity(
EntitySchema(
title="Multi Tagged Note",
directory="tags/multi",
entity_type="note",
content="# Tagged Note",
entity_metadata={"tags": ["tier1", "alpha"]},
)
)
await search_service.index_entity(tagged_entity)
results = await search_service.search(SearchQuery(text="tag:tier1,alpha"))
assert any(r.permalink == tagged_entity.permalink for r in results)
@pytest.mark.asyncio
async def test_search_by_frontmatter_tags(search_service, session_maker, test_project):
"""Test that entities can be found by searching for their frontmatter tags."""