# PR説明の骨子 — `develop` → `main`

> ★★これは「骨子」です。確定版ではありません。★★
>
> - 本書の数字は ★すべて `develop` の `992a456`（2026-09-16）時点での実測★ です。
>   ★`develop` は並行作業で進むため、PRを出す直前にもう一度実測し直してください★（→ 付録のコマンド）。
> - ★★§6（マージ方式）は「ユーザー判断待ち」です。★★ 本書では両案の帰結を実データで併記するだけにしてあり、
>   ★どちらかに決めていません★。決めた結果は `docs/decisions.md` に決定として記録してください。
> - ★★PRを出す前に `develop` を push する必要があります。★★ 実測で ★`origin/develop` は `develop` より
>   71コミット遅れています★（`git rev-list --count origin/develop..develop` → ★71★）。
>   ★GitHub の PR は `origin/develop` を見るので、push しないと本書の数字とPRの中身が一致しません★。
>
> 更新日: 2026-09-16（初版 2026-09-11）／
> 対象: `develop`（★`992a456`★）→ `main`（★`1f214cc`★, PR #5 マージ, ★2026-08-27★)
> ※ 実測: `git log -1 main --format='%H %ad %s' --date=iso` → `1f214cc… 2026-08-27 00:44:18 +0900 Merge pull request #5 …`

---

## 0. このPRの位置づけ

- ★`main` は 2026-08-27 の PR #5 で止まっており、以降 ★約3週間★ の作業がすべて `develop` に載っています。★
  範囲内の最初のコミットは ★2026-08-30★、最後は ★2026-09-16★（実測 `git log --date=short main..develop`）。
- `main` は `develop` の祖先です（`git merge-base --is-ancestor main develop` が真）。
  ★競合はありません。★ 範囲内にマージコミットは ★0本★ で、履歴は一直線です。
- 過去のPR #1〜#5 は「1マイルストーン＝1PR」でしたが、★本PRは #1〜#5 の全体量を大きく超える1本★になります。
  そのため本説明では「何が入るか」より★「どこを見ればレビューが成立するか」に重心を置きます★（§2）。

---

## 1. 何が入るのか（機能単位）

> コミット順ではなく成果物で整理しています。時系列は `docs/handoff.md` の追記1〜52、
> 判断の根拠は `docs/decisions.md` の ★D-050〜D-141★ を参照してください。
> ★入口としては `docs/handoff/セッション引き継ぎ_20260916.md`（347行）が最短です。★

### 1-1. 実DB接続 ★このPRの主題★

PR #5 時点ではアプリは `lib/fixtures/` のサンプルデータだけで動いていました。本PRで
★「実データの入ったテストDBから読んで画面が出る」ところまで★到達しています。

| 内容 | 主なファイル | 根拠 |
|---|---|---|
| Prisma + MariaDB アダプタでの接続、`USE_FIXTURES` による fixtures / Prisma 切替 | `lib/db/index.ts`, `lib/db/prisma.ts`, `lib/db/use-fixtures.ts` | D-044 / D-045 / D-086 |
| `schema.prisma` を実DDL（訂正版0911）に追従。率13列＋面積10列を double 化 | `prisma/schema.prisma`（★+936 / -6★） | D-056 / D-059 / D-094 / D-096 / D-113（指示書E・Q・X） |
| ローカル検証用DB `dwatch_local`（MySQL 8.4）と冪等なシード | `scripts/seed-local-db.ts` | D-095（指示書M） |
| 誤接続ガード（`dwatch_local` 以外への書き込みを構造的に止める） | `lib/db/local-guard.ts:23` `assertLocalDwatchDatabaseUrl()` | 指示書M 段階2-4 |
| DB行→APIモデルの変換を純粋関数に分離 | `lib/db/clean-log-mapping.ts`, `report-mapping.ts`, `precheck-mapping.ts`, `ds-status.ts` | 指示書R / S / T |
| ★DATETIME を「日本時間の壁時計値」として読む★ | `lib/format/date.ts:67` `fromDbJst()` / `:95` `jstIsoFromDb()` | ★D-114 / D-115（指示書Y）★ |
| ★絞り込み・並べ替え・期間を SQL に降ろした（全件読み込みをやめた）★ | `lib/db/index.ts`（`DataSource` 全メソッドが取得条件を必須引数で受ける） | ★D-131（指示書AC）★ |
| ★接続情報を部品変数から組み立てる（パスワードの `@` 問題・リモートDBのタイムアウト対策）★ | `lib/db/prisma.ts` `mariadbAdapterConfig()` | 追記48〜52 の申し送り |

★`prismaSource` の実装状況★

- ★未実装（`notImplemented()`）は ★2本★ だけです — `inspections` / `inspectionDetail`
  （実測: `lib/db/index.ts:1705` / `:1708`）。→ §3 #1
- ★初版で未実装としていた `continuousProblems` は実装済み★（`lib/db/index.ts:2302`。D-116）。

### 1-2. 画面ごとの実装

PR #5 時点の実装済み画面は F-01 / F-02 中心でした。本PRで★画面が一通り揃います★
（実測: `components/**/*.tsx` ★26本★、API ルート `app/api/**/route.ts` ★22本★、
`app/(dashboard)/` 配下 ★11ディレクトリ★）。

| 画面 | 場所 | 主な内容 |
|---|---|---|
| F-01 ダッシュボード | `app/(dashboard)/dashboard/`, `components/features/dashboard/` | 実DB由来の集計に切替。★要レスキュー店舗を詳細ログの上に追加★（D-135 / D-137） |
| F-02 清掃詳細ログ | `.../detail-log/`, `app/api/clean-logs/` | 実DB化。CSVエクスポート、行展開アドバイス。★期間上限を2年（731日）に拡大★（D-136）。★エリア別完了率モーダル★（D-119） |
| F-03 アラート（事前チェック） | `.../alerts/`, `app/api/prechecks/` | 実DB化（指示書S / D-105）。★`precheck` は「分数そのもの」として読む★（D-112 / D-113） |
| F-04 レポート | `.../reports/`, `app/api/reports/*` | ★新規★。KPI / 施設別パフォーマンス / エラー種別内訳 / 連続問題施設（指示書F・T・V・W）。★確定期間KPIは対象顧客が1社に定まるときだけ出す★（D-138 / D-140） |
| ★清掃面積★ | `.../clean-area/`, `app/api/clean-area/`, `components/features/clean-area/` | ★★新規（モニタリングの4つ目 📐）★★。ホールディングス/屋号/店舗の粒度で清掃面積・対象面積・完了率。★サマリを読まず `clean_status_histories` から集計★（D-141） |
| F-06 D-Secログ | `.../ds-logs/`, `app/api/ds-devices/` | 2状態化＋備考入力（指示書G / D-076）、実DB化（指示書P） |
| F-07 D-Secアラート | `.../ds-alerts/`, `app/api/ds-alerts/` | `detail` 列削除（D-088）、実DB化 |
| F-08 施設ロボットマスタ / F-09 スケジュール / F-10 システム管理 | `.../facilities/`, `.../schedules/`, `.../system/` | ★新規3画面★（指示書L / D-084）。システム管理から★連続日数の表示上限を編集可能★（D-130） |
| ログイン | `app/login/`, `app/api/auth/login`, `.../logout` | ★新規★（指示書I / D-101） |
| 点検記録 | `app/(dashboard)/inspections/page.tsx` | ★プレースホルダのみ。メニュー非表示（`components/layout/AppShell.tsx:22` でコメントアウト）★ → §3 #1 |

