Newer
Older
client_dwatch / docs / handoff / 指示書AG_店舗検索を記号ゆれに強くする.md

指示書AG — 店舗検索を記号のゆれに強くし、画面ごとの食い違いを無くす

項目 値
★★状態★★ ★★完了(2026-09-13)★★ — ★段階1〜6 すべて実行済み★。★決定は ★D-129★(docs/decisions.md)/ 引き継ぎは ★docs/handoff.md 追記43★。★詳細は末尾「7. 実行状態」★
★完了時の実測★ ★tsc 0 / eslint 0 / vitest ★51ファイル 1284件 全通過★(着手前 1230件 → ★+54★)★
作成日 2026-09-13
起点 develop の f2288ad(docs: API契約を実装に追随させ、AEの査読結果を記録する(D-128))
★着手前の基準★ ★tsc 0 / eslint 0 / vitest 50ファイル 1230件 全通過★
依存 ★指示書AE 完了済み★(D-121〜D-128)
★次に来る指示書★ ★AF(上限の設定と編集)→ AC(絞り込みをSQLに降ろす)★。★本書は AC より先に実施すること★(理由は段階5)
着手前確認 ★なし★(下の「着手前確認がない理由」を読むこと)

0. この指示書の目的

★店舗(施設)を探すとき、店名の記号を打たなくても引けるようにする。★

実データが ドン・キホーテ戸塚 / ドン・キホーテ四街道 のとき:

入力 現状 本書の完了後
戸塚 ★ヒットする★ ヒットする(変化なし)
ドン ★両方ヒットする★ 両方(変化なし)
ドン・キホーテ ★両方ヒットする★ 両方(変化なし)
★ドンキホーテ(中点なし)★ ★★0件★★ ★★両方ヒットする★★
★ドンキ★ ★★0件★★ ★★両方ヒットする★★

★部分一致そのものは、すでに全5系統で動いている★。 本書が足すのは ★記号のゆれを吸収すること★ と、★画面ごとの食い違いを無くすこと★ の2つ。

0-1. ★ついでに直す、もっと日常的なバグ★

★照合の規則が、すでに3種類に分裂している★。

系統 ファイル 大文字小文字
施設マスタ lib/facilities/query.ts:67 ★区別しない★
スケジュール lib/schedules/query.ts:100 ★区別しない★
D-S制御盤 lib/ds-devices/query.ts:65 ★区別しない★
★清掃詳細ログ★ ★lib/clean-logs/query.ts:117,120,121★ ★★区別する★★
★レポート★ ★lib/reports/query.ts:192,202★ ★★区別する★★

★これが画面に出ている形★:

入力 施設マスタ 予定 D-S ★清掃詳細ログ★ ★レポート★
8111r(シリアル) ヒット ヒット ヒット ★0件★ ★0件★
★cc1(機種)★ — — — ★★0件★★ —
pudu(機種) — — — ★0件★ —

★機種を小文字で cc1 と打つのは自然な操作★なので、★すでに日常的に踏まれている可能性が高い★。

lib/facilities/query.ts:108-111 のコメントは、この事故をすでに予見している:

★個別パラメータも q と同じく大文字小文字を区別しない★ …… 同じ画面の検索が入力欄によって 当たったり当たらなかったりするほうが事故になるため揃える。

★その配慮が2系統に適用されていない。★ ★本書で5系統すべてを1本の関数に寄せる。★


1. 着手前確認がない理由

★通常なら止まって確認する論点が3つあったが、すべて調査で解消した。★ ★起草時に実測済みなので、実装者は止まらずに進めてよい。★

論点 なぜ止まらなくてよいか
★どこまで正規化するか★ ★ユーザー確認済み(2026-09-13)★ = ★「記号を無視する」のみ★。★カナ・全半角の統一と、スペース区切りのAND検索は選ばれなかった★
★どのフィールドに適用するか★ ★実データで誤ヒットを実測して決めた★(決定事項 D-2)。推測ではない
★SQLに降ろす件(AC)とぶつからないか★ ★実測で成立を確認した★(段階5)。★照合順序では解決しないことも実測済み★(罠5)

