Skip to content
This repository was archived by the owner on Aug 26, 2026. It is now read-only.

層の境界(common/ lib/ app/ mcp/)をESLintで強制する - #60

Merged
reitojike merged 8 commits into
mainfrom
feat/43-layer-boundary-lint
Aug 8, 2026
Merged

reitojike merged 8 commits into
mainfrom
feat/43-layer-boundary-lint

Conversation

@reitojike

@reitojike reitojike commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Closes #43

PO確認ゲートの判定 (docs/decision-policy.md)

確認不要と判定し、そのまま実装した。

  • 「確認を挟む」5条件のいずれにも該当しない。成果物どうしの矛盾はなく(docs/roadmap.md
    フェーズ3が「検討する」、docs/lint-policy.md に層の節が無いだけで、両者は両立している)、
    外から観測できる製品の挙動(権限の可否・既定値・保存されるデータ・画面表示)は動かない。
    docs/prd.md / docs/data-model.md / docs/permissions.md には一切触れていない
  • 「確認を挟まず進めてよい」に該当する。判断が「どう実装するか」に閉じており、
    外れても設定を戻せば元に戻る(マイグレーション・永続データ・公開APIに触れない)
  • docs/lint-policy.mddocs/roadmap.md の更新はIssue app/がlib/の結果を直接フィルタ・集計しないようno-restricted-importsで層を強制する #43 の完了条件に明記された追記で、
    「Issueで内容が確定している追記」に当たる

決定した方針

1. 対象の粒度 — app/lib/ のimportは禁止しない

app/lib/ のクエリ関数を呼ぶこと自体は正当なI/O呼び出しである。ここを塞ぐと、
app/ からデータに到達する経路が無くなり(common/ は純粋に保つ必要があるので中継できない)、
結局Supabaseクライアントを直接握ったクエリが app/ に生えるだけになる。新しい中間層を
作る案も検討したが、それは CLAUDE.md のディレクトリ構成そのものの変更になるため見送った。

**禁止したいのは「importすること」ではなく「結果を使って判断すること」**なので、
importの向き(no-restricted-imports)と判断の実体(no-restricted-syntax)を分けて掛けた。

禁止するimport 判断ロジック(配列操作)の禁止
common/ app/ lib/ mcp/ / Next.js・React系 / @supabase/* — (ここが置き場所)
lib/ app/ mcp/ / common/ の値としてのimport(import type は可) 掛けない(理由は下記)
app/ mcp/ / @supabase/* する
mcp/ app/ / Next.js・React系 / @supabase/* する

配列操作の禁止対象: filter find findIndex findLast findLastIndex flatMap
every some includes indexOf sort toSorted reduce reduceRight
.map() は描画のための変換として正当なので除いた。この一覧は
CLAUDE.md「ルールをpure関数に切り出す」の
「フィルタ、並び順、検証、集計、権限判定、日付計算」をそのまま機械語に落としたもの。

2. mcp/ は対象に含めた

app/ と同じく common/ を経由せず lib/ を直接叩いて判断する余地があり、片方だけ
古いルールで動き続けるのがこのリポジトリで最も痛い壊れ方(CLAUDE.md「MCPサーバーと
Web UIは同じ操作を2経路持つ」)。着手時点で mcp/ は空なので、含めるコストはゼロだった。
加えて mcp/ からはNext.js・React系も禁止した(stdioサーバーがNext.jsを丸ごと
読み込むのを防ぐ)。

3. lib/ に構文ルールを掛けなかった理由

PostgRESTのクエリビルダが .filter() を持つ(supabase.from(...).select().filter("col", "eq", v))
ため、同名で誤検知する。lib/ 側は「common/ を値としてimportしない」制約だけで押さえた。
lib/ 内での .reduce() による集計は機械では止まらないという残ったギャップは
docs/lint-policy.md「残っているギャップ」に明記した。

4. message の文言

「何が禁止か」ではなく**「代わりにどこへ書くか」**を書き、根拠の成果物と節名を必ず添えた。
例(app/ / mcp/ の配列操作):

取得結果をここでフィルタ・並び替え・集計しない。フィルタ・並び順・検証・集計・権限判定・
日付計算は common/ のpure関数に切り出し、ここは結果を描画/返却するだけにする
(CLAUDE.md「ルールをpure関数に切り出す」)。アプリを起動しないと到達できないルールは
テストされない。表示のための単純な変換は .map() で書く。

5. drainは不要 — 最初からerror

app/ はNext.jsの雛形2ファイルのみ、lib/mcp/.gitkeep だけ。既存違反0件のため
docs/lint-policy.md のdrain→ratchetでいう「新規開発の段階では最初からerrorで入れられる」に
該当する。

実装上の落とし穴(設定に反映済み)

すべて docs/lint-policy.md「設定を書くときの落とし穴」にも書いた。
この設定は「1つの制約を2つの書き方で表現する」箇所が多く、片方だけ直すと静かに穴が開く。

  • パッケージ名を paths に書くとすり抜ける。paths は完全一致しか見ないため、
    next を禁止しても next/headers が通る。patterns に移した
  • パッケージ名の「形」ごとに穴が開く。next next/x だけでなく、ハイフン系(next-auth
    react-hook-form)とスコープ付き(@next/env @react-three/fiber)も別に列挙が要る
  • 相対パスでもすり抜ける。@/lib/x を禁止しても ../../lib/x は通る。かといって ../
    個数を列挙するとその数が検出の上限になる(App Routerのルート木は5階層を簡単に超える)。
    ../**/lib の形で受けて深さ非依存にした
  • **動的 import()no-restricted-imports の対象外。**静的なimport宣言しか見ないため
    await import("react") で全部すり抜ける。no-restricted-syntaxImportExpression
    セレクタに同じ制約を掛けた。こちらは正規表現で書くのでグロブ側と対で管理する
  • files の拡張子。*.ts *.tsx だけだと .mts .cts や素のJSが対象外になる。
    拡張子リストを1か所に置いて組み立てた
  • allowTypeImports@typescript-eslint/no-restricted-imports にしかない。
    型を二重定義しない方針と両立させるため、lib/common/ の制約だけ拡張ルールを使った

壊して確認した (完了条件)

レビューで対象が2回広がった(動的import()includes/indexOf、スコープ付きパッケージ)ので、
最終状態の eslint.config.mjs に対して全パターンをまとめて実行し直した。
一時ファイルを置いて yarn lint が赤くなることを確認し、削除して緑に戻ることを確認している。

静的import (19件、すべてerror)

置いた場所 書いたコード
app/ lib/ の結果に .filter()
app/ lib/ の結果に .reduce()
app/ ALLOWED_IDS.includes(userId)
app/ ALLOWED_IDS.indexOf(userId) !== -1
app/ import { createClient } from "@supabase/supabase-js"
app/ import ... from "@/mcp/..."
common/ import { useState } from "react"
common/ import ... from "@/lib/..."
common/ import ... from "../lib/..."
common/a/b/c/d/e/f/ import ... from "../../../../../../lib/..." (6階層)
common/ import { useForm } from "react-hook-form"
common/ import { auth } from "next-auth"
common/ import { env } from "@next/env"
common/ import { Canvas } from "@react-three/fiber"
common/ import { createClient } from "@supabase/supabase-js"
common/ import { cookies } from "next/headers"
lib/ import { canJoinEvent } from "@/common/permissions" (値)
lib/ import ... from "../mcp/..."
mcp/ import { cookies } from "next/headers"

動的 import() (13件、すべてerror)

置いた場所 書いたコード
common/ await import("react")
common/ await import("next/headers")
common/ await import("next-auth")
common/ await import("@next/env")
common/ await import("@react-three/fiber")
common/ await import("@supabase/supabase-js")
common/ await import("@/lib/x")
common/ await import("../lib/x")
app/deep/x.cts await import("@/mcp/tool")
app/deep/x.cts await import("@supabase/ssr")
app/deep/x.cts rows.filter(...)
mcp/x.js await import("next/headers")
mcp/x.js await import("@/app/page")

.cts.js を混ぜてあるのは、拡張子の網羅(ts tsx mts cts js jsx mjs cjs)が
効いていることを同時に確認するため。

通ることも確認した (誤検知がないこと)

  • common/ から import { z } from "zod" / await import("zod") → 通る
  • common/ から await import("@reduxjs/toolkit") → 通る(@react-* と紛らわしいが別物)
  • lib/ から import type { InviteContext } from "@/common/permissions" → 通る
  • app/ から @/lib/...(I/O)と @/common/...(判断)をimportし、.map() で描画 → 通る

一時ファイル削除後、yarn lint / yarn typecheck / yarn test はいずれも緑。

承知の上で受け入れた誤検知

.includes() / .indexOf() はレシーバの型を見ないので、文字列に対する
pathname.includes("/events") も止まる。それでも対象に含めたのは非対称性による。
**「厳しすぎて例外が出る」は例外リストに1行足せば戻せるが、「判断が静かに app/ に残る」は
誰も気づかない。**引っかかったときの手順も docs/lint-policy.md に書いた。

スコープ外として分離したもの