横断で入ったもの:

- ★一覧6画面のページ送り★（`components/ui/Pagination.tsx` / `lib/pagination/page-items.ts`。★モックに無いUIを例外として新設★。D-120）
- ★分類（ホールディングス / 屋号 / 店舗）の3階層絞り込みを復活★ — `GET /groups` `/customers` を新設し、
  一覧系11エンドポイントと5画面に通した。★D-053 の「マスタが揃うまで削除」を解除★（D-138）
- ★店舗検索を記号のゆれに強くした★（`ドンキ` で `ドン・キホーテ戸塚` が引ける。5系統に分裂していた照合規則を1本に統一。D-129）。
  ★記号だけの入力は0件にする★（D-134）
- 対応機種レジストリと「未対応」バッジ（`lib/config/index.ts` の `ROBOT_MODELS` / D-087）、
  `(値なし)` 表示とラベル折り返し（`lib/format/raw-label.ts` / D-089）、i18n の器（`lib/i18n.ts`）、
  3桁区切り表示（`lib/format/number.ts`）

### 1-3. 判定ロジック

PR #3 で入れた `lib/judge/` を土台に、★仕様確定にあわせて作り直した部分★が入っています。

- ★★判定の境界を「含める」に変えた★★ — ★90.0 は ◎ / 70.0 は ○★。★× の境界だけは「10%未満」のままなので 10.0 は △★。
  実装は `lib/judge/grade.ts:31-33` の比較演算子だけ（`rate >= excellent` / `rate >= good` / `rate < poor`）。
  ★`GRADE_THRESHOLDS` の 90 / 70 / 10 は変えていない★。★★D-020b（境界は下のランクに落ちる）は破棄★★。
  **D-139（ユーザー指示 2026-09-16「◯、×は90%以上のように境はふくめてください」）／追随漏れの片付けは指示書AM。**
  ★期待値表は `lib/judge/__tests__/grade.test.ts:26,30,33` に固定済み★
- ★月跨ぎ週を TeamWorks 方式（月境界で分割）に統一★ — 週の長さ1〜7日、月の週番号1〜6。
  帰属ルールは `lib/format/period.ts` の `weekOfMonth()` だけが知る。2020〜2040の全252か月・1313週で総当たり検証済み。
  **D-071 / D-072 / D-073（指示書H）。D-067 の旧DFA案と D-024 は破棄。**
- ★清掃結果の判定を `clean_status_histories.clean_error` に置き換え★ — 出発失敗＝`clean_error === 1`
  または履歴行なし。判定入口は `notExecutedFromHistory()` 1本。**D-106 / D-107（指示書U）**
- ★連続日数を施設単位に揃え、当期の分母から未来を外す★ — 分割キーは `facility_id`（`serial_number` は誤り）。
  同一日は `foldProblemDaysByDate()` で畳む。施設ステータスに4値目 `no_data` を追加。**D-108〜D-110（指示書V）**
  ★表示上限を入れ、その1つだけをシステム管理画面から編集可能にした★（D-130）
- ★要レスキュー店舗の判定を新設★ — `lib/judge/rescue.ts`。★3条件★（未実施3日連続 / 連続問題L3 / 完了率の前回比20%以上の急落）。
  ★件数はAPIで切らず、初期5件＋「もっと表示」は画面の責務★。**D-135 / D-137**。
  ★★ユーザー原文の4条件目「出発失敗」だけ未実装★★ → §3 #6
- ★エラー率の分子を本清掃側にした★（事前チェック行のエラーは数えない）。**D-114 項2 / D-118**
- ★エラー種別は変換せず、DBの生値をそのまま表示★ — 7分類への寄せは破棄。**D-077 / D-088（指示書K）**

### 1-4. 認証とアクセス制限 ★新規★

PR #5 時点は「URLを知っていれば全画面が開く」状態でした。本PRで解消しています（D-101 / 指示書I）。

- `proxy.ts` — ★`middleware.ts` ではない★（Next.js 16 で `middleware` は非推奨）。
  ★実行順は 相関ID発行 → IP制限(403) → 認証(401 / `/login` リダイレクト)★。順序に意味があるので入れ替えないこと。
- 簡易トークン + httpOnly / Secure / SameSite=Lax cookie（`dwatch_session`）。★トークン期限は実装しない★（D-030）。
- IP制限 — ★`X-Forwarded-For` の「末尾」を見る★（nginx が追記する。先頭は詐称可能）。
  `TRUSTED_PROXY_HOPS = 1`（LBと nginx は同一＝1段。D-079 項1）。許可リストは `lib/config/allowed-ips.ts`（完全一致のみ、CIDR非対応）。
- 相関IDと構造化ログ — `lib/logger/`、`instrumentation.ts`、`docs/logging-design.md`。
  ★認証で弾いたリクエストにも request_id が付く★ように相関IDを最初に発行している。

### 1-5. セキュリティの作り込み（★本PRで後半に追加★）

指示書AE / AC の後追いで、★ユーザー提起「SQLをDBに流すようにしたならインジェクションやHTMLは大丈夫か」★に答えた記録が入っています。

- ★★生SQL禁止を ESLint ルールとして機械化★★ — `eslint.config.mjs:41-60` の `no-restricted-syntax`。
  対象は `app/**` `lib/**` `components/**` `scripts/**` `proxy.ts`。★CI の `npm run lint` が落とす★。**D-122**