2. 決定事項

D-1. ★正規化は純粋関数1本に閉じ込め、5系統すべてがそれを呼ぶ★

★置き場所は lib/format/★。CLAUDE.md §4.6 が契約フラグに課したのと同じ規律である (「桁位置をハードコードせず純粋関数1箇所に閉じ込める」)。

★既存の includesFold()(lib/facilities/query.ts:67-69)は module-private なので、これを昇格させる★のが出発点。

★1箇所だけ直してはいけない★ — ★分裂が3種類から4種類に増えるだけ★。★5系統を同時に寄せること★。

D-2. ★★フィールドによって正規化の強さを変える★★(本書で最も重要な決定)

★記号を全フィールドに一律で適用してはいけない。実データで誤ヒットが出る。★

強い正規化(foldName)— 大文字小文字 + ★記号除去★

フィールド 系統 理由
★facility_name★ ★全5系統★ ★本件の本命★。ドン・キホーテ の中点は表記ゆれで、★桁位置の意味を持たない★
prefecture 施設マスタ 値は 東京都/奈良県/三重県/神奈川県。★記号ゼロなので実質 no-op。無害★

弱い正規化(foldPlain)— ★大文字小文字のみ★

フィールド 系統 ★記号を除いてはいけない理由(実データ)★
★address★ 施設マスタ ★★ハイフンは番地の区切り★★。下の実例を見ること
★serial_number★ 全5系統 ★★形式が未確定★★。下の実例を見ること
maker / model / model_name 施設マスタ / 清掃詳細ログ ★ROBOT_MODELS(lib/config/index.ts:352-357)に記号は1文字も無い★ので、記号除去は効果ゼロ。★直すべきは cc1 が当たらないことのほう★

★住所からハイフンを除いてはいけない — fixtures の実データで起きる★

東京都○○区1-2-3  →(ハイフン除去)→ 東京都○○区123
東京都○○区2-3-4  →(ハイフン除去)→ 東京都○○区234

★住所検索に 23 と入れると、この2件が両方ヒットする。★ ★現状はどちらもヒットしない★ (1-2-3 にも 2-3-4 にも 23 という連続部分文字列が無いため)。

★別の言い方をすると、1-23番地 と 12-3番地 と 1-2-3番地 が区別できなくなる★(3つとも 123)。

★シリアルから記号を除いてはいけない — 桁の境界が消える★

PUDU-CC1-0001  →(ハイフン除去)→ PUDUCC10001

★シリアル欄に 10001(連番10001号機を探したつもり)と入れると、CC1 の 1 と 0001 が 繋がった PUDU-CC1-0001 にヒットする。★ ★現状は0件。★

★そして利得がほぼ無い★ — ★実データの主形式 8111R5628060011 には記号が無く、記号除去は no-op★。 DS-001 形式のために上のリスクを負うことになる。

★さらに lib/config/index.ts:309-313 が明示している★:

★文字種は絞らない★(CLAUDE.md §7「仕様が不明な箇所を推測で実装しない」)。 実データのシリアルの形式は TeamWorks のデータ収集処理が決めるもので、DFA側で確定していない

★形式が未確定のフィールドに「記号は意味を持たない」という前提を置くのは、この禁止事項そのもの。★

D-3. ★除く記号の集合★

★foldName が除くのは次の4種類★。★★それ以外は除かない★★。

種類 文字 何のため
中点 ・(U+30FB) ・(U+FF65) ·(U+00B7) •(U+2022) ★ドンキホーテ → ドン・キホーテ★(本件の本命)
長音 ー(U+30FC) ★サーバ で サーバー を引ける★のと同じ
空白 半角スペース /  (U+3000) / タブ ★ドンキホーテ港南 → MEGAドン・キホーテ 港南★
ハイフン類 -(U+002D) ‐〜―(U+2010-2015) -(U+FF0D) _(U+005F) 店名の区切り

★大文字小文字は toLowerCase() で揃える★(foldName / foldPlain の両方)。

D-4. ★実装しないもの(意図的に残す穴)★

