You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
FocusRelay currently treats the complete search value as one literal,
case-insensitive substring against task names and notes. An LLM often searches
with related but non-identical keywords, receives no result, and retries with
several variations. The repeated calls increase latency and context use.
OmniFocus's normal Search covers keywords in titles, notes, and tags. Omni
Automation exposes native Smart Match for projects, folders, and tags, but it
does not expose a documented tasksMatching() equivalent.
User-facing acceptance journey
Find YouTube URLs about a Swift conference.
One list_tasks search should rank Swift Conf Indonesia Sessions Recording —
YouTube near the top without hard-coded Swift, conference, YouTube, or
playlist aliases.
Required behavior
Keep the existing search argument. Do not add a search mode or another public
tool.
Task search must:
Iterate the documented flattenedTasks collection; do not use byName()
for discovery because it is exact and returns only the first directly
contained match.
Normalize query and candidate text for case, whitespace, punctuation, and
diacritics.
Split the query into keywords.
Match keywords against task names and notes using inexpensive substring and
word-prefix comparisons. Two words are prefix-related only when the shorter
word has at least four characters and one normalized word begins with the
other; this handles Conf/conference without an abbreviation dictionary.
Use the documented tagsMatching() Smart Match API to resolve relevant tag
candidates, then include tasks carrying those tags.
Include a task when any keyword matches.
Rank the complete candidate set before applying pagination.
Rank results in this order:
Complete query phrase in the task title.
All keywords in the title.
All keywords across title, tags, and note.
Greater keyword coverage.
Title matches before tag matches, then note matches.
Complete-word matches before the defined prefix matches.
OmniFocus document order as the final tie-breaker.
Preserve the existing 50-task default, status and availability defaults,
explicit filters, cursor behavior, and list/count search membership parity.
Why this shape
This deliberately returns a broader, relevance-ranked candidate set for the
LLM to interpret. It reuses native OmniFocus tag matching where that API exists
and adds only lightweight keyword matching for task titles and notes. It is not
a new semantic-search subsystem.
Explicit non-goals
Do not add searchMode.
Do not use byName() as a task search.
Do not drive or scrape OmniFocus's visible Search interface.
Do not implement edit distance, embeddings, semantic search, or synonym
dictionaries.
Do not add dependencies, task caching, or hard-coded aliases.
Do not combine unrelated invalid-argument or schema cleanup into this issue.
Validation impact
query
Acceptance criteria
The acceptance journey succeeds with one search call.
Any-keyword matching returns a broader candidate set than literal phrase
matching.
Exact phrase and higher-coverage results rank first.
Names, notes, URLs, and tags are covered by deterministic fixtures.
Multiple keywords, partial coverage, the four-character prefix rule,
punctuation, and diacritics are covered.
Ranking occurs before pagination.
Existing status, availability, and pagination defaults are unchanged.
list_tasks and get_task_counts agree on search membership.
On the same production fingerprint, a thousands-of-task fixture and live
comparison keep search P95 within 10% or 100 ms of the literal-search
baseline, whichever allowance is larger.
Non-search query results are unaffected.
swift run focusrelay-dev validate --impact query passes.
Follow-up threshold
Do not add typo-distance or semantic matching in this issue. Record UAT misses;
only create a later bounded enhancement if real queries remain undiscoverable
after broad keyword ranking.
User-visible problem
FocusRelay currently treats the complete
searchvalue as one literal,case-insensitive substring against task names and notes. An LLM often searches
with related but non-identical keywords, receives no result, and retries with
several variations. The repeated calls increase latency and context use.
OmniFocus's normal Search covers keywords in titles, notes, and tags. Omni
Automation exposes native Smart Match for projects, folders, and tags, but it
does not expose a documented
tasksMatching()equivalent.User-facing acceptance journey
One
list_taskssearch should rank Swift Conf Indonesia Sessions Recording —YouTube near the top without hard-coded Swift, conference, YouTube, or
playlist aliases.
Required behavior
Keep the existing
searchargument. Do not add a search mode or another publictool.
Task search must:
flattenedTaskscollection; do not usebyName()for discovery because it is exact and returns only the first directly
contained match.
diacritics.
word-prefix comparisons. Two words are prefix-related only when the shorter
word has at least four characters and one normalized word begins with the
other; this handles
Conf/conferencewithout an abbreviation dictionary.tagsMatching()Smart Match API to resolve relevant tagcandidates, then include tasks carrying those tags.
Rank results in this order:
Preserve the existing 50-task default, status and availability defaults,
explicit filters, cursor behavior, and list/count search membership parity.
Why this shape
This deliberately returns a broader, relevance-ranked candidate set for the
LLM to interpret. It reuses native OmniFocus tag matching where that API exists
and adds only lightweight keyword matching for task titles and notes. It is not
a new semantic-search subsystem.
Explicit non-goals
searchMode.byName()as a task search.dictionaries.
Validation impact
queryAcceptance criteria
matching.
punctuation, and diacritics are covered.
list_tasksandget_task_countsagree on search membership.comparison keep search P95 within 10% or 100 ms of the literal-search
baseline, whichever allowance is larger.
swift run focusrelay-dev validate --impact querypasses.Follow-up threshold
Do not add typo-distance or semantic matching in this issue. Record UAT misses;
only create a later bounded enhancement if real queries remain undiscoverable
after broad keyword ranking.