- ★SQLインジェクション / XSS の成立経路は0件★ — 推測ではなく実際に攻撃文字列を投げ、発行SQLと生HTMLを見て確認。
  ★ただし副産物として「実データを入れた日に初めて出る不具合」を3件見つけて潰した★（`control_reports` の全件読み、
  備考保存の全件読み、顧客サマリを顧客で絞っていない）。**D-132 / D-133**
  - ★★`take: 1` は1文字もSQLに降りない★★ — 本PRで最も失われやすい知見（D-133 項2）
- パスパラメータ検証を `parseInt` の前方一致から全体一致に（`lib/api/path-params.ts`。**D-123**）／
  `PATCH /settings/advice` の長さ上限（**D-124**）／CSVの数式インジェクション対策（**D-125**）。
- ★セキュリティヘッダはアプリに入れず nginx への申し送りにした★（**D-126**。★実装ではなく「約束」であることに注意★）。

### 1-6. 運用の仕組み

- `docs/deploy.md` — 本番デプロイ手順、nginx の XFF 追記要件、403 の切り分け表。
  ★`next start` が XFF を自分で埋めるという実測上の落とし穴★を含む。
- `npm run check:env`（`scripts/check-env.ts` + `lib/config/env-check.ts`）— 環境変数の検査。
  ★`ALLOW_ALL_WHEN_EMPTY` を本番で `false` にし忘れると誰でも通る★ため、ビルド前に挟める終了コード設計。
- `.env.production.example` — 実値を書かないテンプレート。
- JSONストアのアトミック書き込み＋`.bak`（`lib/settings/json-store.ts` / D-075 / 指示書J）。
- CI は PR #2 で入れた `.github/workflows/ci.yml`（lint / test / build）をそのまま使用。
  ★現時点では「結果を表示するだけ」で、赤くてもマージできます★（同ファイル冒頭コメント）。

### 1-7. 資料

★このPRの差分の約56%はドキュメントです（★86ファイル / +75,081行★）。★ レビュー負荷の見積もりに使ってください。

| 資料 | 実測（`git diff --numstat main..develop`） | 中身 |
|---|---|---|
| `docs/decisions.md` | ★+10,872 / -5★ | 決定台帳。★D-050〜D-141 を追加★ |
| `docs/handoff.md` | ★+4,824★ | 作業ログ。★追記1〜52★ |
| `docs/handoff/` 配下 | ★77ファイル / +55,635★ | ★指示書 D〜AN の36本（`AD` は欠番）★、★TeamWorks とのやり取り 第1回〜第9回★、`analysis/`、`pdf/`（PDF本体は非コミット）、★`セッション引き継ぎ_20260916.md`★ |
| `docs/code-map.md` | +2,548 | ★新規★。コードの地図 |
| `docs/architecture.md` / `docs/logging-design.md` / `docs/deploy.md` | +192 / +519 / +346 | 新規 |
| `d-watch-api.yaml` | ★+1,199 / -160★ | API契約。コードより先にここを直す運用（D-040） |
| `CLAUDE.md` | ★+170 / -11★ | §4.1 判定境界 / §4.7 週 / §4.8 生値 / §7 禁止事項 などの追加 |

---

## 2. レビューで見るべき点

> ★13万行を全部読むのは不可能です。★ 以下の5点だけ見れば、このPRのリスクは概ね押さえられます。
> 逆に言うと、★それ以外（画面のCSS・fixtures・ドキュメント）は詳細レビュー不要★という提案です。

### 2-1. 判定ロジックと期待値表テスト ★最重要★

読むファイル:

- `lib/judge/`（`grade.ts` / `escalation.ts` / `kpi.ts` / `advice.ts` / ★`rescue.ts`★）と `lib/judge/__tests__/`
  （`grade.test.ts` ★9★ / `rescue.test.ts` ★48★ / ほか `advice` `escalation` `kpi`。数値は `it(`/`test(` の記述行数）
- `lib/db/report-mapping.ts` と `lib/db/__tests__/report-mapping.test.ts`（★142★ — 単一ファイル最多）
- `lib/db/clean-log-mapping.ts` と `lib/db/__tests__/clean-log-mapping.test.ts`（★68★）
- `lib/format/period.ts` と `lib/format/__tests__/period.test.ts`（★33★ — 月跨ぎ週）
- ★`lib/clean-area/`★ と その `__tests__/`（`aggregate` 21 / `anomaly` 5 / `visibility` 7。D-141）

見るポイント:
1. ★境界値が期待値表として固定されているか★（完了率 90.0 / 90.1 / 70.0 / 70.1 / 10.0 / 9.9 / 0 / 未実施）
   — ★★D-139 で「90.0 は ◎ / 70.0 は ○ / 10.0 は △」に変わっています★★。古い理解（D-020b）で読まないこと
2. ★丸める前の生値で判定しているか★（`89.5 → 表示90% だが判定は ○`）
3. ★連続日数が「スケジュール存在日の連続」で数えられているか★（カレンダー日ではない）
4. ★閾値がハードコードされていないか★（`lib/config/` から読んでいるか）
5. ★`components/` に置いたテストは走らない★（`vitest.config.ts` の `include` は `lib/**` と `app/**` のみ）。
   判定は純粋関数にして `lib/` に置く、が守られているか

### 2-2. 書き込みが4カラムに限定されていることの確認 ★実測値つき★

CLAUDE.md §3 / §7 の「メインDBは読み取り専用。書き込みは4カラムのみ」が守られているかは、
★次の2コマンドで機械的に確認できます★（`lib/generated/prisma` は Prisma の生成物なので除外）。

```bash
# 1. 生SQL は1件も無い（★さらに ESLint の no-restricted-syntax が構造的に禁止している。D-122★）
grep -rn '\$executeRaw\|\$queryRaw' --include=*.ts --include=*.tsx app lib components scripts prisma proxy.ts \
  | grep -v 'lib/generated'
# → 0件（ヒットするのは eslint.config.mjs のルール定義とコメントのみ）

# 2. アプリコード（app/ lib/ components/ proxy.ts）に Prisma の書き込み呼び出しは1件も無い
grep -rnE '(prisma|tx)\.[a-zA-Z_]+\.(update|updateMany|create|createMany|delete|deleteMany|upsert)\(' \
  --include=*.ts --include=*.tsx app lib components proxy.ts | grep -v 'lib/generated' | wc -l
# → 0
```