.mts ファイルを1つ置くと yarn lint がlintエラーではなくクラッシュする。
型情報を要するルールのブロックが **/*.mts を対象にしている一方、eslint-config-next
パーサーが .mtsparserOptions.project を転送しないため。main でも再現する
このPR以前からの別件なので #61 に切り出した。

見送ったもの

  • lint設定そのもののリグレッションテスト。ESLint#lintText で層越えコードを流す
    ユニットテストを検討したが、projectService が有効なため実在しない仮想ファイルの扱いが
    不安定(実際に .mts の実ファイルで型情報ルールがクラッシュしており、.mts ファイルを置くと yarn lint がクラッシュする(型情報ルールのパーサー設定漏れ) #61 に分離した)。
    また eslint.config.mjs の他のルールも同様にテストされておらず、ここだけ入れると非対称になる。
    入れるなら設定全体に対する方針として別途決める
  • **eslint-plugin-boundaries などの専用プラグイン。**依存を増やさずビルトインで表現できた
  • common/ での new Date() 禁止(docs/roadmap.md フェーズ2の書き方の制約)。
    同種の「文書化済みの制約を機械化する」話だが、このIssueのスコープ外なので触っていない

🤖 Generated with Claude Code

CLAUDE.md「ディレクトリ構成」が定める依存の向きを、no-restricted-imports と
no-restricted-syntax で固定する。向きが逆でもtscは通りテストも緑になるため、
機械が止めないと誰も気づかない箇所だった。

- common/ は app/ lib/ mcp/ とフレームワーク・Supabaseへの依存を禁止
- lib/ は app/ mcp/ と、common/ の値としてのimportを禁止(import type は可)
- app/ は mcp/ と @supabase/* を禁止
- mcp/ は app/ と next*/react*、@supabase/* を禁止
- app/ mcp/ では判断ロジックの実体である配列操作(filter/reduce/sort 等)を禁止。
  .map() は描画のための変換として許可

app/ から lib/ へのimport自体は禁止しない。正当なI/O呼び出しであり、塞ぐと
Supabaseクライアントを直接握ったクエリが app/ に生えるだけになるため。
禁止したいのは「importすること」ではなく「結果を使って判断すること」なので、
そちらは no-restricted-syntax で押さえる。

導入時点で app/ はNext.jsの雛形のみ、lib/ と mcp/ は空。違反0件のため
drainなしで最初からerrorで入れた。

Closes #43

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@reitojike, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 969208c2-4317-42cd-a8cb-356888296fdc

📥 Commits

Reviewing files that changed from the base of the PR and between 80f6eac and 249a658.

📒 Files selected for processing (3)
  • docs/lint-policy.md
  • docs/roadmap.md
  • eslint.config.mjs
📝 Walkthrough

Walkthrough

common/lib/app/mcp/間の依存境界をESLintで検査する設定を追加しました。app/mcp/の判断ロジック用配列メソッドを禁止し、方針とロードマップを更新しました。

Changes

層境界Lint

Layer / File(s) Summary
層境界ルールの定義と実装
eslint.config.mjs, docs/lint-policy.md
禁止import、型importの例外、相対パスの扱い、動的import、Supabase・Next.js・React依存の制約を定義しました。app/mcp/ではfilterfindsortreduceなどを禁止しました。
導入状況の文書更新
docs/roadmap.md
フェーズ3の検出方法を手動レビューからESLintへ変更しました。判断ロジック漏れを「ESLintで検出済み」に更新しました。

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #43の要件である層境界検査、mcp/の対象化、代替配置メッセージ、関連文書更新、error導入を満たしています。
Out of Scope Changes check ✅ Passed 変更はESLint設定、関連ポリシー文書、ロードマップ更新に限定され、Issue #43の目的と整合しています
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルは、common/、lib/、app/、mcp/間の層境界をESLintで強制するという変更の主目的を明確に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/43-layer-boundary-lint

Comment @coderabbitai help to get the list of available commands.

Comment thread eslint.config.mjs
Comment on lines +47 to +55
const WEB_FRAMEWORK_PACKAGES = [
"next",
"next/*",
"next/**",
"react",
"react/*",
"react-dom",
"react-dom/*",
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WEB_FRAMEWORK_PACKAGESnext / next/* / next/** / react / react/* / react-dom / react-dom/* のみで、これは「完全一致 or / の後のワイルドカード」しか捕まえません。docs/lint-policy.md の表とこのファイルのコメント(L27, L44-45)はどちらも「next* react* を禁止」と書いていますが、実際のパターンにはハイフン直後に続く形(next-auth next-themes next-seo react-hook-form react-icons react-query 等)にマッチするワイルドカードがありません。

例えば common/ から import { useForm } from "react-hook-form" を書いても、このルールでは検出できません(react/*react の直後が / である場合にしかマッチしないため)。

現状の依存関係にはこれらのパッケージは無いので今すぐ壊れるわけではありませんが、Phase1でフォーム系ライブラリ等が追加された瞬間に静かに抜け道になります。next* react* の意図どおりにするなら "next-*" "react-*" のようなパターンを追加するか、ドキュメントの表現を実装に合わせて「next / next/** 配下のみ」に修正しておくべきだと思います。

Comment thread eslint.config.mjs Outdated
// 内部モジュールは `@/` エイリアスで書く規約だが、相対パス(`../../lib/x`)で書けば
// すり抜けられてしまうので、両方の表記を並べる。npmパッケージの深いパス
// (`somepkg/lib/...`)を巻き込まないよう `**/lib/**` のような広いパターンは使わない。
const RELATIVE_DEPTHS = [1, 2, 3, 4, 5];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RELATIVE_DEPTHS[1, 2, 3, 4, 5] で打ち切られているので、@/ エイリアスを使わずに6階層以上深い相対パス(例: ../../../../../../lib/x)で層を越えるimportを書くと、このルールをすり抜けます。

Next.js App Routerはルートグループ・動的セグメント・ネストしたlayoutで app/ 配下が5階層を超えるのは珍しくありません(例: app/(dashboard)/events/[eventId]/participants/[participantId]/edit/page.tsx はこの時点で6階層)。そこから相対パスで mcp/@supabase/* を叩けば検出されないことになります。

PR本文では「相対は5階層まで」と実装上の割り切りとして触れられていますが、docs/lint-policy.mdの「残っているギャップ」にはlib/.reduce()の件だけが明記されていて、この上限のことは書かれていません。深さの上限を持つ設計だと今後気づかれにくいので、docs/lint-policy.mdにも既知のギャップとして明記するか、RELATIVE_DEPTHSの列挙をやめて"../**/lib" / "../**/lib/**"のような深さに依存しないパターンに置き換えることを検討したほうが良さそうです(相対パスは必ず../または./から始まるので、npmパッケージの深いパスと誤検知することもないはずです)。

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

レビュー総評

eslint.config.mjs に層の境界(common/ lib/ app/ mcp/)を no-restricted-imports / no-restricted-syntax で機械化するPR。コード本体は触っておらず、docs/lint-policy.md / docs/roadmap.md の更新とESLint設定のみ。

CLAUDE.md / docs方針との整合性

  • as キャスト、anyeslint-disable / @ts-ignore による黙殺: なし
  • common/ に置くべき判断ロジックの複製: 該当なし(このPRはcommon/自体には触れていない)
  • 型の手書き: 該当なし(型を新規追加していない)
  • PO確認ゲートの判定(docs/decision-policy.md): docs/prd.md / docs/data-model.md / docs/permissions.md に触れておらず、docs/lint-policy.md / docs/roadmap.md の更新もIssue #43の完了条件に沿った追記。妥当な判定だと思います
  • app/ → lib/ のimportを禁止せず、判断ロジックの実体(配列操作)を no-restricted-syntax で消費側から塞ぐという設計は、PR本文の説明どおり筋が通っています。importの向きだけでは「lib/から受け取った結果をその場でfilter/reduceする」形の層越えを検出できないという指摘は正しく、対処もCLAUDE.mdの「ルールをpure関数に切り出す」の対象一覧(フィルタ・並び順・検証・集計・権限判定・日付計算)と一致しています

気になった点(インラインコメント参照)

  1. WEB_FRAMEWORK_PACKAGES の禁止パターンが「next* react*」という謳い文句ほど広くない。 next / next/* / next/** / react / react/*/ の後のワイルドカードしか捕まえず、next-auth next-themes react-hook-form react-icons のようなハイフン系パッケージ名を common/ や mcp/ からimportしてもすり抜けます。docs/lint-policy.md の表も同じ「next* react*」表記で、実装より広く読めます。
  2. RELATIVE_DEPTHS = [1,2,3,4,5] が相対パスでの層越えの検出上限になっている。 Next.js App Routerのルート木は5階層を容易に超えるため、@/ エイリアスを使わず深い相対パスで書かれた層越えimportは検出されません。PR本文には実装上の割り切りとして触れられていますが、docs/lint-policy.md「残っているギャップ」には(lib/.reduce()の件はあるのに)この上限は明記されていません。

どちらも現状のリポジトリ構成(ファイル数が少ない)では今すぐ壊れる話ではなく、将来コードが増えたときに気づかれにくい形で抜け道になるという指摘です。マージのブロッカーというよりは、docs/lint-policy.mdの「残っているギャップ」への追記、またはパターンの拡張を検討してほしいという提案です。

その他

  • 10パターンの手動検証(壊してyarn lintが赤くなることを確認)はdocs/testing.mdの「新しいテストは、対象を壊したときに赤くなるかを確認する」の精神に沿っています。ESLint設定自体の自動リグレッションテストは見送り理由(projectServiceとの相性、他ルールとの非対称性)が明記されており、判断として妥当だと思います
  • lib/側だけ@typescript-eslint/no-restricted-importsallowTypeImportsを使い分けている点は、「型を二重定義しない」方針(common/の型をlib/で再利用する経路)と整合しています

1. next-* / react-* 系のパッケージが抜けていた。`next/**` `react/**` は
   スラッシュ以降しかマッチしないため、`next-auth` `react-hook-form` を
   common/ や mcp/ からimportしてもすり抜けていた。

2. 相対パスの検出に階層数の上限があった。`../` の個数を1〜5で列挙していたため、
   6階層以上深いファイルからの層越えを検出できなかった。App Routerのルート木は
   5階層を簡単に超える。`../**/lib` の形にして深さ非依存にした。

合わせて docs/lint-policy.md の表記を実装に合わせ、「残っているギャップ」に
層と同名サブディレクトリの誤検知と、no-restricted-syntax がメソッド名しか
見ていない点を追記した。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@reitojike

Copy link
Copy Markdown
Owner Author

