ローカルプロンプトのフィルタリングを単語境界マッチ+先頭位置順ソートに刷新

basecamp/lexxy

Lexxyのローカルプロンプト(メンション補完など)のフィルタリングロジックを変更し、単語の途中にマッチするノイズを排除しつつ、マッチ位置が先頭に近い候補を優先して表示するようになりました。

背景

これまでの実装では、フィルタ文字列が検索テキストのどこに出現してもマッチとして扱われていました。例えば「em」で検索すると、名前の途中に「em」を含む「David Bemmer」のような候補もリストに現れ、関連性の低い結果が混入していました。また、「Emma Smith」と「Anna Emilio」が並んでいる場合、入力した「em」がどちらの名前のより早い位置にマッチしているかは考慮されず、元の並び順のまま表示されていました。

これらの問題により、候補リストの直感的な順序付けが損なわれ、ユーザーが目的の候補を素早く選択しにくい状況が生じていました。

技術的な変更

string_helper.js の関数シグネチャと、それを呼び出す local_filter_source.js の処理フローが同時に変更され、「マッチするかどうか」から「どの位置でマッチするか」を返す設計に切り替わりました。

src/helpers/string_helper.js では、filterMatches 関数が filterMatchPosition 関数に置き換えられました。新関数はマッチした位置のインデックスを返し、マッチしない場合は -1 を返します。内部では String.prototype.includes の代わりに正規表現を使用し、(?:^|\b) の前置パターンによって単語の先頭またはテキスト先頭でのマッチのみを許容します。また、フィルタ文字列に正規表現の特殊文字が含まれる場合に備えて escapeForRegExp ヘルパー関数も追加されました。

変更前:

export function filterMatches(text, potentialMatch) {
  return normalizeFilteredText(text).includes(normalizeFilteredText(potentialMatch))
}

変更後:

export function filterMatchPosition(text, potentialMatch) {
  const normalizedText = normalizeFilteredText(text)
  const normalizedMatch = normalizeFilteredText(potentialMatch)

  if (!normalizedMatch) return 0

  const match = normalizedText.match(new RegExp(`(?:^|\\b)${escapeForRegExp(normalizedMatch)}`))
  return match ? match.index : -1
}

function escapeForRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
}

src/editor/prompt/local_filter_source.js では、#buildListItemsFromPromptItems メソッドの処理フローが再構成されました。変更前はループ内で即時にリストアイテムを構築していましたが、変更後はまず全候補に対して filterMatchPosition を呼び出してマッチ結果を { promptItem, position } の配列に収集し、position で昇順ソートしてからリストアイテムを構築します。またフィルタ文字列が空の場合は新設の #buildAllListItems メソッドに処理を委譲し、無駄な照合処理を回避しています。

変更後のフロー:

テストは Playwright を使ったブラウザテストとして追加されました。test/browser/tests/prompts/mention_filtering.test.js では「ja」で検索したとき Jack FranklinJason ClackClara JacksonThomas Jaiden の順(マッチ位置の昇順)で4件が返ること、および「mid」で検索したとき Sam Smidjam がヒットしないこと(単語途中のマッチを除外)の2ケースが検証されています。

設計判断

filterMatches(boolean返却)をfilterMatchPosition(インデックス返却)に置き換える設計が採用されました。マッチ有無の判定とソートキーの取得を1回の処理で済ませられるため、全候補を2度走査する必要がありません。

単語境界の実現に \b アンカーを用いた正規表現が選ばれています。normalizeFilteredText によってダイアクリティカルマークを除去した正規化済み文字列に対してマッチングを行う設計になっており、フィルタ文字列の特殊文字エスケープも escapeForRegExp で明示的に処理しています。

また、フィルタが空の場合の早期リターンを #buildAllListItems として切り出しているのも注目点です。フィルタなし時はソート処理が不要であるため、パスを分離することで不要なオーバーヘッドを避けています。

まとめ

本PRは「単純な文字列包含チェック」から「単語境界を考慮した位置返却」へとフィルタリングの基礎関数を再設計し、その戻り値をソートキーとして活用するアーキテクチャへと移行しています。マッチ判定とランキングを単一の関数呼び出しで完結させた設計は、将来的にスコアリングロジックを拡張する際の基盤にもなり得ます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
8b37b9bc

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)」の3部構成が明確に守られています。特に、任意項目である「設計判断」セクションが設けられており、変更の意図が深く理解できる構成になっています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きのシンタックスハイライト(```javascript:src/helpers/string_helper.js)およびPR番号のリンク記法([#975](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

正規表現の解説や関数シグネチャの変更点など、専門知識を持つエンジニアを対象とした適切な技術レベルで記述されています。初心者向けの冗長な説明はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論から各論へと展開され、各段落はトピックセンテンスで始まっています。1段落1トピックの原則が守られ、段落長も適切です。非常に読みやすい構成です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロック(`filterMatches`の削除前コード、`filterMatchPosition`の追加後コード)は、提供されたDiff情報と完全に一致しています。また、`local_filter_source.js`の変更ロジックやテストファイルの内容についても、Diffを正確に反映した説明がなされています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「単語境界 (`\b`)」、「関数シグネチャ」、「ダイアクリティカルマーク」などの技術用語が文脈に応じて正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

正規表現 `(?:^|\b)` の役割、`filterMatchPosition` の戻り値(インデックスまたは-1)、そしてその戻り値を使ったソート処理など、技術的な説明はすべて正確で論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張は、PRのDescriptionやDiff内のコード変更に裏付けられています。特に、「背景」セクションの例はPR Descriptionの例を反映しており、ハルシネーションは見られません。「設計判断」もコードから読み取れる合理的な分析であり、推測や憶測ではありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#975)、ファイルパス、関数名(`filterMatchPosition`など)といった固有名詞はすべて正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

元のPRタイトル「Update filtering method」よりも具体的で、記事の内容(単語境界でのマッチと位置によるソート)を的確に要約した優れたタイトルです。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事はPRで提供された情報のみに基づいており、バージョン情報やリリース予定など、PR外の知識を持ち込んでいません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「〜するようになりました」「置き換えられました」など、完了した変更を正しく表現する時間表現が使われており、PRの内容と一致しています。