> ★初版の grep（`\.(update|create|...)\(` の素通し）は `Object.create()` や
> `response.cookies.delete()` を拾ってノイズになります。上の形に直してあります。★

★DBへの書き込みは `scripts/seed-local-db.ts` にしか存在しません★（実測 ★44箇所★、すべて `scripts/` 内）。
これはローカル検証用DB `dwatch_local` を作り直すシードで、`lib/db/local-guard.ts:23` の
`assertLocalDwatchDatabaseUrl()` により★`localhost` かつ DB名 `dwatch_local` 以外では例外で止まります★
（`scripts/seed-local-db.ts:79` で呼んでいる）。

★結論: 現時点では 4カラム（`status` / `comment` / `responded_at` / `resolved_at`）への UPDATE すら実装していません。★
★★理由は「やり忘れ」ではなく、TeamWorks から★DB更新用アカウントを受領できていない★ためです★
（2026-09-03 から未受領。第5・6・8・9回で継続依頼中）→ §3 #7。
運用設定（アドバイス文言、D-Sec備考、連続日数の表示上限）は ★メインDBではなく `/data` 配下のJSONストア★に書いています
（`lib/settings/json-store.ts`。`/data` は `.gitignore` 済み）。

### 2-3. 認証とIP制限

- `proxy.ts` — ★実行順（相関ID → IP制限 → 認証）を確認★。
- `lib/config/allowed-ips.ts:35` — ★`ALLOW_ALL_WHEN_EMPTY` は ★★`true` のまま★★★。
  ★本番で `false` にしないと誰でも通ります★（`docs/deploy.md` §3 / §5）→ §3 #10。
- `lib/auth/client-ip.ts` と `lib/auth/__tests__/client-ip.test.ts` — ★XFF の末尾を取っているか★。
  先頭を取ると偽装ヘッダで素通りします。偽装XFFが403になることは実測済み（`docs/handoff.md` 追記29）。
- `lib/proxy/__tests__/authenticate.test.ts`。
- ★未確認事項: 本番 nginx 配下での実測はまだ行えていません★（本番環境未構築）。

### 2-4. 機微情報がコミットされていないこと

```bash
git ls-files | grep -E '(^|/)\.env'
# → .env.example と .env.production.example の2本だけ（テンプレート。実値なし）
```

- `.gitignore` に `.env*` があり、`!.env.example` / `!.env.production.example` で2本だけ除外しています。
  ★`.env.local`（実際の接続情報）は追跡外★。
- ★経緯の申告★: `lib/db/__tests__/local-guard.test.ts` に、TeamWorks から受領した参照用アカウントの
  ★実在のDBユーザー名が2箇所入っていました★。★コミット `c832891`（2026-09-10）で `testuser` に置換済み★です
  （実測: 現在は `testuser:pw@example.com` / `testuser:s3cret@example.com` の2箇所のみ）。
  - ★パスワードは架空値（`pw` / `s3cret`）、ホスト名は `example.com`★ でした。漏れたのは★ユーザー名のみ★。
  - ★履歴は書き換えていません★（`a5502ac` に残存。実測: `a5502ac` は `develop` の祖先）。
    force push 禁止（CLAUDE.md §6-5）かつ並行セッションが動いていたためです。
    ★本番接続前のパスワード再発行は D-078 項1 に記録済み。★
  - ★この1点だけはマージ方式の判断に影響します → §6 を参照★。
- ★★あわせて渡すべきインフラ側の申し送り4件★★（`docs/handoff.md` 追記41。★期限は「ステージングに実データが入る前」★）:
  ① テストDB `133.125.238.144` が公開IPに平文（`have_ssl=DISABLED` / `require_secure_transport=OFF`）／
  ② `npm audit` が `mariadb` コネクタに「`ssl: true` でも平文パスワードが漏れる」を報告（★`No fix available`★）／
  ③ 上記のGit履歴残存／④ 本番構成（VM＋DBアプライアンス）の明示確認が未取得（D-100）。
  ★★これらはアプリコードでは直せません★★（アプリコード側に危険な箇所は1件も見つかっていない）。

### 2-5. 自動生成物

- ★`lib/generated/prisma` は追跡外★（`.gitignore:47`）。`prisma generate` で再生成されます。
  grep するときは必ず除外してください（除外しないとノイズで結果を読み違えます）。
- ★`prisma/schema.prisma`（+936行）は `prisma db pull` の生成物★です。★手で書き換えないこと★（D-056）。
  レビューでは「実DDLと一致しているか」ではなく「生成物をそのまま入れているか」を見れば十分です
  （実DDLとの照合結果は `docs/handoff/analysis/ddl-diff-dbpull.md`）。
- ★`docs/handoff/pdf/*.pdf` は追跡外★。原稿Markdownと `make_*_pdf.py` から再生成します。
- `package-lock.json`（+853 / -124）は依存追加に伴う機械的差分です。

---

## 3. 意図的に未完成な部分 ★「作り忘れ」ではありません★

> すべて★本書の更新時（2026-09-16 / `992a456`）にコードを実測して確認★しています。
> ★初版（2026-09-11）にあった5件（areas の501 / continuousProblems / ページ送り / メモリ絞り込み / DATETIMEのTZ）は
> 実装を確認したうえで本表から削除しました★（削除の根拠は §3-2）。