レビュー指摘の分類 (1巡目: Claude)

CodeRabbitは1巡目がレート制限で走らなかった(Review limit reached)。CodexはOPENAI_API_KEY未設定で意図的にスキップ(docs/roadmap.md「保留: 外部アカウント待ち」)。

# 指摘 分類 対応
1 next* react* を謳っているが next-auth react-hook-form 等のハイフン系がすり抜ける 本物の修正 next-* next-*/** react-* react-*/** を追加。ドキュメントの表記も実装に合わせた
2 RELATIVE_DEPTHS = [1..5] が相対パスでの検出上限になっている 本物の修正 提案どおり階層数の列挙をやめ、../**/lib ../**/lib/** の深さ非依存パターンに置き換えた

どちらも「今は壊れないが、コードが増えたときに静かに抜ける」種類の指摘で、まさにこのPRが
潰そうとしている性質の穴だった。層を機械化するPR自体に、機械化しきれていない穴が
2つ残っていた
という構図なので、指摘としては的確。

修正後に再検証したこと

追加したパターンが効いているか、壊して確認した。

  • common/a/b/c/d/e/f/x.ts から ../../../../../../lib/y (6階層) → error (修正前は通っていた)
  • common/ から react-hook-formerror (修正前は通っていた)
  • common/ から next-autherror (修正前は通っていた)
  • common/ から next/headers → error (回帰なし)
  • 1巡目に確認した11パターン全部を作り直して再実行 → 全部error (回帰なし)
  • app/ から @/lib/... + @/common/... + .map() の正当な組み合わせ → 通る (誤検知なし)
  • lib/ から import type { InviteContext } from "@/common/permissions"通る (誤検知なし)

削除後 yarn lint / yarn typecheck / yarn test は緑。

「静的解析で拾えたはずか」の自問 (docs/lint-policy.md)

**該当しない。**どちらも「ESLintのパターンの網羅性」そのものについての指摘で、
lintルールとして表現できる種類ではない(パターンが正しいかを検証するlintは無い)。
代わりに、指摘の内容をdocs/lint-policy.md「設定を書くときの落とし穴」と
「残っているギャップ」に文章として残した。次に同じ設定を触る人が同じ穴を掘らないため。

合わせて、レビューで指摘されなかったが同種の穴として気づいた2点もギャップに追記した。

  • 層と同名のサブディレクトリへの相対import (lib/a/b.ts../common/x) を誤検知すること
  • no-restricted-syntax がメソッド名しか見ておらず、配列でないオブジェクトの .find() も止まること

Comment thread eslint.config.mjs Outdated
"no-restricted-syntax": [
"error",
...JUDGEMENT_ARRAY_METHODS.map((method) => ({
selector: `CallExpression[callee.type="MemberExpression"][callee.property.name="${method}"]`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick(今回のスコープ外で構いません): このセレクタは callee.property.name を見ているため、ブラケット記法 arr["filter"](fn) / arr["re" + "duce"](fn) は素通りします。docs/lint-policy.md「残っているギャップ」には「メソッド名しか見ていない」ことによる誤検知(配列でないオブジェクトの .find())は書かれていますが、このすり抜け(ブラケット記法での回避)は書かれていません。他の抜け道(next/headers、相対パスの階層数)をここまで丁寧に塞いだPRなので、ギャップとして一行足しておくと将来「なぜ通った」で悩まずに済むかもしれません。ブロッキングではありません。

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

レビュー結果

CLAUDE.md / docs/lint-policy.md / docs/decision-policy.md の観点で確認しました。ブロッキングな指摘はありません。

良かった点

  • common/ / lib/ / app/ / mcp/ の依存方向(CLAUDE.md「ディレクトリ構成」)を no-restricted-imports で、判断ロジックの持ち出し(.filter() .reduce() 等)を no-restricted-syntax で分けて機械化しており、「importの向きだけでは app/ が lib/ の結果をその場で絞り込む」形の層越えを検出できないという弱点を正しく認識した上での設計になっている
  • paths ではなく patterns を使う理由(next/headers のすり抜け)、相対パスを ../**/dir で深さ非依存にした理由、allowTypeImports を lib/ から common/ の型importにだけ適用した理由など、実装上の落とし穴がすべてコメントと docs/lint-policy.md に対で残っており、後から読んでも意図が追える
  • docs/lint-policy.md「残っているギャップ」に lib/ へ構文ルールを掛けない理由(PostgRESTの .filter() との誤検知)、層と同名サブディレクトリの誤検知、no-restricted-syntax がメソッド名しか見ていない誤検知を明記しており、「機械で全部は塞げない」ことを隠していない
  • docs/roadmap.md の該当箇所(フェーズ3の注意点、フェーズ2バックログ表)も実装に合わせて更新されており、ドキュメントとコードの矛盾がない
  • 実際に10パターンを一時ファイルで壊してlintが赤くなることを確認し、誤検知が無いことも確認した上で戻している(PR本文に記録あり)。さらに1回のレビューサイクルで next-* / react-* の抜けと相対パスの階層数上限という実際のバグ2件を潰しており、eslint.config.mjs と common/permissions.ts / app/* の現状(違反0件)とも整合している
  • PR本文の docs/decision-policy.md ゲート判定(PO確認不要)も妥当。この変更はコードの書き直しだけで戻せる範囲(成果物の意味・権限・既定値・永続データに触れていない)に収まっている

指摘した観点との照合

  • as / any / eslint-disable / @ts-ignore: 変更ファイルは eslint.config.mjs と docs/*.md のみで、該当なし
  • common/ に置くべき判断ロジックの app/・mcp/ への複製: 該当なし(このPR自体がそれを機械的に禁止する仕組みを追加するもの)
  • 権限判定・削除時の分岐・公開設定の既定値の変更とテスト: 該当なし(アプリケーションロジックの変更はゼロ)
  • 型の手書き: 該当なし

軽微な指摘(inline済み、ブロッキングではない)

  • eslint.config.mjs の no-restricted-syntax セレクタはメソッド名(callee.property.name)だけを見ているため、arr"filter" のようなブラケット記法での呼び出しはすり抜けます。docs/lint-policy.md「残っているギャップ」に載っている誤検知(false positive)とは逆方向の、未記載のすり抜け(false negative)なので、一行足しておくと良さそうです。詳細はインラインコメントに書きました。

総評

lint設定のみの変更で、アプリケーションの挙動には触れていません。層の境界という「機械が最も止めやすいのに今まで止めていなかった」ルールを、抜け道の検証込みで丁寧に導入しており、CLAUDE.md の「これは機械が止められるか?」という原則にまっすぐ沿ったPRです。承認できる状態だと思います。

Claudeレビューのnitpick対応。誤検知(配列でないオブジェクトの .find())は
書いていたが、逆方向のすり抜け(rows["filter"](fn))が抜けていた。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@eslint.config.mjs`:
- Around line 111-126: Extend the ESLint configuration’s import restrictions to
cover dynamic import() expressions, not only the existing no-restricted-imports
rules. Add a no-restricted-syntax pattern or suitable dedicated rule targeting
ImportExpression that enforces the same layer and Supabase constraints for both
`@/` aliases and relative paths, reusing the existing layer(),
SUPABASE_CLIENT_PACKAGES, WEB_FRAMEWORK_PACKAGES, and MESSAGES.commonPure
symbols.
- Around line 109-110: eslint.config.mjs の層境界設定で、共通ヘルパーに .ts・.tsx・.mts
を許可拡張子として集約し、各拡張子の fixture を追加して同じ境界ルールを検証してください。no-restricted-imports が動的
import() を検査しないため、動的 import を許可する設定では別の構文ルールにも同一の層境界制約を適用してください。
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f5081863-8721-44fc-b8a6-05fcf8e1163a

📥 Commits

Reviewing files that changed from the base of the PR and between 5bcecd4 and 2e33490.

📒 Files selected for processing (3)
  • docs/lint-policy.md
  • docs/roadmap.md
  • eslint.config.mjs

Comment thread eslint.config.mjs Outdated
Comment thread eslint.config.mjs
Comment thread eslint.config.mjs
"reduceRight",
];

const layerBoundaries = [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no-restricted-imports はコアの ImportDeclaration / re-export 系構文しか見ておらず、動的な import() 式(ImportExpression)は対象外のはずです。そのため common/ から await import("react")app/ から await import("@/mcp/...") のような書き方をすると、ここで禁止しているはずの層越え・フレームワーク依存がそのまま素通りします。

rows["filter"](fn) のようなブラケット記法のすり抜け(docs/lint-policy.md「残っているギャップ」に追記済み)は意図的な回避行為ですが、動的importはNext.jsのコード分割で自然に出てくる書き方なので、うっかり踏み抜く可能性がブラケット記法より高いと思います。

ImportExpression を対象にした no-restricted-syntax の追加を検討するか、最低限このPRが明記している他の既知の穴と同様に docs/lint-policy.md「残っているギャップ」へ一行追記しておくと、次にここを触る人が気づけると思います。マージのブロッカーではありません。

Comment thread eslint.config.mjs Outdated

const layerBoundaries = [
{
files: ["common/**/*.ts", "common/**/*.tsx"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

軽微ですが、層境界の files パターンが *.ts / *.tsx のみで *.mts を含んでいません(lib/ app/ mcp/ の各ブロックも同様)。一方、型情報を要する4ルールのブロック(216行目付近)は "**/*.mts" も対象にしています。現状 common/ lib/ app/ mcp/ 配下に .mts ファイルは無く今すぐ壊れる話ではありませんが、非対称なので気になりました。

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

レビュー総評

eslint.config.mjscommon/ / lib/ / app/ / mcp/ の層境界を no-restricted-imports / no-restricted-syntax で機械化するPR。アプリケーションロジックには触れておらず、docs/lint-policy.md / docs/roadmap.md の更新とESLint設定のみです。

