| name | frourio-framework-review |
| description | frourio (Fastify + aspida + zod ベースのTypeScriptフルスタックフレームワーク) 採用プロジェクトの PR / 差分コードレビュー観点。違反検出規約・優先度 (Critical/Warning/Info)・ 検出パターンと修正案・自動生成ファイル編集検出・DTO 変換漏れ検出・ Prisma Model 変換違反検出・hooks 重複検出・useEffect データ取得検出・ センシティブ field 漏洩検出・useFrourioSWR 移行漏れ検出を網羅。 実装規約 skill (frourio-framework) の対となるレビュー観点 skill。 Triggers: "frourio レビュー", "frourio コードレビュー", "frourio PR レビュー", "frourio review", "controller レビュー", "DTO 漏れ", "toDto 検出", "hooks 重複", "useEffect 検出", "useFrourioSWR 移行漏れ", "$server.ts 編集", "$relay.ts 編集", "__generated__ 編集", "frourio-framework-review".
|
| allowed-tools | ["Read","Grep","Glob","Bash"] |
frourio Framework — Code Review
frourio (Fastify + aspida + zod) ベース TypeScript フルスタックフレームワーク採用プロジェクトの
PR / 差分レビュー観点を集約。
違反検出 → 指摘必須。1指摘1行 (位置・問題・修正)。
実装規約 (skill: frourio-framework) の鏡像。実装時規約 vs レビュー時検出観点で役割分離。
0. レビュー優先度
- 🔴 Critical — 型安全性・セキュリティ・自動生成破壊。即修正
- 🟡 Warning — 規約逸脱・保守性低下。修正推奨
- 🔵 Info — スタイル・命名。任意
1. ディレクトリ・ファイル配置 (🔴 Critical)
検出パターン:
api/foo/_id/ (型指定無し) → _id@string or _id@number に修正
api/foo/[id]/ (Next.js 風) → frourio 規約違反
api/foo/:id/ (Express 風) → frourio 規約違反
検出 grep:
find api -type d -name '_*' | grep -v '@string\|@number'
find api -type d \( -name '\[*\]' -o -name ':*' \)
2. 自動生成ファイル編集 (🔴 Critical)
以下が diff に含まれる → 即 reject:
$server.ts (プロジェクトルート)
- 各ルートの
$relay.ts
api/$api.ts
__generated__/ 配下全て (Prisma model + repository 生成物)
検出 grep:
git diff --name-only origin/main...HEAD | grep -E '(\$server\.ts|\$relay\.ts|\$api\.ts|__generated__/)'
修正方針: 該当ファイル変更を revert → schema / index.ts 等の 入力側 を修正 → frourio / prisma generate 再実行で再生成。
3. controller.ts (🔴 Critical)
違反例:
export default async (req) => ({ status: 200, body: {} });
get: () => ({ body: {} }),
get: ({ query }) => { const x = query.foo; ... },
検出 grep:
grep -rL 'defineController' api/**/controller.ts
4. API 応答 — DTO 変換 (🔴 Critical)
最重要: API レスポンスは必ず <Model>Model.toDto() 経由。
検出パターン (NG):
const user = await prisma.user.findUnique({ where: { id } });
return { status: 200, body: user };
const user = UserModel.fromPrismaValue({ self: prismaUser });
return { status: 200, body: user };
return { status: 200, body: { id: user.id, name: user.name } };
return { status: 200, body: { ...user.toDto(), password: undefined } };
OK パターン:
return { status: 200, body: user.toDto() };
return { status: 200, body: user.toPublicDto() };
return { status: 200, body: users.map(u => u.toDto()) };
検出 grep:
grep -rn 'prisma\.\w\+\.\(findUnique\|findFirst\|findMany\|create\|update\)' api/**/controller.ts
grep -rn 'body:\s*\(await\|prisma\)' api/**/controller.ts
5. Prisma → Model 変換 (🔴 Critical)
違反例:
const user = new UserModel({ id, name, ... });
const user = UserModel.fromPrismaValue({ self: prismaUser });
検出 grep:
grep -rn 'new \w\+Model(' src/ usecase/ api/
6. index.ts — 型定義 (🔴 Critical)
違反例:
export type Methods = DefineMethods<{
get: { resBody: { id: number; name: string } };
}>;
import type { UserModelDto } from '<shared-types>';
export type Methods = DefineMethods<{ get: { resBody: UserModelDto } }>;
検出 grep:
grep -rn 'resBody:\s*{' api/**/index.ts | grep -v 'ModelDto\|Dto\b'
7. validators.ts (🟡 Warning)
検出 grep:
for d in $(find api -type d -name '_*'); do
parent=$(dirname "$d")
[ -f "$parent/validators.ts" ] || echo "missing validators: $parent"
done
8. hooks.ts — auth (🔴 Critical)
違反例:
get: async (req) => {
if (!req.headers.authorization) return { status: 401, body: {...} };
...
}
req.user = await req.jwtVerify();
検出 grep:
grep -rn 'headers\.\(authorization\|cookie\)' api/**/controller.ts
grep -rn 'jwtVerify\|verifyToken' api/**/controller.ts
for f in $(find api/admin -name controller.ts); do
dir=$(dirname "$f")
found=""
while [ "$dir" != "api" ] && [ "$dir" != "." ] && [ "$dir" != "/" ]; do
if [ -f "$dir/hooks.ts" ]; then found=1; break; fi
dir=$(dirname "$dir")
done
[ -z "$found" ] && echo "AUTH BYPASS RISK: $f"
done
9. hooks 重複 — middleware 切り出し (🟡 Warning)
9.1 配置パターン早見表
認証範囲とディレクトリ構成の典型パターン:
| 構成 | hooks.ts 配置 | 理由 |
|---|
admin/ 配下全部に同一認証 + auth/login のみ token 発行 | 各サブ範囲ごと: admin/clients/hooks.ts, admin/report/hooks.ts, admin/sync/hooks.ts (login は親に置けないので) | admin/hooks.ts 親集約 → login も認証必須化 → token発行不能 |
admin/ 配下全部に同一認証 + login 系無し | admin/hooks.ts 1個のみ | 共通祖先1個で全子孫継承 |
| ルート別に異なる scope (admin vs user) | admin/hooks.ts (admin scope) + user/hooks.ts (user scope) | scope ごとに祖先分離 |
| 一部だけ追加 hook (rate limit 等) | 既存 auth hooks.ts より下の階層に新規 hooks.ts (auth は祖先継承、rate limit は当該階層追加) | hooks 重ねがけ |
違反例:
OK パターン:
import type { FastifyRequest, FastifyReply } from 'fastify';
export const authAdminMiddleware = async (req: FastifyRequest, reply: FastifyReply) => {
const payload = await req.server.jwt.verify(token);
if (!hasAdminScope(payload)) { reply.code(403).send(...); return reply; }
};
import { defineHooks } from './$relay';
import { authAdminMiddleware } from '$/middleware/authAdminMiddleware';
export default defineHooks(() => ({ onRequest: authAdminMiddleware }));
検出 grep:
find api -name 'hooks.ts' -exec wc -l {} \; | awk '$1 > 15'
grep -rh '^import' api/**/hooks.ts | sort | uniq -c | sort -rn | head
for f in $(find api -name hooks.ts); do
dir=$(dirname "$f")
imports=$(grep -oE 'from .+/middleware/\w+' "$f" | sort -u)
parent=$(dirname "$dir")
while [ "$parent" != "api" ] && [ "$parent" != "." ] && [ "$parent" != "/" ]; do
if [ -f "$parent/hooks.ts" ]; then
for imp in $imports; do
if grep -q "$imp" "$parent/hooks.ts" 2>/dev/null; then
echo "DUPLICATE INJECTION: $f also injects $imp already from $parent/hooks.ts"
fi
done
fi
parent=$(dirname "$parent")
done
done
grep -lE 'defineHooks\(\(\) => \(\{ onRequest: \w+ \}\)\)' api/**/hooks.ts
10. Prisma Schema — DTO アノテーション (🔴 Critical)
違反例:
// ❌ password が hidden 指定無し → toDto() で漏洩
model User {
password String
}
// ❌ controller で手動 omit (自前ロジック)
return { status: 200, body: { ...user.toDto(), password: undefined } };
検出 grep:
grep -nE '^\s+(password|apiKey|secret|token|refreshToken)\s+String' schema.prisma | \
grep -v '@dto(hidden'
11. Prisma バージョン整合 (🔴 Critical)
検出 bash:
node -e "
const p = require('./package.json');
const a = (p.dependencies||{})['@prisma/client'];
const b = (p.devDependencies||{})['prisma'] || (p.dependencies||{})['prisma'];
if (a !== b) console.log('VERSION MISMATCH:', { '@prisma/client': a, prisma: b });
"
12. 共有型エクスポート (🟡 Warning)
検出 grep:
grep -rn '__generated__' frontend/src/ web/src/
13. フロントエンド — データ取得 (🔴 Critical)
違反例:
useEffect(() => {
adminApiClient.admin.users.$get().then(setUsers);
}, []);
const [data, setData] = useState();
useEffect(() => {
if (!id) return;
adminApiClient.admin.tenants._tenantId(id).$get().then(setData);
}, [id]);
const { data } = useAspidaSWR(adminApiClient.admin.users);
OK パターン:
const { data } = useFrourioSWR(adminApiClient.admin.users);
const { data } = useFrourioSWR(
id ? adminApiClient.admin.tenants._tenantId(id) : null,
);
const { data } = useFrourioSWR(adminApiClient.hq.projects, {
query: { status: 'IN_PROGRESS' },
});
検出 grep:
grep -rnB1 -A5 'useEffect' frontend/src/ | grep -E '(\.\$get\(|\.\$post\(|fetch\()'
grep -rn 'useAspidaSWR' frontend/src/
14. 生成コマンド実行漏れ (🟡 Warning)
検出 bash:
git diff --name-only origin/main...HEAD | grep -q 'schema\.prisma' && \
! git diff --name-only origin/main...HEAD | grep -q '__generated__/' && \
echo "WARN: schema 変更あり、__generated__ 未更新 → prisma generate 漏れ"
git diff --name-only origin/main...HEAD | grep -q 'api/.*/index\.ts' && \
! git diff --name-only origin/main...HEAD | grep -qE '\$server\.ts|\$relay\.ts' && \
echo "WARN: index.ts 変更あり、生成物未更新 → frourio 生成漏れの可能性"
15. 型エラー / ビルド (🔴 Critical)
検出 bash:
npx tsc --noEmit
npm run generate && git diff --exit-code
16. レビュー実行手順
git diff --name-only origin/main...HEAD で差分ファイル一覧
- セクション 2 (自動生成ファイル編集) — 即 reject 判定
- セクション 1, 3-6, 8, 10, 11, 13, 15 (🔴 Critical) — 全数チェック
- セクション 7, 9, 12, 14 (🟡 Warning) — サンプリング or 全数
- 検出 → genshijin-review 形式で出力
レビュー出力フォーマット (genshijin-review 互換)
[file:line] 🔴/🟡/🔵 [問題]: [修正案]
例:
api/users/_id@string/controller.ts:12 🔴 Prisma 生値直接返却: user.toDto() に変更
api/users/index.ts:5 🔴 自前 resBody 型定義: UserModelDto を import 使用
prisma/schema.prisma:34 🔴 password に @dto(hidden: true) 欠落: コメント追加
frontend/pages/users.tsx:18 🔴 useEffect でデータ取得: useFrourioSWR に置換
$server.ts:1 🔴 自動生成ファイル編集: 元に戻し frourio CLI 再実行
api/admin/report/hooks.ts:5-65 🟡 hooks ロジックコピペ: middleware/authAdminMiddleware に切り出し
api/foo/_id/index.ts 🔴 動的セグメント型指定欠落: _id@string or _id@number に rename
package.json:12 🔴 prisma バージョン不整合: prisma と @prisma/client を同一バージョン (>= 7.2.0) に揃える
関連 skill
- 実装規約:
frourio-framework skill (skills/frourio-framework/SKILL.md)
- 実装規約 (rule 形式):
rules/frourio-framework-activate.md
- レビュー規約 (rule 形式):
rules/frourio-framework-review.md