| # | 内容 | 実測での現れ方（`file:line`） | 根拠 |
|---|---|---|---|
| 1 | ★点検記録（`inspections` / `inspectionDetail`）が未実装★ | `lib/db/index.ts:1705` / `:1708` で `notImplemented()`。画面は `PlaceholderScreen`（`app/(dashboard)/inspections/page.tsx`）。`components/layout/AppShell.tsx:22` でメニュー行をコメントアウト | ★ユーザー判断（2026-09-11）で後回し。サービス展開自体も保留★ **D-111 項6** / D-083 / D-084 |
| 2 | ★本番DBに未接続★（★テストDBには接続済み★） | 実データの確認はすべて★テストDB `133.125.238.144`（読み取りのみ）★と★ローカル `dwatch_local`★。本番のホスト・ポートは未受領 | D-078 / D-111 項7 / `docs/handoff.md` 追記41 |
| 3 | `PATCH /prechecks/{id}/status` が未実装 | `d-watch-api.yaml:630` にはあるが `app/api/prechecks/` に `route.ts` は一覧用の1本のみ | D-105。★#7（更新用アカウント）と同根★ |
| 4 | 契約フラグ（`facilities.contract`）によるメニュー表示制御が未実装 | `parseContract()` の呼び出しは `lib/db/index.ts:1632`（施設行のフラグ化）だけ。`AppShell.tsx` のメニューは静的配列 | メニューは施設横断のグローバルUIで「どの施設の契約か」が決まらない。★P2で顧客アカウント導入時に対応★ D-083 項5 |
| 5 | B3（オフライン）のアドバイス発火文字列が空 | `lib/config/advice-rules.ts:124-125`「現在リストは空のため発火しない（値が未判明）」 | 実データで値が判明するまで保留。D-078 項2 / D-082 |
| 6 | ★要レスキュー店舗の4条件目「出発失敗」が未実装★ | `lib/judge/rescue.ts:34` の `RescueReason` は3値（`not_executed_streak` / `escalation_l3` / `completion_drop`）。★「出発失敗」と「未実施」が `notExecutedFromHistory()` に潰れている★ | ★実際の清掃開始時刻がDBに無く猶予30分を計算できない★。TeamWorks ★第9回 Q11-1★ 待ち。★回答が来たときの足し方は `docs/handoff.md:4426-4440` に手順化済み★ D-135 |
| 7 | ★4カラムの UPDATE が1本も書けない★ | `app/`・`lib/` に Prisma の書き込み呼び出し ★0件★（§2-2） | ★★DB更新用アカウントが2026-09-03 から未受領★★（第5・6・8・9回で継続依頼） `docs/handoff.md:2925-2943` |
| 8 | ★面積の単位「㎡」に裏付けがない★ | `lib/config/index.ts:696` `AREA_UNIT = "㎡"` | ★DFAの判断。TeamWorks 第9回 Q5-1 で照会中★。★画面・APIに "㎡" を直書きせず1行で直せるようにしてある★ D-141 |
| 9 | ★サマリテーブルの面積列を使っていない★ | `lib/clean-area/` は常に `clean_status_histories` から集計 | ★実測でサマリの `task_area` が全24行 `1000` 固定＝合計になっていない★。第9回 Q2-1 で照会中 D-141 |
| 10 | ★`ALLOW_ALL_WHEN_EMPTY` が `true` のまま★ | `lib/config/allowed-ips.ts:35` | 開発を止めないための既定値。★本番前に `false` にすること★ D-101 |
| 11 | ★ページングは SQL に降りていない★ | `app/api/clean-logs/route.ts:98-104` — SQL で日付・施設まで絞ったあと、`filterCleanLogs()`（機種・シリアル・判定）と `slice()` はメモリ | ★意図的な決定★。SQLでページングすると `meta.total` が判定フィルタ後の総件数と食い違う。現状の実データ量（1日あたり数十行）ならメモリで足りる。★施設数が増えたら見直す（D-131 申し送り④⑥）★ |
| 12 | ★単一ID取得（`/clean-logs/{id}/advice` / `/{id}/areas`）の連続日数クエリだけ全期間のまま★ | 対象ログの日付が引くまで分からず遡り範囲を決められない。★実測: エリアモーダル1,084行のうち約1,080行がこれ★ | D-131 申し送り⑬ |
| 13 | ★施設200件超で `facility_id: { in: [...] }` の実行計画が静かに劣化★ | ローカル MySQL 8.4 で `eq_range_index_dive_limit = 200` を実測（★MariaDB 10.11 の値は未確認★）。現状は施設12件 | D-131 申し送り⑫ / D-132 |
| 14 | ★索引が7件未追加★ | 第9回1章で依頼中（うち2件は索引ゼロ列）。★2年検索（D-136 で上限731日に拡大）が重いまま★ | 第9回 依頼①〜⑦ |
| 15 | ★複数顧客の合算ができない★ | `group_summaries` テーブルが無い。★確定期間KPIは対象顧客が1社に定まらないと 422★（`lib/reports/target-customer.ts`） | ★これは仕様であって欠陥ではない★（D-138 項2）。合算の可否は第9回 Q6-1 / Q6-2 |
| 16 | ★ブラウザでの目視確認が未実施★ | ★追記50・51・52 の3回連続で未実施★（`docs/handoff.md:4663` / `:4717` / `:4795`）。確認は SSR HTML と API 応答まで | ログインが要る環境のため。★「もっと表示」は実DB（施設10件）では踏めず `USE_FIXTURES=true` が要る★ |
| 17 | ★スマートフォン幅（375px）のサイドバー220px・本文155px は未対応★ | `docs/decisions.md:10591` | D-137 |

### 3-2. ★初版の「未完成」から削除した5件 — 削除の根拠（実測）★

| 初版 # | 内容 | 決定 | ★本書更新時に確認した実物★ | 判定 |
|---|---|---|---|---|
| #2 | `GET /clean-logs/{id}/areas` が 501 | D-119 | `app/api/clean-logs/[id]/areas/route.ts` に ★501 を返すコードは無い★（`grep -rn "501" app/api` のヒットは同ファイル `:13` のコメント「501 の仮実装（D-034）をやめて本実装にした」1件のみ）。`db.areaCompletions()` と `lib/clean-logs/areas.ts` を呼ぶ本実装 | ★★解決済み★★ |
| #3 | `continuousProblems` が未実装 | D-116 | `lib/db/index.ts:2302` に `async continuousProblems(query)` の Prisma 実装。★`notImplemented()` は `:1705` `inspections` / `:1708` `inspectionDetail` の2本だけ★ | ★★解決済み★★ |
| #4 | 一覧画面にページ送りが無い | D-120 | `components/ui/Pagination.tsx` と `lib/pagination/page-items.ts`（+ `__tests__`）が存在。★6画面が参照★（`alerts` / `detail-log` / `ds-alerts` / `ds-logs` / `facilities` / `schedules`） | ★★解決済み★★ |
| #5 | 全件取得してメモリで絞り込んでいる | D-131 | `app/api/clean-logs/route.ts:82-92` — ★日付・施設IDは `db.cleanLogs({ date_from, date_to, facility_ids })` で SQL に降りている★。`DataSource` 全メソッドが取得条件を必須引数で受ける | ★★「全件取得」は解決済み★★。★ただしページングと一部フィルタは意図的にメモリのまま★ → §3 #11・#12 に移した |
| #6 | DATETIME のタイムゾーンが未確定 | D-114 / D-115 | ★TeamWorks 第8回 A2 で「JST の壁時計値」と確定★。`lib/format/date.ts:67` `fromDbJst()` / `:95` `jstIsoFromDb()` が実在し、`lib/db/index.ts:1308` ほかで使用 | ★★解決済み★★ |