やらないこと 理由
★カナ・全半角の統一★ ★ユーザーが選ばなかった★(2026-09-13)。ドン / どん は引けないまま
★スペース区切りのAND検索★ 同上。ドン・キホーテ 戸塚 は★スペースが除かれて1語として扱われる★
★★英字とカナの読み替え(MEGA ⇄ メガ)★★ ★★どの語を対にするかは仕様判断★★(CLAUDE.md §7)。★★実店舗名が MEGAドン・キホーテ なので、「メガドンキホーテ」では引けないまま★★ — ★ユーザーに伝達済み(2026-09-13)★
Unicode 正規化(NFKC) 上の「カナ・全半角」に含まれる。★入れるなら全半角とセットで一度に★

★「メガドンキホーテ」で引けないのは仕様である★。★★不具合として報告されたら、この行を示すこと★★。

D-5. ★正規化は「両側」に掛ける★

★入力側だけ正規化しても意味がない。★ ★データ側も同じ関数を通してから比較する。★

// ✅ 正しい
foldName(row.facility_name).includes(foldName(query))

// ❌ 間違い(データ側の中点が残るので、結局ヒットしない)
row.facility_name.includes(foldName(query))

D-6. ★fixtures では本件を再現できない★

★lib/fixtures/ の施設名には記号が1文字も含まれていない★(lib/fixtures/facilities.ts:6-7 が 「実在の店舗名を含めない」と明記し、ドン・キホーテ → バリューマート に置換してある)。

★したがってテストは各 __tests__/query.test.ts の合成データで組む★。 ★lib/fixtures/ は変更しないこと★(lib/db/clean-log-mapping.ts:279 に変更禁止の記述がある)。

★合成データには必ず中点入りの名前を含めること★。 ★中点が入っていることだけが要件★なので、ドソ・キホーテ のような1文字違いの架空名でよい。


3. 段階

★段階ごとにコミットすること。★ ★決定番号は着手直前に自分で実測してから採番すること★ (grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3。★本書の起草時点の最終番号は D-128★)。

段階1. ★先に、現状の挙動をテストで固定する★

★★lib/clean-logs/query.ts の検索には、テストが1件も無い★★。

lib/clean-logs/__tests__/query.test.ts の it は全5件で、 ★facility_name / model / serial の検索を固定しているテストは1件も存在しない★ (2件は日付範囲、1件は result=problem の grade 絞り込み)。

★このまま正規化を入れると、間違えても赤くならない。★ ★誰も気づけない。★

やること: lib/clean-logs/__tests__/query.test.ts に、★いまの挙動をそのまま固定する★テストを足す。

# 入力 いまの期待値
1 facility_name に施設名の一部 ヒットする
2 facility_name に存在しない語 0件
3 model に CC1 ヒットする
4 ★model に cc1★ ★★0件(いまはこうなる)★★
5 serial にシリアルの一部(大文字含む) ヒットする
6 ★serial を小文字にしたもの★ ★★0件(いまはこうなる)★★
7 3つを同時指定 AND で効く
8 すべて空文字 素通し(全件)

★#4 と #6 は「いまはこうなる」を固定するテスト★。★段階3 で期待値が反転する★。 ★★反転させる時に、テストを消さずに期待値を書き換えること★★ — ★消すと「昔はこうだった」が失われる★。 ★書き換えたテストには「段階3 で反転させた」とコメントを残すこと★。

コミット: test: 清掃詳細ログの検索の現状挙動をテストで固定する


段階2. ★正規化の純粋関数を作る★

新規: lib/format/search-text.ts

/** 大文字小文字だけを揃える(記号は残す) */
export function foldPlain(value: string): string

/** 大文字小文字を揃え、さらに記号(中点・長音・空白・ハイフン類)を除く */
export function foldName(value: string): string

/** haystack と needle の両方を foldName に通してから部分一致を見る */
export function includesFoldName(haystack: string, needle: string): boolean

/** haystack と needle の両方を foldPlain に通してから部分一致を見る */
export function includesFoldPlain(haystack: string, needle: string): boolean

★includesXxx() を用意するのは、呼び出し側が「片側だけ正規化する」間違い(D-5)をできなくするため★。