観点別の確認結果

  • as / any / eslint-disable / @ts-ignore による黙殺: なし
  • common/ に置くべき判断ロジックの複製: 該当なし(このPR自体がそれを機械的に禁止する仕組みを追加するもの)
  • 権限判定・削除時の分岐・公開設定の既定値の変更とテスト: 該当なし(アプリケーションロジックの変更はゼロ)
  • 型の手書き: 該当なし(型を新規追加していない)
  • PO確認ゲート判定 (docs/decision-policy.md): docs/prd.md / docs/data-model.md / docs/permissions.md に触れておらず、判断が「どう実装するか」に閉じている。妥当な判定だと思います

設計そのもの(app/ → lib/ のimportは許可しつつ、判断ロジックの実体である配列操作を消費側で no-restricted-syntax により禁止する二段構え)は、CLAUDE.md「ルールをpure関数に切り出す」の対象一覧(フィルタ・並び順・検証・集計・権限判定・日付計算)とそのまま対応しており、筋が通っています。パッケージ名を paths ではなく patterns にした理由、相対パスを ../**/dir で深さ非依存にした理由、allowTypeImportslib/common/ の型importにだけ使った理由など、実装上の落とし穴がコードコメントと docs/lint-policy.md に対で残っており、後から読んでも意図が追えます。

10パターンの手動break-and-fix検証(壊してlintが赤くなることを確認してから戻す)がPR本文に記録されており、CIも全緑です。過去2巡のレビューで指摘された next-* / react-* の抜けと相対パスの階層数上限は直近のコミットで修正・再検証済みで、ブラケット記法のすり抜けも「残っているギャップ」に追記済みです。

気になった点(インラインコメント参照)

  • 動的 import() 式が層境界の検査対象外になっているはずです。no-restricted-imports は静的な import 宣言しか見ないため、common/ から await import("react") のような書き方で今回の制約をすり抜けられます。Next.jsではコード分割で動的importが自然に出てくるため、ブラケット記法のような意図的な回避とは違い、うっかり踏み抜く経路になり得ます。ブロッカーではありませんが、他の既知の穴と同様に docs/lint-policy.md「残っているギャップ」への追記、または ImportExpression を対象にした構文ルールの追加を検討してほしいです
  • 軽微: 層境界の files パターンが *.ts / *.tsx のみで、型情報ルールのブロックが対象にしている *.mts を含んでいません。現状 .mts ファイルは存在しないため実害はありません

総評

lint設定のみの変更で、アプリケーションの挙動には触れていません。CLAUDE.mdの「これは機械が止められるか?」という原則に沿って層境界を機械化しており、レビューサイクルの中で実際の抜け穴(next-/react-, 相対パスの階層数上限)を2件潰した上での状態です。動的importの穴以外にブロッキングな指摘はなく、承認できる水準だと思います。

CodeRabbitとClaudeの双方から指摘された2件。

1. no-restricted-imports は静的なimport宣言しか見ないため、
   `await import("react")` で全部すり抜けていた。同じ制約を
   no-restricted-syntax の ImportExpression セレクタにも掛けた。
   グロブと正規表現を対で管理する必要があるので、その旨を明記した。

2. files が *.ts / *.tsx だけで .mts .cts 素のJSが対象外だった。
   拡張子リストを1か所に置いて組み立てるようにした。

検証中に、.mts ファイルを置くと yarn lint がクラッシュする問題が
main から存在することが判明した。層の境界とは別件なので issue #61 に
切り出し、docs/lint-policy.md の「残っているギャップ」に記載した。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@reitojike

Copy link
Copy Markdown
Owner Author

レビュー指摘の分類 (2巡目: Claude / CodeRabbit)

CodeRabbitは3回目のpushでようやくレート制限が解けて走った。両者の指摘は独立に同じ2点へ収束した。

# 指摘元 指摘 分類 対応
1 Claude / CodeRabbit no-restricted-imports は静的import宣言しか見ないので、動的 import() で層の境界を全部すり抜けられる 本物の修正 no-restricted-syntaxImportExpression > Literal[value=/.../] で同じ制約を掛けた
2 Claude / CodeRabbit files*.ts / *.tsx のみで .mts .cts や素のJSが対象外 本物の修正 拡張子リストを1か所に置き、ts tsx mts cts js jsx mjs cjs を組み立てるようにした
3 Claude (1巡目のnitpick) ブラケット記法 rows["filter"](fn) のすり抜けがギャップ節に無い 妥当なnitpick 前コミットで追記済み。今回さらに「動的importのモジュール名がリテラルでない場合」も追記

指摘1は特に妥当だった。Next.jsはコード分割で動的importが自然に出てくるので、
ブラケット記法のような「意図的な回避」とは性質が違い、うっかり踏む経路になる。

なお動的import側はグロブではなく正規表現で書くことになるため、**グロブ側と正規表現側で
同じ範囲を指すよう対で管理しないと片方だけすり抜ける。**この落とし穴自体を
docs/lint-policy.md「設定を書くときの落とし穴」に書いた。

壊して確認したこと (追加分)

  • common/ から await import("react") / "next/headers" / "next-auth" /
    "@supabase/supabase-js" / "@/lib/x" / "../lib/x"6件すべてerror(修正前は全部通っていた)
  • 同じファイルの await import("zod")通る(誤検知なし)
  • app/deep/x.cts から await import("@/mcp/tool") / "@supabase/ssr".filter()error
    (.cts が対象になったことの確認)
  • mcp/x.js から await import("next/headers") / "@/app/page"error(素のJSでも効く)
  • 既存の静的import 15パターンを作り直して再実行 → 全部error(回帰なし)
  • lib/import type と、app/@/lib + @/common + .map()通る(誤検知なし)

削除後 yarn lint / yarn typecheck / yarn test は緑。

スコープ外として分離したもの