> ★★5件すべて、決定台帳の記載とコードの実物の両方を確認できました。「未検証」で残したものはありません。★★

---

## 4. 数字

> ★すべて `develop` = `992a456`（2026-09-16）時点の実測★。再実測コマンドは付録。

### 4-1. 差分の規模（`git -c core.quotepath=false diff --numstat main..develop`）

| 区分 | ファイル数 | 追加 | 削除 |
|---|---|---|---|
| **合計** | **★393★** | **★+133,051★** | **★-1,117★** |
| ドキュメント（`docs/`） | ★86★ | ★+75,081★ | -19 |
| └ うち `docs/handoff/` | 77 | +55,635 | 0 |
| └ `docs/decisions.md` 単体 | 1 | +10,872 | -5 |
| └ `docs/handoff.md` 単体 | 1 | +4,824 | 0 |
| コード（`app/` `lib/` `components/` `scripts/` `proxy.ts`） | ★291★ | ★+54,299★ | -712 |
| └ うちテスト（`__tests__/`） | ★95★ | ★+24,579★ | -23 |
| `d-watch-api.yaml` | 1 | +1,199 | -160 |
| `prisma/schema.prisma`（`db pull` 生成物） | 1 | +936 | -6 |
| `package-lock.json` | 1 | +853 | -124 |
| `CLAUDE.md` | 1 | +170 | -11 |

★ドキュメントが約56%、テストが約18%。実装コードの純増はおよそ29,700行★という内訳です。

> ★★`--numstat` は日本語ファイル名をクオートして出すため、`-c core.quotepath=false` を付けないと
> `docs/` の集計が 37ファイル / +42,078 に見えます（実際は 86 / +75,081）。★★ 初版の数字はこの罠を踏んでいます。

### 4-2. コミット

- ★★356コミット、マージコミット0本、履歴は一直線★★
  （`git rev-list --count main..develop` → 356 ／ `git log --oneline --merges main..develop | wc -l` → 0）
- 種別: `docs:` ★167★ / `feat:` ★127★ / `fix:` ★38★ / `chore:` ★6★ / `test:` ★3★ / `refactor:` ★3★ /
  `style:` ★1★ / ★規約に沿わない接頭辞 11本★（初期の移行期のもの）
- ★356コミット中 ★104コミット★ がコミットメッセージ（件名）に決定番号（D-0xx / D-1xx）を含む★ → §6 の判断材料
- 期間: ★2026-08-30 〜 2026-09-16★
- ★★`origin/develop` は `develop` より71コミット遅れています★★（未push）。★PRを出す前に push が必要★

### 4-3. テスト ★実測★

- ★★96ファイル / 2,141テスト 全パス★★（2026-09-16、`992a456` で `npm test` を実行した実測値）
- 参考（静的に数えた値）: 追跡下のテストファイル ★96本★、`it(` / `test(` の記述行 ★1,640行★
  （`it.each` / `describe.each` によるテーブル駆動があるため、展開後の 2,141 と整合）
- 単一ファイル最多は `lib/db/__tests__/report-mapping.test.ts`（★142記述★）
- ★★`npx tsc --noEmit` / `npm run lint` / `npm run build` は本書の更新作業では実行していません★★ → §8 で消化すること
  （直近の記録では `tsc` 0 / `lint` 0。`docs/handoff.md:4161-4162` ほか）

### 4-4. 決定・資料

| 項目 | 実測 |
|---|---|
| 決定事項 | ★★129件★★（`grep -oE '^#+ *D-[0-9]+[a-z]?' docs/decisions.md \| grep -oE 'D-[0-9]+[a-z]?' \| sort -u \| wc -l`）。最終 ★★D-141★★。欠番あり。枝番は `D-020b` の1件のみ |
| └ うち本PRで追加 | ★D-050〜D-141★ |
| 指示書 | ★★36本（指示書D 〜 指示書AN。`AD` は欠番）★★ ＋ `管理画面_設計検討_20260910.md` ＋ `セッション引き継ぎ_20260916.md` |
| TeamWorks とのやり取り | ★第1回〜第9回★（第1〜8回は回答受領済み。★★第9回の全20問＋依頼9件は未回答★★） |
| 作業ログ（`docs/handoff.md`） | ★追記1〜52★（4,824行） |
| API ルート（`app/api/**/route.ts`） | ★22本★ |
| 画面コンポーネント（`components/**/*.tsx`） | ★26本★ |
| 画面ディレクトリ（`app/(dashboard)/`） | ★11★（うち `inspections` はプレースホルダ） |

> ★★`docs/decisions.md` は番号順に並んでいません。★★ 位置を取るときは
> `grep -n "^### D-" docs/decisions.md` を使ってください（★`^### D-0` だと3桁台を拾えません★）。

---

## 5. PRのタイトル案

★どれも「実DB接続」を主語にしています。★ #1〜#5 が「M0:」「M1:」のようなマイルストーン接頭辞や
機能名で始めていたので、それに揃えました。

1. **`実DB接続・画面11枚・認証/IP制限・レポート/清掃面積・分類の復活を develop から取り込む（D-050〜D-141）`**
   — 中身を並べる案。最も情報量が多く、レビュアーが範囲を掴みやすい。★推奨★
2. **`M2: fixtures から実DBへ — Prisma接続・レポート・認証・マスタ3画面・清掃面積（356コミット）`**
   — PR #1「M0:」/ #3「M1:」の連番を継ぐ案。台帳運用と相性が良い。
3. **`develop を main に同期（2026-08-30〜09-16 の全作業。実DB接続・認証・レポート・決定D-050〜D-141）`**
   — 「溜まった差分の同期」であることを正面から書く案。★1本が大きい理由が伝わる★。

---

## 6. マージ方式 ★★ユーザー判断待ち — 本書では決めません★★

> ★★合意は未取得です。★★ 初版は「マージコミット推奨」と書いていましたが、
> ★これは本書の筆者の提案であってユーザー判断ではありません★。
> 本節では ★この案件の実データ★ だけを並べます。★決めた結果は `docs/decisions.md` に決定として記録してください★。