★関数コメントに書くこと★(全社ルール7):

  • ★なぜ2種類あるのか★ = ★フィールドによって記号の意味が違うから★(D-2 の実例を引く)
  • ★どの文字を除くのか★ = D-3 の表
  • ★★なぜシリアルと住所に foldName を使ってはいけないのか★★ = D-2 の実例(1-2-3/2-3-4 と PUDU-CC1-0001)
  • ★カナ・全半角・英字カナ読み替えを入れていないこと★(D-4)

新規: lib/format/__tests__/search-text.test.ts

★期待値表を先に作ること★(CLAUDE.md §6-2)。★境界を外さないこと★:

入力(needle) 対象(haystack) includesFoldName includesFoldPlain
ドンキホーテ ドン・キホーテ戸塚 ★true★ false
ドン・キホーテ ドン・キホーテ戸塚 true true
戸塚 ドン・キホーテ戸塚 true true
ドンキ ドン・キホーテ戸塚 ★true★ false
ドンキホーテ港南 MEGAドン・キホーテ 港南 ★true★ false
cc1 PUDU CC1 true ★true★
PUDUCC1 PUDU CC1 ★true★ ★false★
8111r 8111R5628060011 true ★true★
★23★ ★東京都○○区1-2-3★ ★★true(だから住所には使わない)★★ ★★false★★
★10001★ ★PUDU-CC1-0001★ ★★true(だからシリアルには使わない)★★ ★★false★★
空文字 何でも ★true(素通し)★ ★true★
ドン ドン・キホーテ戸塚 ★false(D-4。入れていない)★ false
どん ドン・キホーテ戸塚 ★false(D-4)★ false
★メガドンキホーテ★ ★MEGAドン・キホーテ 港南★ ★★false(D-4。残る穴)★★ false

★除く記号1つずつのテストも書くこと★(中点4種・長音・空白3種・ハイフン類も個別に)。

コミット: feat: 検索用の文字列正規化を lib/format に1本作る


段階3. ★5系統を1本に寄せる★

★D-2 の表のとおりにフィールドごとの強さを割り当てる★。

ファイル 直す行 フィールド 使う関数
lib/clean-logs/query.ts :117 facility_name ★includesFoldName★
同上 :120 model_name includesFoldPlain
同上 :121 serial_number includesFoldPlain
lib/facilities/query.ts :84 facility_name ★includesFoldName★
同上 :85 prefecture ★includesFoldName★
同上 :86 address ★includesFoldPlain★
同上 :89-91 serial_number / maker / model includesFoldPlain
同上 :122 facility_name(個別パラメータ) ★includesFoldName★
同上 :123付近 address / serial_number(個別) includesFoldPlain
lib/schedules/query.ts :100 facility_name(q) ★includesFoldName★
同上 :101 serial_number(q) includesFoldPlain
同上 :127 facility_name(個別) ★includesFoldName★
同上 :133付近 serial(個別) includesFoldPlain
lib/ds-devices/query.ts :65 facility_name ★includesFoldName★
同上 :66 serial_number includesFoldPlain
lib/reports/query.ts :192 facility_name ★includesFoldName★
同上 :202 serial_number includesFoldPlain

★行番号は起草時点のもの。★ ★着手時に必ず自分で確認してから直すこと★(段階1 でテストを足すと行がずれる)。

★lib/facilities/query.ts:67-69 の private な includesFold() は削除し、新しい関数に置き換える★。

★ここで段階1 のテストの期待値が反転する★:

# 入力 段階1 の期待値 ★段階3 後の期待値★
4 model に cc1 0件 ★ヒットする★
6 serial を小文字に 0件 ★ヒットする★

★他の系統の既存テスト(約49件)は、記号の有無を根拠にした否定アサーションを1件も持たないので、 そのまま通るはず★。★通らないものが出たら、期待値を書き換える前に「なぜ変わったか」を説明できること★。

★各系統に、記号ゆれのテストを1件ずつ足すこと★(合成データで。D-6)。 ★5系統すべてに足す★ — ★1系統でも漏れると、そこだけ挙動が違うまま残る★。