検証中に、.mts ファイルを1つ置くと yarn lint がlintエラーではなくクラッシュすることが
判明した。型情報を要するルールのブロックが **/*.mts を対象にしている一方、
eslint-config-next のパーサーが .mtsparserOptions.project を転送しないため。

maineslint.config.mjs でも同じく再現するのでこのPR以前から存在する別件である
(git show origin/main:eslint.config.mjs を使って確認した)。issue #43 のスコープ外なので
#61 に切り出し、docs/lint-policy.md「残っているギャップ」に暫定記述を置いた。

「静的解析で拾えたはずか」の自問

**該当しない。**1〜3はいずれも「ESLint設定自身の網羅性」に関する指摘で、
lintルールとして表現できる種類ではない。代わりに落とし穴とギャップを
docs/lint-policy.md に文章として残した。

指摘が尽きたと判断したので、Ready化してCopilotの最終レビューを受ける。

Comment thread eslint.config.mjs
Comment on lines +92 to +105
const JUDGEMENT_ARRAY_METHODS = [
"filter",
"find",
"findIndex",
"findLast",
"findLastIndex",
"flatMap",
"every",
"some",
"sort",
"toSorted",
"reduce",
"reduceRight",
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JUDGEMENT_ARRAY_METHODSincludes(および indexOf)が入っていません。

このPRの狙いは「app/mcp/lib/ の結果を使ってその場で判断してしまう」ことを機械的に止めることですが、ALLOWED_IDS.includes(userId) のようなメンバーシップ判定による権限判定は、意味的には some(id => id === userId) と同じ判断ロジックです。後者はこのリストで捕まりますが、前者はすり抜けます。.includes() は配列の真偽値判定として .some() よりむしろ書かれやすい書き方なので、抜け穴として踏まれやすいと思います。

docs/lint-policy.mdの「残っているギャップ」節には、ブラケット記法・動的メソッド名・.mtsのクラッシュ・同名サブディレクトリの誤検知は明記されていますが、この「対象メソッド一覧そのものの抜け」には触れられていません。意図的に対象外にしたのであれば理由をコメントかドキュメントに残し、見落としであればリストに追加を検討してください。

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

レビュー結果

このPRは eslint.config.mjscommon/ / lib/ / app/ / mcp/ の依存方向を強制する
no-restricted-imports / no-restricted-syntax ルールを追加し、docs/lint-policy.md
docs/roadmap.md を更新するものです。app/ common/ lib/ mcp/ 配下の実コードは
変更されておらず、設定ファイルとドキュメントのみの差分でした。

観点別チェック

  • as キャスト / any / eslint-disable / @ts-ignore による黙殺: 該当なし。
    差分はESLint設定とドキュメントのみで、コードの変更を伴わない。
  • common/ に置くべき判断ロジックの app//mcp/ への複製・直書き: 該当なし
    (このPR自体がそれを機械的に検出する仕組みを追加するもので、判断ロジックそのものの
    追加・移動は無い)。
  • 権限判定・削除時の分岐・公開設定の既定値の否定側テスト: このPRはこれらの製品挙動を
    一切変更していないため対象外。lint設定自体の検証は、PR本文にある通り10パターンを
    実際に踏んで赤くなることを確認 → revert という形で行われており、
    自動リグレッションテストを見送った理由(projectService下での仮想ファイル解析が不安定)
    も明記されている。妥当な判断だと思う。
  • 型の手書き / 二重定義: 該当なし。

設計の妥当性

  • importの「向き」(no-restricted-imports)と「判断ロジックの実体」(no-restricted-syntax
    による配列メソッド禁止)を分けて掛ける設計は、app/lib/ のimportを禁止できない
    制約下で「結果を使って判断する」ことだけを狙い撃ちする現実的な折衷案になっている。
  • paths ではなく patterns を使う、相対パスの深さを数えない、動的import()にも同じ制約を
    掛ける、.mts/.cts/素のJSまで拡張子を網羅する、といった実装上の落とし穴は
    PR本文と docs/lint-policy.md「設定を書くときの落とし穴」に丁寧に記録されており、
    グロブ側と正規表現側を突き合わせても矛盾はなかった。
  • docs/lint-policy.md「残っているギャップ」に、lib/への構文ルール未適用・
    同名サブディレクトリの誤検知・ブラケット記法/動的メソッド名のすり抜け・
    .mtsでのlintクラッシュ(別issue化)が明記されており、既知の限界を隠していない。
  • docs/roadmap.md の更新はフェーズ3の「検討する」を前倒しで解消した内容で、
    他のドキュメントとの矛盾は見当たらない。

指摘

1点、インラインコメントで指摘しました。JUDGEMENT_ARRAY_METHODSincludes
(および indexOf) が含まれておらず、ALLOWED_IDS.includes(userId) のような
メンバーシップ判定による権限判定が配列メソッド制約をすり抜けます。意図的な対象外なのか
見落としなのか確認をお願いします。

それ以外に、方針・実装上のブロッカーは見つかりませんでした。

Claudeレビューの指摘。ALLOWED_IDS.includes(userId) は
ALLOWED_IDS.some((id) => id === userId) と意味的に同じ権限判定なので、
some だけ止めても書きやすいほうへ逃げられて抜け道になる。

文字列の .includes() まで巻き込む誤検知は承知の上。
「厳しすぎて例外が出る」ほうが「判断が静かに app/ に残る」より
戻しやすいという非対称性で判断し、その理由を
docs/lint-policy.md の「残っているギャップ」に明記した。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@reitojike

Copy link
Copy Markdown
Owner Author

レビュー指摘の分類 (3巡目: Claude)

# 指摘 分類 対応
1 JUDGEMENT_ARRAY_METHODSincludes / indexOf が無く、ALLOWED_IDS.includes(userId) 形の権限判定がすり抜ける 本物の修正 両方を対象に追加した

「意図的な対象外か見落としか」という問いへの回答: 見落とし。.some() を止めて
.includes() を通すのは、意味的に同じ権限判定を書き方だけで区別していることになり、
しかも .includes() のほうが書きやすいので、実質そちらへ逃がしていた。

承知の上で受け入れた誤検知

.includes() / .indexOf() はレシーバの型を見ないので、文字列に対する
pathname.includes("/events") のような書き方も止まる。
app/ で普通に書きたくなる形なので、
これは無視できない誤検知である。

それでも対象に含めたのは非対称性による。「厳しすぎて例外が出る」は例外リストに
1行足せば戻せるが、「判断が静かに app/ に残る」は誰も気づかない。

docs/lint-policy.md「残っているギャップ」に、この誤検知が起きること・
なぜそれでも入れているか・引っかかったときの手順(まず common/ への切り出しを考え、
本当に表示の都合なら例外リストに理由付きで置く)を明記した。

確認

  • app/ALLOWED_IDS.includes(userId) / ALLOWED_IDS.indexOf(userId) !== -1どちらもerror
  • 既存コードに対して yarn lint / yarn typecheck / yarn test は緑

指摘が尽きたので、次で Ready 化して Copilot の最終レビューを受ける。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@eslint.config.mjs`:
- Around line 105-106:
ESLint設定の回帰をfixtureテストで追加し、app/およびmcpのコードで.includes()と.indexOf()が検出されることを固定してください。.map()は許可されるfixtureも追加し、文字列に対する.includes()と.indexOf()は意図した誤検知として検出されるケースも含めてください。
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 088ab5f7-8fe2-4164-a676-c399076e4ced

📥 Commits

Reviewing files that changed from the base of the PR and between cd50bff and 80f6eac.

📒 Files selected for processing (2)
  • docs/lint-policy.md
  • eslint.config.mjs

Comment thread eslint.config.mjs
Comment on lines +105 to +106
"includes",
"indexOf",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n \
  --glob '*.{test,spec}.{js,mjs,cjs,ts,tsx,mts,cts}' \
  'RuleTester|no-restricted-syntax|includes|indexOf|\.map\(' .

Repository: reitojike/oshikatsu-manager

Length of output: 165


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- candidate files ---'
git ls-files | sed -n '1,160p'

printf '%s\n' '--- eslint config outline and relevant section ---'
ast-grep outline eslint.config.mjs 2>/dev/null || true
sed -n '70,135p' eslint.config.mjs

printf '%s\n' '--- package scripts and test configuration references ---'
if [ -f package.json ]; then
  node - <<'JS'
const p = JSON.parse(require('fs').readFileSync('package.json', 'utf8'));
console.log(JSON.stringify({scripts: p.scripts, devDependencies: p.devDependencies, dependencies: p.dependencies}, null, 2));
JS
fi
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' \
  'RuleTester|fixture|fixtures|eslint|no-restricted-syntax|includes|indexOf|\.map\(' . \
  | sed -n '1,260p'

Repository: reitojike/oshikatsu-manager

Length of output: 22915


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- eslint rule application ---'
sed -n '145,252p' eslint.config.mjs

printf '%s\n' '--- lint policy ---'
sed -n '100,190p' docs/lint-policy.md

printf '%s\n' '--- test setup ---'
cat -n vitest.config.ts
sed -n '1,180p' docs/testing.md
printf '%s\n' '--- tracked test files ---'
git ls-files 'test/**' | sort

printf '%s\n' '--- current state ---'
git status --short
if [ -d node_modules ]; then
  printf '%s\n' 'node_modules: present'
  node -e 'console.log(require.resolve("eslint"))' 2>&1 || true
else
  printf '%s\n' 'node_modules: absent'
fi

Repository: reitojike/oshikatsu-manager

Length of output: 10725


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

node - <<'JS'
const { Linter } = require("eslint");

const methods = [
  "filter", "find", "findIndex", "findLast", "findLastIndex",
  "flatMap", "every", "some", "includes", "indexOf", "sort",
  "toSorted", "reduce", "reduceRight",
];

const linter = new Linter({ configType: "flat" });
const config = [{
  languageOptions: {
    ecmaVersion: 2022,
    sourceType: "module",
  },
  rules: {
    "no-restricted-syntax": [
      "error",
      ...methods.map((method) => ({
        selector: `CallExpression[callee.type="MemberExpression"][callee.property.name="${method}"]`,
        message: `.${method}() restricted`,
      })),
    ],
  },
}];

const cases = {
  arrayIncludes: "const ok = ids.includes(userId);",
  arrayIndexOf: "const ok = ids.indexOf(userId);",
  stringIncludes: 'const ok = pathname.includes("/events");',
  stringIndexOf: 'const ok = pathname.indexOf("/events");',
  mapAllowed: "const ok = rows.map((row) => row.name);",
};

for (const [name, code] of Object.entries(cases)) {
  const messages = linter.verify(code, config, { filename: "app/example.js" });
  console.log(name, JSON.stringify(messages.map(({ ruleId, message, line }) => ({ ruleId, message, line }))));
}
JS

Repository: reitojike/oshikatsu-manager

Length of output: 553


ESLint設定の回帰をfixtureテストで固定してください。

app/mcp/.includes().indexOf() を検出し、.map() を許可するケースを追加してください。文字列の .includes().indexOf() も意図した誤検知として固定してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eslint.config.mjs` around lines 105 - 106,
ESLint設定の回帰をfixtureテストで追加し、app/およびmcpのコードで.includes()と.indexOf()が検出されることを固定してください。.map()は許可されるfixtureも追加し、文字列に対する.includes()と.indexOf()は意図した誤検知として検出されるケースも含めてください。

Comment thread eslint.config.mjs
"every",
"some",
"includes",
"indexOf",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: docs/testing.md「新しいテストは、対象を壊したときに赤くなるかを確認する」/ PR本文の「壊して確認した(完了条件)」表は、最初のコミット時点の10パターン(.filter() .reduce() 等の静的検出)のみを記録しています。

その後の2つのフォローアップコミット

  • 動的import()と.mts/.cts/JSを層の境界の対象に含める
  • includes / indexOf も判断ロジックの対象に含める(このブロック)

で検出範囲を広げていますが、PR本文の表はこれらに対応する行(例: app/await import("@/mcp/...")app/ALLOWED_IDS.includes(userId))を追加していないようです。コード自体は読んだ限り正しく見えますが、このPRが完了条件として掲げている「壊して赤くなることを確認」という自己申告の検証がこの2件については記録に残っていません。表を追記するか、確認済みであればその旨をPRコメントに残すことをお勧めします。

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

レビュー結果

CLAUDE.md / docs/lint-policy.md / docs/roadmap.md の方針に沿っているかを中心に確認しました。全体として高品質で、指摘は軽微な1件のみです。

確認した観点

  • as キャスト / any / eslint-disable / @ts-ignore / @ts-expect-error: diffに該当箇所なし(grep確認済み)
  • common/ に置くべき判断ロジックの複製・直書き: このPRはESLint設定とドキュメントのみの変更で、app/ lib/ mcp/ は雛形/空のままなのでN/A
  • 権限判定・削除時の分岐・公開設定の既定値に伴うテスト: ドメインロジックの変更なしのためN/A
  • 型の手書き禁止(生成型/Zodのz.infer): このPRは型定義を追加していないためN/A

設計・実装の妥当性

docs/lint-policy.md「層の境界」の表・図と eslint.config.mjslayerBoundaries を突き合わせて確認しました。

  • 4層(common/ lib/ app/ mcp/)それぞれの禁止import方向(no-restricted-imports / @typescript-eslint/no-restricted-imports)と、動的import()用の正規表現(no-restricted-syntaxImportExpression セレクタ)が、ドキュメントの表と1対1で対応していることを確認しました
  • judgementArraySyntax(filter find includes indexOf 等)が app/ mcp/ にのみ適用され、lib/(PostgRESTの .filter() と誤検知するため対象外)には掛けていない点もドキュメントの説明と一致しています
  • layerModuleRegex / WEB_FRAMEWORK_MODULE_REGEX / SUPABASE_MODULE_REGEX の正規表現は、対応するglobパターン(layer() / WEB_FRAMEWORK_PACKAGES / SUPABASE_CLIENT_PACKAGES)と同じ範囲を指すように書けています(手計算で複数ケースを追跡し、想定通りマッチ/非マッチになることを確認)
  • ドキュメント化された既知のギャップ(ブラケット記法・動的メソッド名のすり抜け、lib/内の.reduce()未検出、.mtsでのyarn lintクラッシュ)はコードの実際の限界と一致しており、誇張も過小記載もありませんでした
  • PR本文の「PO確認ゲートの判定」も docs/decision-policy.md の基準に照らして妥当な判断だと思います(製品の外部挙動を変えない設定変更で、確認不要側に該当)

指摘(1件、nitpick)

インラインコメントの通り、フォローアップ2コミット(動的import()検出の追加、includes/indexOfの追加)について、PR本文の「壊して確認した(完了条件)」表が最初のコミット時点の10パターンのまま更新されていません。コード自体は正しく見えますが、このPRが自ら掲げている検証基準(docs/testing.md「対象を壊したときに赤くなるかを確認する」)が新規追加分には適用された記録がない、という点のみ確認をお願いします。

@reitojike

Copy link
Copy Markdown
Owner Author

レビュー指摘の分類 (4巡目: Claude / CodeRabbit)

# 指摘元 指摘 分類 対応
1 Claude PR本文の「壊して確認した」表が初回コミット時点の10パターンのままで、追加分(動的import()includes/indexOf)の検証記録が本文に無い 妥当なnitpick PR本文を更新した。最終状態の設定に対して全パターンを実行し直し、静的import 15件 / 動的import 11件 / 誤検知なし3件に整理した
2 CodeRabbit ESLint設定のリグレッションをfixtureテストで固定してほしい 見送り(既出) 下記

指摘1は的確だった。個々の検証は各巡のコメントに書いていたが、PR本文が初回の状態で
止まっていた
ので、後から読む人は追加分が未検証に見える。このPR自身が
「壊して確認する」を掲げている以上、記録が分散しているのは良くない。
最終状態の設定に対して全部まとめて流し直した上で本文を差し替えた。

指摘2は初回のPR本文「見送ったもの」で既に理由を書いたもので、判断は変えない。

  • projectService が有効なため、ESLint#lintText に渡す仮想ファイルの扱いが不安定
    (実際に今回 .mts の実ファイルで型情報ルールがクラッシュしており、issue .mts ファイルを置くと yarn lint がクラッシュする(型情報ルールのパーサー設定漏れ) #61 に切り出した。
    仮想ファイルなら踏まない保証はない)
  • eslint.config.mjs の他のルール(no-explicit-anymax-lines-per-function など)も
    同様にテストされておらず、層の境界だけテストを持つと非対称になる
  • lint設定のテストを入れるなら設定全体に対する方針として決めるべきで、
    このIssueのスコープではない

「入れない」ではなく「別途方針として決める」なので、必要になったらIssueを立てる。
現時点では docs/lint-policy.md にルールと既知のギャップを文章で残すことで代替している。

指摘が出尽くしたので Ready 化し、Copilot の最終レビューを受ける。

@reitojike
reitojike marked this pull request as ready for review August 8, 2026 07:39
Copilot AI lite review requested due to automatic review settings August 8, 2026 07:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

ESLintのルールで common/ / lib/ / app/ / mcp/ の層境界を機械的に強制し、層越えimportや app/mcp/ での判断ロジック(配列操作)をCIで検出できるようにするPR。

Changes:

  • eslint.config.mjs に層境界の制約(no-restricted-imports / no-restricted-syntax)を追加
  • docs/lint-policy.md に層境界ルールの全体像・理由・落とし穴・残ギャップを追記
  • docs/roadmap.md のフェーズ3注意点を「ESLintで機械化済み」に更新

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
eslint.config.mjs 層境界のimport制約と、app/mcp/での判断ロジック(配列操作)禁止をESLintで強制
docs/roadmap.md フェーズ3の注意点を、層境界のESLint機械化に合わせて更新
docs/lint-policy.md 層境界ポリシーの仕様・理由・設定注意点・残ギャップを文書化

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/roadmap.md Outdated
Comment on lines +186 to +187
- `app/` に判断ロジックを書かない。`common/` を経由しているか、コンポーネントごとに確認する
- これを人力の注意力に頼らないため、**`app/` から `lib/` のクエリ結果を直接フィルタ・集計する
コードが書かれていないかを、レビュー時のチェック項目として明文化する**
(ESLintの `no-restricted-imports` 等で機械化できないか、このフェーズで検討する)
- これは人力の注意力に頼らず、**ESLintで機械化済み**(issue #43。フェーズ0で先行導入した)。
Copilotレビューの指摘。「コンポーネントごとに確認する」(手動)と
「人力に頼らずESLintで機械化済み」が別々の箇条書きに残っていて、
どちらが方針か読み取れなかった。1つにまとめた。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@reitojike

Copy link
Copy Markdown
Owner Author

レビュー指摘の分類 (5巡目: GitHub Copilot / Ready後の最終レビュー)

# 指摘 分類 対応
1 docs/roadmap.md フェーズ3「注意点」で、1つ目の箇条書きが「コンポーネントごとに確認する」(手動)、2つ目が「人力に頼らずESLintで機械化済み」となっており方針が矛盾して読める 本物の修正 2つの箇条書きを1つにまとめ、手動確認の文言を落とした

これは的確な指摘だった。既存の行を消さずに機械化の説明を足したため、
「目視で確認する」と「目視に頼らない」がドキュメント上に並存していた。
このリポジトリは人間がdiffを読まない前提なので、方針が2通りに読めるドキュメントは
そのまま次の実装者(モデル)の判断を割る。docs/decision-policy.md
「一つの記述が2通りに読める」を確認対象に挙げているのと同じ性質の問題を、
自分で作り込んでいた。

Copilot再レビューは行わない

pr-review-flow skill の「Ready後の運用」に照らして判断した。再リクエストが必要なのは
(1) quota上限で失敗した場合 (2) Ready後にコードの実装を変更した場合 の2つだけ。

  • Copilotの最終レビューは正常に完了している(quota失敗ではない。3ファイルすべてレビュー済み、
    総評とインラインコメント1件が投稿されている)
  • Ready後の変更は docs/roadmap.md の文言のみで、eslint.config.mjs は1文字も変えていない。
    skillが明示的に対象外としている「ドキュメント・コメント・テスト名のみの修正」に当たる

CIとClaude/CodeRabbitは通常どおり走るので、機械的なバックストップは失われていない。

全5巡の総括

レビュアー 本物の修正 妥当なnitpick 誤検知
1 Claude 2 (next-*/react-* の抜け、相対パスの階層上限) 0 0
2 Claude 0 1 (ブラケット記法のギャップ未記載) 0
3 Claude + CodeRabbit 2 (動的import()、拡張子の網羅) 0 0
4 Claude 1 (includes/indexOf の抜け) 1 (本文の検証記録が古い) + 1見送り (fixtureテスト) 0
5 Copilot 1 (ドキュメントの方針矛盾) 0 0

