Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions hermes_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from typing import Any, Callable, Dict, List, Optional, TypeVar

logger = logging.getLogger(__name__)

T = TypeVar("T")

DEFAULT_DB_PATH = get_hermes_home() / "state.db"
Expand Down Expand Up @@ -968,10 +967,19 @@ def _preserve_quoted(m: re.Match) -> str:
sanitized = re.sub(r"\*+", "*", sanitized)
sanitized = re.sub(r"(^|\s)\*", r"\1", sanitized)

# Step 4: Remove dangling boolean operators at start/end that would
# cause syntax errors (e.g. "hello AND" or "OR world")
# Step 4: Remove dangling or duplicated boolean operators that would
# cause syntax errors (e.g. "hello AND", "OR world", "a AND OR b")
sanitized = re.sub(r"(?i)^(AND|OR|NOT)\b\s*", "", sanitized.strip())
sanitized = re.sub(r"(?i)\s+(AND|OR|NOT)\s*$", "", sanitized.strip())
while True:
collapsed = re.sub(
r"(?i)\b(?:AND|OR|NOT)\b\s+\b(?:AND|OR|NOT)\b",
lambda m: m.group(0).split()[-1],
sanitized,
)
if collapsed == sanitized:
break
sanitized = collapsed

# Step 5: Wrap unquoted dotted and/or hyphenated terms in double
# quotes. FTS5's tokenizer splits on dots and hyphens, turning
Expand Down
16 changes: 15 additions & 1 deletion tests/test_hermes_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import time
import pytest
from pathlib import Path

from hermes_state import SessionDB

Expand Down Expand Up @@ -390,6 +389,17 @@ def test_search_dotted_term_does_not_crash(self, db):
assert isinstance(results2, list)
assert len(results2) >= 1

def test_search_broad_or_query_with_hyphenated_term_still_runs(self, db):
broad_query = "错题本 OR 错题 OR wrong-book OR mistakes"
db.create_session(session_id="s1", source="cli")
db.append_message("s1", role="user", content="We built a wrong-book review workflow for mistakes.")

results = db.search_messages(broad_query)

assert isinstance(results, list)
assert len(results) >= 1
assert any(r["session_id"] == "s1" for r in results)

def test_search_quoted_phrase_preserved(self, db):
"""User-provided quoted phrases should be preserved for exact matching."""
db.create_session(session_id="s1", source="cli")
Expand Down Expand Up @@ -456,6 +466,10 @@ def test_sanitize_fts5_quotes_hyphenated_terms(self):
assert s('"chat-send"') == '"chat-send"'
# Hyphenated inside a quoted phrase stays as-is
assert s('"my chat-send thing"') == '"my chat-send thing"'
# Non-word hyphenated tokens must not be left bare, or FTS parses them as column filters
assert s('wrong-book') == '"wrong-book"'
result = s('错题本 OR 错题 OR wrong-book OR mistakes')
assert '"wrong-book"' in result

def test_sanitize_fts5_quotes_dotted_terms(self):
"""Dotted terms should be wrapped in quotes to avoid FTS5 query parse edge cases."""
Expand Down