コミット: fix: 検索の照合を5系統で1本に揃え、施設名の記号ゆれを吸収する


段階4. ★API契約(YAML)を直す★ — CLAUDE.md §6-1

★検索の意味を変えるので、YAML の description を直す★。

箇所 直すもの
d-watch-api.yaml:256-267 /clean-logs の facility_name / model / serial
:549-556 /reports/facility-performance
:718-721 /ds-devices の q
:848-866 /facilities の q と個別3つ
:889-898 /schedules
★:932-939★ ★/inspections(未実装)★ — ★実装時に同じ正規化を通すことを description に書き残す★

★書く内容★:

  • ★施設名は「記号(中点・長音・空白・ハイフン類)を無視した部分一致」★
  • ★それ以外(シリアル・機種・住所)は「大文字小文字を区別しない部分一致」★
  • ★★なぜ施設名だけ強いのかを1行添える★★(後任が「揃っていない」と思って揃えないように)

★行番号は起草時点のもの★。★指示書AE の段階4 で YAML を触っているので、着手時に自分で確認すること★。

コミット: docs: 検索の照合規則を API 契約に書く


段階5. ★指示書AC を改訂する★ — ★★本書を AC より先にやる理由★★

★AC は「絞り込みをSQLに降ろす」指示書★。★施設名の絞り込みが、AC と正面からぶつかる★。

★なぜぶつかるのか★

★「中点を無視して比較する」関数は MariaDB に無い。★ ★用意するには生成カラム+索引=スキーマ変更(CLAUDE.md §7 で禁止)★。 ★生SQLも禁止★(D-122 の ESLint ルールが落とす)。

★★「MySQL の ci 照合順序なら中点も無視されるはず」は誤り。実測済み★★ 'ドン・キホーテ戸塚' LIKE '%ドンキホーテ%' は ★utf8mb4_unicode_ci / utf8mb4_general_ci / utf8mb4_0900_ai_ci の3種すべてで 0★ (中点入りなら 1)。★この行を消さないこと★ — ★後任が「照合順序でできるはず」と戻ってくるのを防ぐ★。

★解き方★

★施設マスタは小さい★(★fixtures / seed / ローカルDB すべて実測12件★)。だから:

  1. ★施設名の照合はアプリ側で施設マスタに対して行う★
  2. ★当たった施設IDの配列を where: { facility_id: { in: [...] } } に渡す★

★清掃ログ本体(本番想定で clean_timetables 約26万行)はSQLで絞ったまま★なので、 ★AC の目的(読み込み行数を減らす)は損なわれない★。★むしろ日付だけで絞るより縮む★。

★AC のどこを直すか★

AC の箇所 直すもの
:507-514(D-1 引数の型) ★`facility_id: number \ null→facility_ids: number[] \ null★。★null=施設名条件なし /[]`=0件ヒット★ を型のコメントに明記
:558-572(D-4 Prisma の書き方) ...(query.facility_ids !== null ? { facility_id: { in: query.facility_ids } } : {})。★!== null で分岐すること★
:606-612(D-6 APIルート側) ★施設名→IDの解決を1ステップ挟む★記述を足す。★解決関数(resolveFacilityIdsByName() 等)は AC 段階2 で作る★
:616-626(期待値表) ★#6 を facility_ids = [A, B] に差し替え、★#6b: [] → 0件★ / #6c: null → 全件 / #6d: 記号違いでヒット を足す★
:233(C-1 の表) 「施設名の部分一致は降ろせるがやらない」→★「施設名はIDに解決してから降ろす。機種/シリアルは据え置き」★
:859-863(E-5) ★facility_id: { in: [...] } の件数上限の実測値を書く(下の罠8)★
:943(申し送り⑨) 上記に合わせて書き換え
:935-936(申し送り) ★③として clean_timetables に (facility_id, start_at) の複合索引が無いことを足す★(第9回で TeamWorks に依頼する候補)

★AC の段階3 / 段階4 / 段階6 は影響なし★。★段階5 は facilityPerformance() だけが影響を受ける★ (reportKpis / continuousProblems / errorBreakdown に施設名フィルタは無い)。