### 6-0. 前提として確定している事実（実測）

| 事実 | 実測値 | コマンド |
|---|---|---|
| `main..develop` のコミット数 | ★356★ | `git rev-list --count main..develop` |
| 範囲内のマージコミット | ★0本★ | `git log --oneline --merges main..develop \| wc -l` |
| リポジトリ全体のマージコミット | ★5本★（PR #1〜#5 のみ。★日常の作業は直接 push 運用でマージコミットが1本も無い★） | `git log --oneline --merges \| wc -l` |
| `main` は `develop` の祖先か | ★真（fast-forward 可能。競合なし）★ | `git merge-base --is-ancestor main develop` |
| 件名に決定番号を含むコミット | ★104 / 356★ | `git log --oneline main..develop \| grep -cE 'D-[0-9]{2,3}'` |
| `docs/decisions.md` が名指しするコミットハッシュ | ★90個（7桁）。うち89個が実在し、★★89個すべてが `main..develop` の範囲内★★★ | 付録参照 |
| `docs/handoff.md` が名指しするコミットハッシュ | ★176個。うち★174個が `main..develop` の範囲内★（残り2個は範囲外の既存コミット）★ | 付録参照 |

### 6-1. ★マージコミット（`--no-ff`）を選んだ場合★

**得られるもの**

1. ★356コミットの履歴が `main` に残る★。★104コミットが件名に決定番号を持つ★ので、
   `git log --grep 'D-071'` で該当変更に辿り着けます。
   `docs/decisions.md` はこのプロジェクトの★一次資料★で「なぜこの仕様か」を遡るときに読むと CLAUDE.md §5 が定めています。
2. ★決定台帳・作業ログが名指しする ★263個★ のコミットハッシュ参照（89 + 174）が、★`main` の履歴から解決できます★。
3. ★仕様が2〜3回ひっくり返っている箇所の経緯が追えます★
   （月跨ぎ週: D-024 → D-067 → D-071 ／ エラー種別: D-057/D-067 → D-077 ／
   ★判定境界: D-020b → D-139★ ／ ★連続日数の70.0の扱い: D-109 決定3 → D-139 で撤回★）。
4. PR #6 という★1つの区切り点★が `main` に残り、「2026-08-30 から 09-16 までの塊」が後から特定できます。

**引き受けるもの**

- ★`c832891` で除去した実在のDBユーザー名が、それ以前のコミット（`a5502ac`）の履歴に残っています。★
  ★履歴ごとマージすると、この文字列が `main` の履歴にも入ります。★
  - 入っているのは★ユーザー名のみ★。パスワードは架空値（`pw` / `s3cret`）、ホスト名は `example.com`。
  - このリポジトリは公開されていません。
  - 本番接続前にパスワードを再発行する方針が D-078 項1 に記録済み。
  - ★`c832891` のコミットメッセージで「履歴は書き換えない」と明示的に判断済み★（force push は CLAUDE.md §6-5 と衝突）。
- ★`main` の `git log` が356本増え、`main` だけを見ている人には読みにくくなります。★

### 6-2. ★squash を選んだ場合★

**得られるもの**

- ★`main` の履歴が1コミットになり、PR単位で読めます。★ 変更内容は §1 と `docs/` に残るので、
  ★「何が入ったか」は失われません★。
- ★`a5502ac` の実在ユーザー名が `main` の履歴に入りません。★（★ただし `develop` ブランチを残す限り
  リポジトリからは消えません★。消すには履歴の書き換えが要り、それは別の判断です）

**引き受けるもの**

- ★104コミットの「決定番号 ↔ 変更」の対応が1個に潰れます。★ `git log --grep 'D-071'` が効かなくなります。
- ★263個のハッシュ参照は `main` からは解決できません。★
  （★`develop` を残せばリポジトリ内には残るので「死んだ参照」にはなりません。
  ただし「`main` の履歴を辿って理由に行き着く」経路は切れます★）
- ★1コミットの差分が +133,051 / -1,117 になり、`git blame` が全行この1コミットを指します。★

### 6-3. ★判断に必要な問い（ユーザーへ）★

1. ★`main` の履歴を「決定を遡る経路」として使いますか★、それとも `docs/decisions.md` だけを一次資料としますか。
2. ★マージ後に `develop` ブランチを残しますか★（残すなら squash でもハッシュ参照はリポジトリ内に残ります）。
3. ★`a5502ac` のユーザー名残存を `main` に持ち込まないことを優先しますか★。

> ★★この3問への回答が決まるまで、本節はどちらにも倒しません。★★
> なお ★rebase / force push はどちらの案でも不要★です（すでに一直線で、force push は CLAUDE.md §6-5 と衝突します）。

---

## 7. レビュー体制

★CLAUDE.md §6-5「PRは必ずもう1人がレビューしてからmainにマージする」★に従い、
★このPRは作成者以外の1名のレビュー承認を得てからマージします。★

- ★1人で13万行は読めません。★ §2 の5点（判定ロジックとテスト / 書き込み4カラム / 認証とIP制限 /
  機微情報 / 自動生成物）に絞ってレビューしていただく想定です。
- §2-2 と §2-4 は★コマンドの実行結果で確認できます★。コードを読む必要はありません。
- ★実質的に読んでいただきたいのは §2-1 の判定ロジックとその期待値表テストです。★
  ここだけは仕様のズレが直接バグになるため、目視でのレビューをお願いします。
  ★特に D-139（判定境界を「含める」に変更）は、古い理解（D-020b）のまま読むと全部逆に見えます★。

---

## 8. マージ前チェックリスト

> ★消化状況は 2026-09-16 / `992a456` 時点の実測です。★

- [x] ★指示書X 完了★（`clean_timetables.precheck` を分数そのものとして読む）
      — ★実測★: `git log --oneline main..develop --grep="指示書X"` で段階1〜4 ＋ 完了記録の ★6コミット★
      （`d8064a1` / `1fafea7` / `0b48ce4` / `fc99231` / `628ba09`）。決定は ★D-113★（`docs/decisions.md:6851`）
- [x] ★指示書W 完了★（`continuousProblems` を実DBから作る）
      — ★実測★: 段階1〜7 ＋ 完了記録の ★8コミット★（`e24e177`〜`b214449`, `d1b7fec`）。決定は ★D-116★（`:7302`）。
      コードは `lib/db/index.ts:2302`