**誤検知ゼロ、本物の修正6件。**うち5件が「層を機械化するPR自身に残っていた層の抜け穴」
という構図で、レビューを回した価値がそのまま出た。

CI全緑を確認のうえマージする。

Comment thread eslint.config.mjs
Comment on lines +47 to +56
const WEB_FRAMEWORK_PACKAGES = [
"next",
"next/**",
"next-*",
"next-*/**",
"react",
"react/**",
"react-*",
"react-*/**",
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: WEB_FRAMEWORK_PACKAGES / WEB_FRAMEWORK_MODULE_REGEX (L127) still only cover unscoped next/react specifiers. Scoped packages in the same ecosystem — e.g. @next/font, @next/bundle-analyzer, @next/env — don't start with next or react, so common/ or mcp/ could import ... from "@next/env" (or await import("@next/env")) without tripping either rule. Same category of gap as the next-*/react-* hyphenated-package fix already made in this PR (docs/lint-policy.md「設定を書くときの落とし穴」) — worth either extending the pattern to @next/**/@react-*/** or adding one line to「残っているギャップ」so it's a documented, not silent, gap. Not a blocker; this PR has already been through 5 review rounds and closed every other package-name hole found.

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

レビュー総評

common/ / lib/ / app/ / mcp/ の依存方向を no-restricted-imports / no-restricted-syntax で機械化するPR。アプリケーションコードには触れておらず、eslint.config.mjsdocs/lint-policy.md / docs/roadmap.md のみの差分。

このPRは既に5巡のレビュー(Claude×4, CodeRabbit, Copilot)を経ており、next-*/react-*系パッケージの抜け、相対パス階層数の上限、動的import()のすり抜け、拡張子網羅漏れ、includes/indexOfの抜け、roadmap.mdの記述矛盾など6件の実バグが既に修正・再検証済みです。今回はその状態に対して独立に一通り確認しました。

CLAUDE.md / docs方針との整合性

  • as キャスト / any / eslint-disable / @ts-ignore による黙殺: なし
  • common/ に置くべき判断ロジックの app//mcp/ への複製・直書き: 該当なし(このPR自体がそれを機械的に禁止する仕組みを追加するもので、既存の common/permissions.ts にも変更なし)
  • 権限判定・削除時の分岐・公開設定の既定値とテスト: 該当なし(製品ロジックの変更ゼロ)
  • 型の手書き・二重定義: 該当なし
  • docs/decision-policy.md のPO確認ゲート判定: 製品の外部挙動(権限可否・既定値・保存データ・画面表示)を一切変えない設定変更であり、「確認不要」側の判定は妥当

設計・実装

  • importの向き(no-restricted-imports)と判断ロジックの実体である配列操作(no-restricted-syntax)を分けて掛ける設計は、app/ → lib/ のimportを禁止できない制約下で「結果を使って判断すること」だけを狙い撃ちする現実的な折衷案になっている
  • layer() のglobパターンと layerModuleRegex() の正規表現(動的import用)を突き合わせて手計算で複数ケースを追跡したが、想定通り一致していた(../**/dir の深さ非依存化、@\/\.\.\/ の両対応、(\/|$) によるパッケージ名途中一致の防止など)
  • docs/lint-policy.md「残っているギャップ」に、lib/への構文ルール未適用(PostgRESTの.filter()との誤検知)、層と同名サブディレクトリの誤検知、ブラケット記法/動的メソッド名のすり抜け、.mtsでのlintクラッシュ(→issue #61に分離)が明記されており、機械化しきれていない部分を隠していない
  • docs/roadmap.md の更新はフェーズ3「検討する」の前倒し解消で、他ドキュメントとの矛盾は見当たらない

指摘(nit、インライン参照)

WEB_FRAMEWORK_PACKAGES / WEB_FRAMEWORK_MODULE_REGEX が、既に修正済みの next-*/react-* と同じ理由で、@next/* のようなスコープ付きパッケージを依然として素通りさせる余地があります。ブロッカーではなく、docs/lint-policy.md「残っているギャップ」への追記候補として置いておきます。

総評

CIは全緑(lint / typecheck / unit-test / db-test)。設定のみの変更で製品挙動への影響なし。ブロッキングな指摘はありません。承認できる水準だと思います。

Claudeレビューの指摘。@next/env や @react-three/fiber は next / react で
始まらないため、既に直したハイフン系(next-auth 等)と同じ理由で
common/ や mcp/ から素通りしていた。

グロブ側に @next/* @next/** @react-*/* @react-*/** を追加し、
動的import側の正規表現も同じ範囲を指すよう対で直した。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@reitojike

Copy link
Copy Markdown
Owner Author

レビュー指摘の分類 (6巡目: Claude)

# 指摘 分類 対応
1 @next/env @react-three/fiber のようなスコープ付きパッケージが next/react で始まらないため素通りする 本物の修正 グロブに @next/* @next/** @react-*/* @react-*/** を追加し、動的import側の正規表現も対で直した

指摘のとおり、既に直したハイフン系(next-auth)とまったく同じ種類の穴が残っていた。
パッケージ名の形(next / next/x / next-x / @next/x)が1つ増えるたびに穴が開く設定に
なっているのが根本原因なので、docs/lint-policy.md にその性質自体を書き足した
(足すときはグロブ側と正規表現側を必ず対で直す、と明記)。

確認

  • common/ から import { env } from "@next/env"error
  • common/ から import { Canvas } from "@react-three/fiber"error
  • common/ から await import("@next/env") / await import("@react-three/fiber")error
  • common/ から import { z } from "zod" / await import("@reduxjs/toolkit")通る(誤検知なし)
  • yarn lint / yarn typecheck / yarn test は緑

Copilotに手動で再レビューを依頼する

pr-review-flow skill の「Ready後の運用」の条件(2)「Ready後にコードの実装を変更した」に
該当するため、マージ直前の手動再リクエストを1回だけ行う。
前回(5巡目)の指摘はドキュメントのみの修正だったので再リクエストしなかったが、
今回は eslint.config.mjs を変更している。これで手動再リクエストの枠(1PRにつき1回)を使い切る。

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

レビュー総評

このPR自体は docs/lint-policy.md / docs/roadmap.md / eslint.config.mjs の3ファイルのみで、アプリケーションロジック・テスト・型定義には触れていません。そのため今回の重点観点のうち以下は該当なしです。

  • as キャスト、anyeslint-disable/@ts-ignore によるエラー黙殺 → 該当箇所なし
  • common/ に置くべき判断ロジックの app//mcp/ への複製 → アプリコードの変更なし
  • 権限判定・削除分岐・公開設定既定値の変更に伴うテスト → 該当する変更なし
  • 型の手書き / 生成型・Zodからの導出 → 型定義の変更なし

その上でESLint設定本体を精査しました。

良い点

  • no-restricted-imports(静的import)と no-restricted-syntax(動的import()・配列メソッド)を対で管理する設計は、PR本文が挙げている「パッケージ名の形が増えるたびに穴が開く」問題に対して誠実に対処できています。グロブ側 (layer(), WEB_FRAMEWORK_PACKAGES 等) と正規表現側 (layerModuleRegex(), WEB_FRAMEWORK_MODULE_REGEX 等) を手計算で照合しましたが、next-auth / react-hook-form / @next/env / @react-three/fiber のような対象と、nextjs-foo / @reactivex/rxjs のような非対象を正しく分離できており、相対パスの深さに依存しない ../**/lib 系パターンも境界(lib/a/b.ts../common のような部分文字列誤検知)を含めて意図通りに機能する設計になっています。
  • docs/roadmap.md フェーズ3の「手動確認」と「機械化検討」という古い記述が、同じPR内のコミット(ff5abf7)で「ESLintで機械化済み」に更新されており、docs/lint-policy.md「層の境界」の内容と矛盾なく揃っています。
  • docs/decision-policy.md の確認ゲート(製品の観測可能な挙動が変わるか)に照らしても、今回の変更はビルド時の静的解析のみで製品挙動に影響しないため、PO確認不要というPR本文の自己判定は妥当だと判断しました。
  • lint設定自体の自動リグレッションテストを見送った判断(projectServiceの不安定さ・既存ルールとの非対称性)も、理由が明記されており、PR本文の「壊して確認した」10パターンの手動検証表が docs/testing.md の「壊れたら赤くなることを確認してから戻す」の精神を代替できていると判断しました。

指摘

インラインコメントを1件投稿しました。mcp/ のNext.js/React依存禁止が、lib/経由の間接依存を捕捉できていない設計上のギャップです。詳細はインラインコメント参照。

🤖 Generated with Claude Code

Comment thread eslint.config.mjs
{
patterns: [
{ group: SUPABASE_CLIENT_PACKAGES, message: MESSAGES.noSupabaseOutsideLib },
{ group: WEB_FRAMEWORK_PACKAGES, message: MESSAGES.mcpNoFramework },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mcp/ のNext.js/React依存禁止に、lib/ 経由の間接依存が抜けています。

このルールが検出できるのは mcp/ 配下のファイルに直接書かれたimport文だけです。一方 lib/ 層(layerFiles("lib") のルールブロック、eslint.config.mjs内)にはフレームワーク系パッケージの制約が一切掛かっておらず、mcp/ から lib/ へのimportは(意図的に)許可されています。

このため、lib/ 内のあるファイルが Supabase の Server Client を組み立てる定番の書き方(next/headerscookies() を使うパターン)に依存していた場合、mcp/ がそのファイルを許可された経路で import するだけで、mcp/ プロセスがNext.js依存を静かに引きずり込みます。この経路はどちらの no-restricted-imports/no-restricted-syntax 設定でも検出できません。

CLAUDE.mdがこの禁止importの根拠として挙げている「stdioサーバーがNext.jsを丸ごと読み込むのを防ぐ」(MESSAGES.mcpNoFrameworkdocs/lint-policy.md の該当表)は、lib/ 経由の間接依存に対しては実現できていない状態です。

現時点では mcp/ が空(.gitkeepのみ)のため実害はありませんが、フェーズ5で mcp/lib/ の関数を呼び始めたときに、lintは緑のまま実行時エラーとして初めて顕在化します。docs/lint-policy.md「残っているギャップ」には他の既知の限界(lib/に構文ルールが無い、同名サブディレクトリの誤検知、.mtsのクラッシュ等)が丁寧に列挙されていますが、この間接依存のギャップは記載がありません。

「残っているギャップ」への追記、もしくは lib/ 側でNext専用APIを使うファイルを命名規則等で分離して mcp/ からの誤importに気づきやすくする設計を検討することを提案します。

Claudeレビューの指摘。no-restricted-imports は依存グラフを辿らないため、
lib/ のファイルが next/headers に依存していると、mcp/ がそれを許可された
経路でimportするだけでNext.jsが入る。lintは緑のままフェーズ5で
実行時に顕在化する。

lib/ 側にフレームワーク制約を掛けることはできない(Supabaseの
サーバークライアントが cookies() を使うため)ので、対処は
「Next.js専用APIに触れるファイルをパスで分離し、mcp/ からそのパスへの
importを禁止する」になる。lib/ が空の今は決められないので、
フェーズ3のチェックリストに作業として書いた。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@reitojike

Copy link
Copy Markdown
Owner Author

レビュー指摘の分類 (7巡目: Claude / Copilot再レビュー)

Copilot(手動再リクエスト、1回): Copilot reviewed 3 out of 3 changed files and generated no new comments. — 新規指摘なし。quota失敗ではなく正常完了。

Claude: 1件。

# 指摘 分類 対応
1 mcp/ のNext.js禁止が間接依存を捕捉できていない。lib/ のファイルが next/headers に依存していると、mcp/ がそれを許可された経路でimportするだけでNext.jsが入る 本物の修正(ただしlintでは直せない) ギャップとして明記し、対処をフェーズ3のチェックリストに作業として落とした

指摘のとおりで、しかもこのPRの仕組みでは原理的に塞げない。
no-restricted-imports が見るのはそのファイルのimport文だけで、依存グラフを辿らない。
lib/ 側にフレームワーク制約を掛けて塞ぐこともできない —
Supabaseのサーバークライアントは next/headerscookies() を使うのが定番で、
lib/ はまさにそれを置く場所だから。

なので設計で対処するしかない。Next.js専用APIに触れるファイルをパスで分離し
(例: lib/web/)、mcp/ からそのパスへのimportを禁止する。

ただし lib/ は今 .gitkeep だけで、置き場所を今決めるのは実体のない先回りになる。
そこで:

  • docs/lint-policy.md「残っているギャップ」に、なぜ塞げないかと対処方針を明記
  • docs/roadmap.md フェーズ3の lib/ のチェックリスト項目に、
    分離とlintルール追加を作業として書き込んだ(フェーズ5で困ってから気づくのではなく、
    lib/ を作る人が同じ画面で読む場所に置いた)

これで「lintが緑だから安全」という誤認は起きない。

収束したのでマージする

  • Copilot: 新規指摘なし(手動再リクエストの枠1回を使用済み)
  • 今回の変更はドキュメントのみで eslint.config.mjs は不変。
    skill の「Ready後の運用」でCopilot再リクエストの対象外
  • CI全緑を確認してマージする

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

レビュー結果

CLAUDE.md / docs(prd, data-model, permissions, lint-policy, testing, decision-policy)の方針に沿って確認しました。今回のPRは eslint.config.mjs への層の境界ルール追加と、対応する docs/lint-policy.md / docs/roadmap.md の更新のみで、app/ common/ lib/ mcp/ 自体にはコード変更がありません。

確認した観点と結果

  • as キャスト / any / eslint-disable@ts-ignore によるエラーの黙殺
    eslint.config.mjs 内に該当なし。exceptions リストは空で、例外の作法(設定ファイルに理由付きで書く)のテンプレートコメントのみが置かれている状態。問題なし。

  • common/ に置くべき判断ロジックの app/ / mcp/ への複製・直書き
    今回のPRにはアプリケーションコードの変更がない(app/ は雛形2ファイル、lib/ mcp/ は空)ため該当なし。既存の common/permissions.ts は新しいレイヤールールに違反しないことを確認しました(外部importなしの純粋な関数のみ)。

  • 静かに失敗する変更へのテスト同伴
    権限判定・削除分岐・公開設定の既定値に触れる変更はなし。今回はESLint設定というツール変更で、方針から外れると「テストが緑のまま静かに壊れる」のではなくCIのyarn lintが赤くなる性質のため、docs/testing.mdが優先する「静かに失敗する」対象には該当しないと判断しました。PR本文に記載されている、一時ファイルを使った手動の壊し-確認マトリクス(静的import 19件・動的import 13件・誤検知なしの確認4件)は目的に対して十分と考えます。ESLint設定自体の自動リグレッションテストを見送った判断(projectServiceが仮想ファイルで不安定という技術的制約、他のルールも同様に未テストという整合性)も理由が明記されており妥当です。

  • 型を手書きせず生成型 / Zodのz.inferから導出
    今回のPRはJS設定ファイルとドキュメントのみで、該当する型定義の追加はありません。

実装内容そのものについて

  • no-restricted-imports(静的import)と no-restricted-syntax(動的import() + 配列メソッドによる判断ロジック持ち出し)を対で管理する設計になっており、グロブ側と正規表現側を突き合わせて確認しましたが、パッケージ名の形(素のパッケージ名/サブパス/ハイフン系/スコープ付き)・相対パスの深さ非依存化のいずれも、静的側と動的側で捕捉範囲が一致していました。誤検知が起きない側(@reduxjs/toolkit 等)・起きるべき側の双方でズレは見つかりませんでした。
  • docs/decision-policy.mdのPO確認ゲート判定(「確認不要」)も、対象が実装の裁量内(ファイル配置・機械化の手段)に閉じており製品挙動を変えていないため妥当です。
  • コミット履歴を見ると、複数回のボットレビュー(ハイフン系パッケージの漏れ、相対パスの階層数上限、動的import、拡張子の網羅、includes/indexOf、スコープ付きパッケージ、間接依存のギャップ)がすでに反映されており、今回改めて確認した範囲では追加で指摘すべき欠陥は見つかりませんでした。

インラインコメントは指摘なしのため投稿していません。

@reitojike
reitojike merged commit 54ba0aa into main Aug 8, 2026
8 checks passed
@reitojike
reitojike deleted the feat/43-layer-boundary-lint branch August 8, 2026 12:12
reitojike added a commit that referenced this pull request Aug 8, 2026
PR #68 のClaude Reviewの指摘に対応。指摘は正しかった。

誤り: 「300行を超えた5本」は事実と異なり、素の差分では9本だった
(#31 307 / #59 365 / #60 380 / #30 441 / #29 617 / #18 679 / #41 698 /
#32 1063 / #16 3528)。同じ本文が書いていた「300行以下が65%(17/26)」は
超過9本を含意しており、記述が自己矛盾していた。

原因: 母集団の統計(中央値・65%)は素の差分で数え、外れ値の説明だけに
除外規則を適用していた。数え方を混在させたうえ、超過リストを上位5本で
打ち切って全件確認しなかった。

訂正: 26本すべてを git show --numstat で数え直し、除外の段階ごとに
表で示す。素の差分(中央値135行 / 65% / 超過9本)、パス名で機械的に
判定できる除外まで(中央値128行 / 85% / 超過4本)、除外規則を最後まで
適用(超過2本 = #59#60)。落ちる7本の内訳も明記した。

あわせて、3段階目の中央値を出さない理由を書いた。supabase/config.toml は
#29 では supabase init の出力(416行)、#56 では根拠コメント付きで手で
直した6行で、同じパスでも扱いが逆になる。パス名では決まらないことが、
この節が機械的ゲートになり得ない理由そのものなので、「lintではない」の
段落の根拠もこの実測に差し替えた。

Refs #44
reitojike added a commit that referenced this pull request Aug 8, 2026
* docs: Issueの粒度とPR差分サイズの目安をCLAUDE.mdに明文化

判断ポイントは1 Issueに3個まで(5個超で分割)、PR差分は300行を目安とする。
記事の実測値をそのまま採らず、main にマージ済みのPR 26本(中央値135行、
300行以下65%)で裏を取ってから採用した。行数の数え方から生成物・ロック
ファイル・権限マトリクスを写したテスト表を除外する根拠も、超過した
PR #16 / #32 の実態から示した。

機械的ゲートにしない旨と、3回ルール(モデルを上げる) / PO確認(判断を
下せる層に上げる) / 粒度超過(Issueを分ける)の対処の違いを表で整理。
docs/roadmap.md はポインタ1行に留め、根拠は1箇所にだけ置く。

Refs #44

* docs: 判断ポイント数の境界を一本化し、裏取り済みの数値と外部実測を書き分ける

PR #68 のClaude Reviewの指摘2件に対応。

指摘1: 「3個まで、5個を超えるなら分割」で4個の扱いが未定義だった。
閾値を「3個まで。4個目が出てきたら分ける」に一本化する。機械的ゲートに
しない方針である以上、「検討」と「必ず」の二段構えは実効性のない
false precisionになるため、緩衝域を作らず単一の線にした。

指摘2: 裏取り済みの300行と、外部実測のままの3個が同じ文脈に並んでいた。
「2つの数字は裏付けの強さが違う」として段落を分け、300行はこのリポジトリの
実測(PR 26本、中央値135行/300行以下65%)で検証済み、3個は外部実測のみを
根拠とする未検証のヒューリスティックであると明示した。過去Issueの判断数は
記録がなく後から数え直せないため、このリポジトリでの裏取りが今はできない
理由も併記。採用の根拠はコストの非対称性に置いた。

あわせて、外部実測に対応値のない4個/5個を推定して線を引いていないことと、
実績が溜まったら見直す旨を記載した。

Refs #44

* docs: PR実測の集計を数え直し、超過本数の誤りを訂正する

PR #68 のClaude Reviewの指摘に対応。指摘は正しかった。

誤り: 「300行を超えた5本」は事実と異なり、素の差分では9本だった
(#31 307 / #59 365 / #60 380 / #30 441 / #29 617 / #18 679 / #41 698 /
#32 1063 / #16 3528)。同じ本文が書いていた「300行以下が65%(17/26)」は
超過9本を含意しており、記述が自己矛盾していた。

原因: 母集団の統計(中央値・65%)は素の差分で数え、外れ値の説明だけに
除外規則を適用していた。数え方を混在させたうえ、超過リストを上位5本で
打ち切って全件確認しなかった。

訂正: 26本すべてを git show --numstat で数え直し、除外の段階ごとに
表で示す。素の差分(中央値135行 / 65% / 超過9本)、パス名で機械的に
判定できる除外まで(中央値128行 / 85% / 超過4本)、除外規則を最後まで
適用(超過2本 = #59#60)。落ちる7本の内訳も明記した。

あわせて、3段階目の中央値を出さない理由を書いた。supabase/config.toml は
#29 では supabase init の出力(416行)、#56 では根拠コメント付きで手で
直した6行で、同じパスでも扱いが逆になる。パス名では決まらないことが、
この節が機械的ゲートになり得ない理由そのものなので、「lintではない」の
段落の根拠もこの実測に差し替えた。

Refs #44

* docs: PR #29/#32の除外理由が2段階なのに1段階しか書いていなかった記述漏れを修正

Claude Reviewの指摘どおり、#29(617→22)はsupabase/config.toml(416行)
だけでなくsupabase/types.ts(179行)も、#32(1063→79)はテスト表(924行)
だけでなくyarn.lock(60行)も除外して初めて数字が再現できる。
片方しか書いていなかったため、追試すると数値が合わなかった。

Refs #44
reitojike added a commit that referenced this pull request Aug 9, 2026
* レビュー指摘の重複による修正ラリーを減らす

同一根拠の指摘がファイルごとに個別投稿され、修正→push→再指摘のラウンドが
かさむ問題(PR #32, #60, #63, #73)への対処。指摘を直す側には横展開確認の
規律を、レビューボット側には同一根拠の指摘を1件に集約する指示を追加する。
Codexレビュー本格導入前の準備。

Closes #79

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Copilotの指摘反映: PR #32/#60の例が読点で連結され読みにくい問題を修正

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

app/がlib/の結果を直接フィルタ・集計しないようno-restricted-importsで層を強制する

2 participants