★★逆順にしてはいけない理由★★

★AC を先にやると、段階2 で確定した「作法」を正規化のためにもう一度書き換えることになる★。 ★段階4・5 がその作法を真似た後なので、修正箇所が3倍に増える★。

コミット: docs: 指示書ACを施設ID解決の形に改訂する


段階6. ★記録★

  • docs/decisions.md に決定を1本(★採番は着手直前に実測★)。書くこと:
    • ★D-2(フィールドごとに強さを変える)の実データ根拠★ — ★これが本書で一番失われやすい★
    • ★D-4(意図的に残した穴)★ — 特に ★「メガドンキホーテ」で引けないのは仕様★
    • ★照合順序では中点を無視できない実測★
    • ★in: undefined が全件を返す実測★(罠1)
  • docs/handoff.md に追記(★先頭の「最新の追記」の番号も直すこと★。過去に番号ズレの事故がある)
  • 本書の冒頭表と実行状態表を更新

コミット: docs: 指示書AGの完了を記録する


4. ★踏みうる罠★

★「エラーも出ず、テストも通ったまま、結果だけ間違う」型を先に並べる。★

# 罠 実測 対策
★1★ ★★in: undefined は全件を返す★★。ids.length ? ids : undefined と書くと「0件ヒット」が「全件表示」に化ける ★実測: in: undefined → 生成SQL WHERE 1=1 → 12件全部★。対して ★in: [] → WHERE 1=0 → 0件(安全)★ ★`number[] \ nullとし、!== nullで分岐★。★?.lengthで分岐しない★。★期待値表に「[]` → 0件」を必ず入れる★
★2★ ★片側だけ正規化する★(D-5)。データ側の中点が残るので結局ヒットしない — ★includesFoldName() を使う★。foldName() を直接呼ばせない
★3★ ★シリアル・住所に foldName を使ってしまう★ D-2 の実例(23 / 10001) ★段階2 の期待値表に「だから使わない」ケースを入れてある★。★消さないこと★
★4★ ★1系統だけ直す★ — ★分裂が3種類から4種類に増えるだけ★。★5系統同時★
★5★ ★★照合順序で中点が無視されると思い込む★★ ★3種の照合順序すべてで 0 を実測★ ★段階5 に明記済み★
6 ★Prisma の contains は % をエスケープしない★ ★実測: contains: "%" → 12件全部★。JS の .includes("%") なら0件 ★本書の案なら施設名をSQLに渡さないので回避される★。★AC で contains を使うなら % _ \ を明示エスケープ★
★7★ ★★論理削除された施設のログが、検索から静かに消える★★ prismaSource.facilities()(lib/db/index.ts:753-754)は ★where: { deleted_at: null }★。一方 cleanLogs() の include: { facility: true } には ★deleted_at の条件が無い★ ★★AC で ID 解決を作るとき、母集合を deleted_at で絞らないこと★★ = ★いまの挙動を変えない★。★db.facilities() をそのまま流用すると挙動が変わる★ので、解決用の読み取りを別に用意し、★理由をコメントに残す★
8 ★施設200件超で実行計画が静かに劣化する★(遅くなるだけでエラーは出ない) ★ローカル MySQL 8.4 実測 eq_range_index_dive_limit = 200★。★MariaDB 10.11 の値は未確認★。★Prisma の maxBindValues は mysql で 65,535、超えると自動分割(65,536要素→3クエリを実測)★ ★現状12件なので当面問題にならない★。★申し送りに「本番で SHOW VARIABLES LIKE 'eq_range_index_dive_limit' を実測」と書く★
9 ★USE_FIXTURES の true/false で件数が変わる★ AC 禁止事項6(:884)が明示的に禁じている ★fixtures 版も null/[] を同じ意味で扱う★
★10★ ★どの画面からも呼ばれていないパラメータを直し忘れる★ ★filterFacilitiesByFields(lib/facilities/query.ts:113-132)と filterSchedules の個別パラメータは、どの画面からも送られていない★(API直叩き専用) ★それでも API 契約上は存在する★。★直さないと「同じAPIのパラメータ間で挙動が違う」状態が新規に生まれる★