- [x] ★§4-1 の差分規模と §4-2 のコミット数を再実測して差し替え★ — 本書で実施（356コミット / 393ファイル / +133,051）
- [x] ★§3 の未実装リストから、解消した項目を削除★ — 本書で実施（5件を削除。根拠は §3-2）
- [x] ★§4-4 の決定件数・最終決定番号・指示書本数を再実測して差し替え★ — 本書で実施（129件 / D-141 / 36本）
- [x] ★`npm test`★ — ★96ファイル / 2,141テスト 全パス★（2026-09-16、`992a456` 実測）
- [ ] ★`npm run lint` / `npx tsc --noEmit` を実行し直す★ — ★本書の更新作業では未実行★。
      直近の記録では 0 / 0（`docs/handoff.md:4161-4162`）だが、★その後のコミットで再実測していない★
- [ ] ★`npm run build` が通る★ — ★未実測★
- [ ] ★★`develop` を push する★★ — ★`origin/develop` が71コミット遅れ★（`git rev-list --count origin/develop..develop` → 71）。
      ★push しないとPRに本書の内容が載りません★
- [ ] CI（lint / test / build）が緑 — ★PRを作らないと走りません★（`on: pull_request`）。
      ★現状のCIは「結果を表示するだけ」で赤くてもマージできる★点に注意（`.github/workflows/ci.yml`）
- [ ] レビュアー1名の承認（CLAUDE.md §6-5）
- [ ] ★★マージ方式の判断★★（§6。★ユーザー判断待ち★。決めた結果を `docs/decisions.md` に記録）

### ★PRとは別に、マージ前に決めておくと良いもの（任意）★

- [ ] TeamWorks ★第9回★（2026-09-15 / 716行）の★送付状況の確定と記録★
      — ★`grep -n "第9回" docs/decisions.md` で送付記録が見つからない★。過去に第1回・第4回で同じ記録漏れ
- [ ] §2-4 の★インフラ申し送り4件★を正式に手渡す（期限: ステージングに実データが入る前）
- [ ] §3 #16 の★ブラウザ目視確認★（fixtures 版と実DB版の2周）

---

## 付録: 本書の数字を再実測するコマンド

```bash
# --- 差分規模（★日本語ファイル名があるので core.quotepath=false は必須★）---
git -c core.quotepath=false diff --shortstat main..develop
git -c core.quotepath=false diff --numstat main..develop > /tmp/ns.txt
awk -F'\t' '{a+=$1;d+=$2;n++} END{print "TOTAL files="n" +"a" -"d}'                        /tmp/ns.txt
awk -F'\t' '$3 ~ /^docs\//    {a+=$1;d+=$2;n++} END{print "docs  files="n" +"a" -"d}'      /tmp/ns.txt
awk -F'\t' '$3 ~ /__tests__/  {a+=$1;d+=$2;n++} END{print "tests files="n" +"a" -"d}'      /tmp/ns.txt
awk -F'\t' '$3 ~ /^(app|lib|components|scripts)\/|^proxy\.ts/ {a+=$1;d+=$2;n++} END{print "code  files="n" +"a" -"d}' /tmp/ns.txt

# --- コミット ---
git rev-list --count main..develop                              # → 356
git log --oneline --merges main..develop | wc -l                # → 0
git log --oneline main..develop | grep -cE 'D-[0-9]{2,3}'       # → 104
git log --format='%s' main..develop | sed -E 's/^([a-z]+)(\(.*\))?:.*/\1/' | sort | uniq -c | sort -rn
git merge-base --is-ancestor main develop && echo "fast-forward 可能"
git log -1 main --format='%H %ad %s' --date=iso                  # main の最終更新
git rev-list --count origin/develop..develop                     # → 71（未push）

# --- §6 の材料: 台帳が名指しするコミットハッシュが範囲内かを数える ---
git rev-list main..develop > /tmp/range.txt
for f in docs/decisions.md docs/handoff.md; do
  grep -oE '`[0-9a-f]{7}`' "$f" | tr -d '`' | sort -u > /tmp/h.txt
  in=0; out=0; nf=0
  for h in $(cat /tmp/h.txt); do
    full=$(git rev-parse --verify -q "$h^{commit}" 2>/dev/null)
    if   [ -z "$full" ];                       then nf=$((nf+1))
    elif grep -q "^$full$" /tmp/range.txt;     then in=$((in+1))
    else                                            out=$((out+1)); fi
  done
  echo "$f: total=$(wc -l < /tmp/h.txt) inRange=$in outside=$out notFound=$nf"
done
# → docs/decisions.md: total=90 inRange=89 outside=0 notFound=1
# → docs/handoff.md:   total=176 inRange=174 outside=2 notFound=0

# --- 書き込み面（lib/generated は Prisma の生成物なので必ず除外する）---
grep -rn '\$executeRaw\|\$queryRaw' --include=*.ts --include=*.tsx app lib components scripts prisma proxy.ts \
  | grep -v 'lib/generated'
grep -rnE '(prisma|tx)\.[a-zA-Z_]+\.(update|updateMany|create|createMany|delete|deleteMany|upsert)\(' \
  --include=*.ts --include=*.tsx app lib components proxy.ts | grep -v 'lib/generated' | wc -l   # → 0

# --- 未実装の確認 ---
grep -n 'notImplemented(' lib/db/index.ts     # → :1705 inspections / :1708 inspectionDetail のみ

# --- 機微情報 / 自動生成物 ---
git ls-files | grep -E '(^|/)\.env'            # → .env.example と .env.production.example のみ
git check-ignore -v lib/generated/prisma       # → .gitignore:47

# --- 決定件数（★このファイルは番号順に並んでいない★）---
grep -oE '^#+ *D-[0-9]+[a-z]?' docs/decisions.md | grep -oE 'D-[0-9]+[a-z]?' | sort -u | wc -l  # → 129
grep -n "^### D-" docs/decisions.md | tail -3                # ★^### D-0 だと3桁台を拾えない★

# --- 資料の本数 ---
ls docs/handoff/指示書*.md | wc -l              # → 36（D〜AN。AD は欠番）
find app/api -name route.ts | wc -l             # → 22
find components -name '*.tsx' | wc -l           # → 26
git ls-files | grep -E '__tests__.*\.test\.tsx?$' | wc -l   # → 96

# --- テスト ---
npm test            # → 96ファイル / 2,141テスト 全パス（2026-09-16 / 992a456）
npm run lint
npx tsc --noEmit
npm run build
```