5. ★禁止事項★

  • ❌ ★lib/fixtures/ を変更すること★(変更禁止。D-6)
  • ❌ ★シリアル・住所に記号除去を適用すること★(D-2)
  • ❌ ★1系統だけ直すこと★(D-1)
  • ❌ ★カナ・全半角の統一や英字カナ読み替えを、勝手に足すこと★(D-4。★ユーザーが選ばなかった★)
  • ❌ ★生SQL★($queryRaw / $executeRaw / *Unsafe / Prisma.raw)— ★ESLint が落とす(D-122)★
  • ❌ ★メインDBのスキーマ変更★(生成カラム・索引追加を含む。CLAUDE.md §7)
  • ❌ ★any★ / ★閾値のハードコード★
  • ❌ ★git add -A★ — ★必ずファイルパスを明示する★
  • ❌ ★次のファイルに触ること★: README.md / docs/code-map.md / docs/handoff/管理画面_設計検討_20260910.md / .claude/**
  • ❌ ★段階1 で足したテストを、段階3 で削除すること★(★期待値を書き換える。消さない★)

6. 完了条件

  • npx tsc --noEmit = ★0★
  • npx eslint . = ★0★
  • npx vitest run ★全通過★(★着手前 1230件★。増分の内訳を報告すること)
  • ★5系統すべてに記号ゆれのテストがあること★
  • ★d-watch-api.yaml が実装に追随していること★(CLAUDE.md §6-1)
  • ★指示書AC が改訂されていること★(段階5)
  • ★git status に、着手前から増えた無関係な変更が無いこと★

7. 実行状態

★★全段階 完了(2026-09-13)★★。★決定番号は着手直前に実測した★ (grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -5 → 最終 D-128 を確認 → ★D-129★ を採番)。

段階 内容 決定番号 コミット 状態
1 現状挙動の固定テスト — 9d6a5b1 ★★完了★★(テスト +8)
2 正規化の純粋関数 ★D-129★ cd1769f ★★完了★★(lib/format/search-text.ts。テスト +40)
3 5系統を1本に寄せる ★D-129★ f4149c9 ★★完了★★(テスト +6。★5系統すべてに記号ゆれのテストあり★)
4 API契約(YAML) — 47ca671 ★★完了★★(★6箇所すべて★。/inspections は未実装だが契約として書き残した)
5 指示書AC の改訂 — 871a5f4 ★★完了★★(★申し送り番号の注意は下の備考★)
6 記録 ★D-129★ (本段階のコミット) ★★完了★★(docs/decisions.md D-129 / docs/handoff.md ★追記43★ / 本表)

★備考★

  • ★★段階5 で指示書AC の申し送り番号が1つずれました★★ — ★改訂で ⑩⑪⑫ を足したため、 ★★本書 §3 段階5 の表が「:943(申し送り⑨)」と書いている項目は、改訂後の AC では★★⑩★★です★★★。 ★本書の行番号・番号は起草時点のもの★なので、AC 側を見るときは ⑩ を読むこと。 ★AC 側は改訂済み(docs/handoff/指示書AC_絞り込みをSQLに降ろす.md の冒頭「改訂」行に改訂箇所の一覧あり)★
  • ★段階1 で固定した「いまはこうなる」2件(model に cc1 → 0件 / serial 小文字 → 0件)は、 段階3 で★★期待値を反転させた。消していない★★★(lib/clean-logs/__tests__/query.test.ts に 「段階3 で反転させた」旨のコメントあり)
  • ★完了条件(§6)の実測★: tsc 0 / eslint 0 / vitest ★51ファイル 1284件 全通過★ / ★5系統すべてに記号ゆれのテストあり★ / ★YAML 追随済み★ / ★AC 改訂済み★ / ★git status に着手前から増えた無関係な変更なし★(README.md / docs/code-map.md / docs/handoff/管理画面_設計検討_20260910.md は★着手前からあった他作業の変更。触っていない★)
  • ★★増分の内訳★★: ★段階1 +8 / 段階2 +40 / 段階3 +6 = +54★(1230 → 1284)。★段階4〜6 は増減なし★