mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-04 08:13:30 +08:00
fix: harden unicode safety checks
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
**🌐 言語 / Language / 語言**
|
||||
**言語 / Language / 語言**
|
||||
|
||||
[**English**](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md)
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 🚀 クイックスタート
|
||||
## クイックスタート
|
||||
|
||||
2分以内に起動できます:
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
|
||||
### ステップ2:ルールをインストール(必須)
|
||||
|
||||
> ⚠️ **重要:** Claude Codeプラグインは`rules`を自動配布できません。手動でインストールしてください:
|
||||
> WARNING: **重要:** Claude Codeプラグインは`rules`を自動配布できません。手動でインストールしてください:
|
||||
|
||||
```bash
|
||||
# まずリポジトリをクローン
|
||||
@@ -143,11 +143,11 @@ cp -r everything-claude-code/rules/golang/* ~/.claude/rules/
|
||||
/plugin list everything-claude-code@everything-claude-code
|
||||
```
|
||||
|
||||
✨ **完了です!** これで13のエージェント、43のスキル、31のコマンドにアクセスできます。
|
||||
**完了です!** これで13のエージェント、43のスキル、31のコマンドにアクセスできます。
|
||||
|
||||
---
|
||||
|
||||
## 🌐 クロスプラットフォーム対応
|
||||
## クロスプラットフォーム対応
|
||||
|
||||
このプラグインは **Windows、macOS、Linux** を完全にサポートしています。すべてのフックとスクリプトが Node.js で書き直され、最大の互換性を実現しています。
|
||||
|
||||
@@ -182,7 +182,7 @@ node scripts/setup-package-manager.js --detect
|
||||
|
||||
---
|
||||
|
||||
## 📦 含まれるもの
|
||||
## 含まれるもの
|
||||
|
||||
このリポジトリは**Claude Codeプラグイン**です - 直接インストールするか、コンポーネントを手動でコピーできます。
|
||||
|
||||
@@ -315,7 +315,7 @@ everything-claude-code/
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ エコシステムツール
|
||||
## エコシステムツール
|
||||
|
||||
### スキル作成ツール
|
||||
|
||||
@@ -374,7 +374,7 @@ Claude Codeで`/security-scan`を実行、または[GitHub Action](https://githu
|
||||
|
||||
[GitHub](https://github.com/affaan-m/agentshield) | [npm](https://www.npmjs.com/package/ecc-agentshield)
|
||||
|
||||
### 🧠 継続的学習 v2
|
||||
### 継続的学習 v2
|
||||
|
||||
instinctベースの学習システムがパターンを自動学習:
|
||||
|
||||
@@ -389,7 +389,7 @@ instinctベースの学習システムがパターンを自動学習:
|
||||
|
||||
---
|
||||
|
||||
## 📋 要件
|
||||
## 要件
|
||||
|
||||
### Claude Code CLI バージョン
|
||||
|
||||
@@ -404,7 +404,7 @@ claude --version
|
||||
|
||||
### 重要: フック自動読み込み動作
|
||||
|
||||
> ⚠️ **貢献者向け:** `.claude-plugin/plugin.json`に`"hooks"`フィールドを追加しないでください。これは回帰テストで強制されます。
|
||||
> WARNING: **貢献者向け:** `.claude-plugin/plugin.json`に`"hooks"`フィールドを追加しないでください。これは回帰テストで強制されます。
|
||||
|
||||
Claude Code v2.1+は、インストール済みプラグインの`hooks/hooks.json`(規約)を自動読み込みします。`plugin.json`で明示的に宣言するとエラーが発生します:
|
||||
|
||||
@@ -416,7 +416,7 @@ Duplicate hook file detected: ./hooks/hooks.json is already resolved to a loaded
|
||||
|
||||
---
|
||||
|
||||
## 📥 インストール
|
||||
## インストール
|
||||
|
||||
### オプション1:プラグインとしてインストール(推奨)
|
||||
|
||||
@@ -471,7 +471,7 @@ Duplicate hook file detected: ./hooks/hooks.json is already resolved to a loaded
|
||||
|
||||
---
|
||||
|
||||
### 🔧 オプション2:手動インストール
|
||||
### オプション2:手動インストール
|
||||
|
||||
インストール内容を手動で制御したい場合:
|
||||
|
||||
@@ -507,7 +507,7 @@ cp -r everything-claude-code/skills/* ~/.claude/skills/
|
||||
|
||||
---
|
||||
|
||||
## 🎯 主要概念
|
||||
## 主要概念
|
||||
|
||||
### エージェント
|
||||
|
||||
@@ -569,7 +569,7 @@ rules/
|
||||
|
||||
---
|
||||
|
||||
## 🧪 テストを実行
|
||||
## テストを実行
|
||||
|
||||
プラグインには包括的なテストスイートが含まれています:
|
||||
|
||||
@@ -585,7 +585,7 @@ node tests/hooks/hooks.test.js
|
||||
|
||||
---
|
||||
|
||||
## 🤝 貢献
|
||||
## 貢献
|
||||
|
||||
**貢献は大歓迎で、奨励されています。**
|
||||
|
||||
@@ -637,7 +637,7 @@ npm install ecc-universal
|
||||
|
||||
---
|
||||
|
||||
## 🔌 OpenCodeサポート
|
||||
## OpenCodeサポート
|
||||
|
||||
ECCは**フルOpenCodeサポート**をプラグインとフック含めて提供。
|
||||
|
||||
@@ -657,13 +657,13 @@ opencode
|
||||
|
||||
| 機能 | Claude Code | OpenCode | ステータス |
|
||||
|---------|-------------|----------|--------|
|
||||
| Agents | ✅ 14 エージェント | ✅ 12 エージェント | **Claude Code がリード** |
|
||||
| Commands | ✅ 30 コマンド | ✅ 24 コマンド | **Claude Code がリード** |
|
||||
| Skills | ✅ 28 スキル | ✅ 16 スキル | **Claude Code がリード** |
|
||||
| Hooks | ✅ 3 フェーズ | ✅ 20+ イベント | **OpenCode が多い!** |
|
||||
| Rules | ✅ 8 ルール | ✅ 8 ルール | **完全パリティ** |
|
||||
| MCP Servers | ✅ 完全 | ✅ 完全 | **完全パリティ** |
|
||||
| Custom Tools | ✅ フック経由 | ✅ ネイティブサポート | **OpenCode がより良い** |
|
||||
| Agents | PASS: 14 エージェント | PASS: 12 エージェント | **Claude Code がリード** |
|
||||
| Commands | PASS: 30 コマンド | PASS: 24 コマンド | **Claude Code がリード** |
|
||||
| Skills | PASS: 28 スキル | PASS: 16 スキル | **Claude Code がリード** |
|
||||
| Hooks | PASS: 3 フェーズ | PASS: 20+ イベント | **OpenCode が多い!** |
|
||||
| Rules | PASS: 8 ルール | PASS: 8 ルール | **完全パリティ** |
|
||||
| MCP Servers | PASS: 完全 | PASS: 完全 | **完全パリティ** |
|
||||
| Custom Tools | PASS: フック経由 | PASS: ネイティブサポート | **OpenCode がより良い** |
|
||||
|
||||
### プラグイン経由のフックサポート
|
||||
|
||||
@@ -737,7 +737,7 @@ npm install ecc-universal
|
||||
|
||||
---
|
||||
|
||||
## 📖 背景
|
||||
## 背景
|
||||
|
||||
実験的なリリース以来、Claude Codeを使用してきました。2025年9月、[@DRodriguezFX](https://x.com/DRodriguezFX)と一緒にClaude Codeで[zenith.chat](https://zenith.chat)を構築し、Anthropic x Forum Venturesハッカソンで優勝しました。
|
||||
|
||||
@@ -745,7 +745,7 @@ npm install ecc-universal
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要な注記
|
||||
## WARNING: 重要な注記
|
||||
|
||||
### コンテキストウィンドウ管理
|
||||
|
||||
@@ -768,13 +768,13 @@ npm install ecc-universal
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Star 履歴
|
||||
## Star 履歴
|
||||
|
||||
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 リンク
|
||||
## リンク
|
||||
|
||||
- **簡潔ガイド(まずはこれ):** [Everything Claude Code 簡潔ガイド](https://x.com/affaanmustafa/status/2012378465664745795)
|
||||
- **詳細ガイド(高度):** [Everything Claude Code 詳細ガイド](https://x.com/affaanmustafa/status/2014040193557471352)
|
||||
@@ -784,7 +784,7 @@ npm install ecc-universal
|
||||
|
||||
---
|
||||
|
||||
## 📄 ライセンス
|
||||
## ライセンス
|
||||
|
||||
MIT - 自由に使用、必要に応じて修正、可能であれば貢献してください。
|
||||
|
||||
|
||||
@@ -103,12 +103,12 @@ c) 影響度別に優先順位付け
|
||||
|
||||
**パターン 1: 型推論の失敗**
|
||||
```typescript
|
||||
// ❌ エラー: Parameter 'x' implicitly has an 'any' type
|
||||
// FAIL: エラー: Parameter 'x' implicitly has an 'any' type
|
||||
function add(x, y) {
|
||||
return x + y
|
||||
}
|
||||
|
||||
// ✅ 修正: 型アノテーションを追加
|
||||
// PASS: 修正: 型アノテーションを追加
|
||||
function add(x: number, y: number): number {
|
||||
return x + y
|
||||
}
|
||||
@@ -116,25 +116,25 @@ function add(x: number, y: number): number {
|
||||
|
||||
**パターン 2: Null/Undefinedエラー**
|
||||
```typescript
|
||||
// ❌ エラー: Object is possibly 'undefined'
|
||||
// FAIL: エラー: Object is possibly 'undefined'
|
||||
const name = user.name.toUpperCase()
|
||||
|
||||
// ✅ 修正: オプショナルチェーン
|
||||
// PASS: 修正: オプショナルチェーン
|
||||
const name = user?.name?.toUpperCase()
|
||||
|
||||
// ✅ または: Nullチェック
|
||||
// PASS: または: Nullチェック
|
||||
const name = user && user.name ? user.name.toUpperCase() : ''
|
||||
```
|
||||
|
||||
**パターン 3: プロパティの欠落**
|
||||
```typescript
|
||||
// ❌ エラー: Property 'age' does not exist on type 'User'
|
||||
// FAIL: エラー: Property 'age' does not exist on type 'User'
|
||||
interface User {
|
||||
name: string
|
||||
}
|
||||
const user: User = { name: 'John', age: 30 }
|
||||
|
||||
// ✅ 修正: インターフェースにプロパティを追加
|
||||
// PASS: 修正: インターフェースにプロパティを追加
|
||||
interface User {
|
||||
name: string
|
||||
age?: number // 常に存在しない場合はオプショナル
|
||||
@@ -143,10 +143,10 @@ interface User {
|
||||
|
||||
**パターン 4: インポートエラー**
|
||||
```typescript
|
||||
// ❌ エラー: Cannot find module '@/lib/utils'
|
||||
// FAIL: エラー: Cannot find module '@/lib/utils'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
|
||||
// ✅ 修正1: tsconfigのパスが正しいか確認
|
||||
// PASS: 修正1: tsconfigのパスが正しいか確認
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
@@ -155,38 +155,38 @@ import { formatDate } from '@/lib/utils'
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 修正2: 相対インポートを使用
|
||||
// PASS: 修正2: 相対インポートを使用
|
||||
import { formatDate } from '../lib/utils'
|
||||
|
||||
// ✅ 修正3: 欠落しているパッケージをインストール
|
||||
// PASS: 修正3: 欠落しているパッケージをインストール
|
||||
npm install @/lib/utils
|
||||
```
|
||||
|
||||
**パターン 5: 型の不一致**
|
||||
```typescript
|
||||
// ❌ エラー: Type 'string' is not assignable to type 'number'
|
||||
// FAIL: エラー: Type 'string' is not assignable to type 'number'
|
||||
const age: number = "30"
|
||||
|
||||
// ✅ 修正: 文字列を数値にパース
|
||||
// PASS: 修正: 文字列を数値にパース
|
||||
const age: number = parseInt("30", 10)
|
||||
|
||||
// ✅ または: 型を変更
|
||||
// PASS: または: 型を変更
|
||||
const age: string = "30"
|
||||
```
|
||||
|
||||
**パターン 6: ジェネリック制約**
|
||||
```typescript
|
||||
// ❌ エラー: Type 'T' is not assignable to type 'string'
|
||||
// FAIL: エラー: Type 'T' is not assignable to type 'string'
|
||||
function getLength<T>(item: T): number {
|
||||
return item.length
|
||||
}
|
||||
|
||||
// ✅ 修正: 制約を追加
|
||||
// PASS: 修正: 制約を追加
|
||||
function getLength<T extends { length: number }>(item: T): number {
|
||||
return item.length
|
||||
}
|
||||
|
||||
// ✅ または: より具体的な制約
|
||||
// PASS: または: より具体的な制約
|
||||
function getLength<T extends string | any[]>(item: T): number {
|
||||
return item.length
|
||||
}
|
||||
@@ -194,14 +194,14 @@ function getLength<T extends string | any[]>(item: T): number {
|
||||
|
||||
**パターン 7: React Hookエラー**
|
||||
```typescript
|
||||
// ❌ エラー: React Hook "useState" cannot be called in a function
|
||||
// FAIL: エラー: React Hook "useState" cannot be called in a function
|
||||
function MyComponent() {
|
||||
if (condition) {
|
||||
const [state, setState] = useState(0) // エラー!
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 修正: フックをトップレベルに移動
|
||||
// PASS: 修正: フックをトップレベルに移動
|
||||
function MyComponent() {
|
||||
const [state, setState] = useState(0)
|
||||
|
||||
@@ -215,12 +215,12 @@ function MyComponent() {
|
||||
|
||||
**パターン 8: Async/Awaitエラー**
|
||||
```typescript
|
||||
// ❌ エラー: 'await' expressions are only allowed within async functions
|
||||
// FAIL: エラー: 'await' expressions are only allowed within async functions
|
||||
function fetchData() {
|
||||
const data = await fetch('/api/data')
|
||||
}
|
||||
|
||||
// ✅ 修正: asyncキーワードを追加
|
||||
// PASS: 修正: asyncキーワードを追加
|
||||
async function fetchData() {
|
||||
const data = await fetch('/api/data')
|
||||
}
|
||||
@@ -228,14 +228,14 @@ async function fetchData() {
|
||||
|
||||
**パターン 9: モジュールが見つからない**
|
||||
```typescript
|
||||
// ❌ エラー: Cannot find module 'react' or its corresponding type declarations
|
||||
// FAIL: エラー: Cannot find module 'react' or its corresponding type declarations
|
||||
import React from 'react'
|
||||
|
||||
// ✅ 修正: 依存関係をインストール
|
||||
// PASS: 修正: 依存関係をインストール
|
||||
npm install react
|
||||
npm install --save-dev @types/react
|
||||
|
||||
// ✅ 確認: package.jsonに依存関係があることを確認
|
||||
// PASS: 確認: package.jsonに依存関係があることを確認
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "^19.0.0"
|
||||
@@ -248,18 +248,18 @@ npm install --save-dev @types/react
|
||||
|
||||
**パターン 10: Next.js固有のエラー**
|
||||
```typescript
|
||||
// ❌ エラー: Fast Refresh had to perform a full reload
|
||||
// FAIL: エラー: Fast Refresh had to perform a full reload
|
||||
// 通常、コンポーネント以外のエクスポートが原因
|
||||
|
||||
// ✅ 修正: エクスポートを分離
|
||||
// ❌ 間違い: file.tsx
|
||||
// PASS: 修正: エクスポートを分離
|
||||
// FAIL: 間違い: file.tsx
|
||||
export const MyComponent = () => <div />
|
||||
export const someConstant = 42 // フルリロードの原因
|
||||
|
||||
// ✅ 正しい: component.tsx
|
||||
// PASS: 正しい: component.tsx
|
||||
export const MyComponent = () => <div />
|
||||
|
||||
// ✅ 正しい: constants.ts
|
||||
// PASS: 正しい: constants.ts
|
||||
export const someConstant = 42
|
||||
```
|
||||
|
||||
@@ -267,7 +267,7 @@ export const someConstant = 42
|
||||
|
||||
### Next.js 15 + React 19の互換性
|
||||
```typescript
|
||||
// ❌ エラー: React 19の型変更
|
||||
// FAIL: エラー: React 19の型変更
|
||||
import { FC } from 'react'
|
||||
|
||||
interface Props {
|
||||
@@ -278,7 +278,7 @@ const Component: FC<Props> = ({ children }) => {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
|
||||
// ✅ 修正: React 19ではFCは不要
|
||||
// PASS: 修正: React 19ではFCは不要
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
}
|
||||
@@ -290,12 +290,12 @@ const Component = ({ children }: Props) => {
|
||||
|
||||
### Supabaseクライアントの型
|
||||
```typescript
|
||||
// ❌ エラー: Type 'any' not assignable
|
||||
// FAIL: エラー: Type 'any' not assignable
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
|
||||
// ✅ 修正: 型アノテーションを追加
|
||||
// PASS: 修正: 型アノテーションを追加
|
||||
interface Market {
|
||||
id: string
|
||||
name: string
|
||||
@@ -310,10 +310,10 @@ const { data } = await supabase
|
||||
|
||||
### Redis Stackの型
|
||||
```typescript
|
||||
// ❌ エラー: Property 'ft' does not exist on type 'RedisClientType'
|
||||
// FAIL: エラー: Property 'ft' does not exist on type 'RedisClientType'
|
||||
const results = await client.ft.search('idx:markets', query)
|
||||
|
||||
// ✅ 修正: 適切なRedis Stackの型を使用
|
||||
// PASS: 修正: 適切なRedis Stackの型を使用
|
||||
import { createClient } from 'redis'
|
||||
|
||||
const client = createClient({
|
||||
@@ -328,10 +328,10 @@ const results = await client.ft.search('idx:markets', query)
|
||||
|
||||
### Solana Web3.jsの型
|
||||
```typescript
|
||||
// ❌ エラー: Argument of type 'string' not assignable to 'PublicKey'
|
||||
// FAIL: エラー: Argument of type 'string' not assignable to 'PublicKey'
|
||||
const publicKey = wallet.address
|
||||
|
||||
// ✅ 修正: PublicKeyコンストラクタを使用
|
||||
// PASS: 修正: PublicKeyコンストラクタを使用
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
const publicKey = new PublicKey(wallet.address)
|
||||
```
|
||||
@@ -341,34 +341,34 @@ const publicKey = new PublicKey(wallet.address)
|
||||
**重要: できる限り最小限の変更を行う**
|
||||
|
||||
### すべきこと:
|
||||
✅ 欠落している型アノテーションを追加
|
||||
✅ 必要な箇所にnullチェックを追加
|
||||
✅ インポート/エクスポートを修正
|
||||
✅ 欠落している依存関係を追加
|
||||
✅ 型定義を更新
|
||||
✅ 設定ファイルを修正
|
||||
PASS: 欠落している型アノテーションを追加
|
||||
PASS: 必要な箇所にnullチェックを追加
|
||||
PASS: インポート/エクスポートを修正
|
||||
PASS: 欠落している依存関係を追加
|
||||
PASS: 型定義を更新
|
||||
PASS: 設定ファイルを修正
|
||||
|
||||
### してはいけないこと:
|
||||
❌ 関連のないコードをリファクタリング
|
||||
❌ アーキテクチャを変更
|
||||
❌ 変数/関数の名前を変更(エラーの原因でない限り)
|
||||
❌ 新機能を追加
|
||||
❌ ロジックフローを変更(エラー修正以外)
|
||||
❌ パフォーマンスを最適化
|
||||
❌ コードスタイルを改善
|
||||
FAIL: 関連のないコードをリファクタリング
|
||||
FAIL: アーキテクチャを変更
|
||||
FAIL: 変数/関数の名前を変更(エラーの原因でない限り)
|
||||
FAIL: 新機能を追加
|
||||
FAIL: ロジックフローを変更(エラー修正以外)
|
||||
FAIL: パフォーマンスを最適化
|
||||
FAIL: コードスタイルを改善
|
||||
|
||||
**最小差分の例:**
|
||||
|
||||
```typescript
|
||||
// ファイルは200行あり、45行目にエラーがある
|
||||
|
||||
// ❌ 間違い: ファイル全体をリファクタリング
|
||||
// FAIL: 間違い: ファイル全体をリファクタリング
|
||||
// - 変数の名前変更
|
||||
// - 関数の抽出
|
||||
// - パターンの変更
|
||||
// 結果: 50行変更
|
||||
|
||||
// ✅ 正しい: エラーのみを修正
|
||||
// PASS: 正しい: エラーのみを修正
|
||||
// - 45行目に型アノテーションを追加
|
||||
// 結果: 1行変更
|
||||
|
||||
@@ -376,12 +376,12 @@ function processData(data) { // 45行目 - エラー: 'data' implicitly has 'any
|
||||
return data.map(item => item.value)
|
||||
}
|
||||
|
||||
// ✅ 最小限の修正:
|
||||
// PASS: 最小限の修正:
|
||||
function processData(data: any[]) { // この行のみを変更
|
||||
return data.map(item => item.value)
|
||||
}
|
||||
|
||||
// ✅ より良い最小限の修正(型が既知の場合):
|
||||
// PASS: より良い最小限の修正(型が既知の場合):
|
||||
function processData(data: Array<{ value: number }>) {
|
||||
return data.map(item => item.value)
|
||||
}
|
||||
@@ -396,7 +396,7 @@ function processData(data: Array<{ value: number }>) {
|
||||
**ビルド対象:** Next.jsプロダクション / TypeScriptチェック / ESLint
|
||||
**初期エラー数:** X
|
||||
**修正済みエラー数:** Y
|
||||
**ビルドステータス:** ✅ 成功 / ❌ 失敗
|
||||
**ビルドステータス:** PASS: 成功 / FAIL: 失敗
|
||||
|
||||
## 修正済みエラー
|
||||
|
||||
@@ -430,17 +430,17 @@ Parameter 'market' implicitly has an 'any' type.
|
||||
|
||||
## 検証手順
|
||||
|
||||
1. ✅ TypeScriptチェック成功: `npx tsc --noEmit`
|
||||
2. ✅ Next.jsビルド成功: `npm run build`
|
||||
3. ✅ ESLintチェック成功: `npx eslint .`
|
||||
4. ✅ 新しいエラーが導入されていない
|
||||
5. ✅ 開発サーバー起動: `npm run dev`
|
||||
1. PASS: TypeScriptチェック成功: `npx tsc --noEmit`
|
||||
2. PASS: Next.jsビルド成功: `npm run build`
|
||||
3. PASS: ESLintチェック成功: `npx eslint .`
|
||||
4. PASS: 新しいエラーが導入されていない
|
||||
5. PASS: 開発サーバー起動: `npm run dev`
|
||||
|
||||
## まとめ
|
||||
|
||||
- 解決されたエラー総数: X
|
||||
- 変更行数総数: Y
|
||||
- ビルドステータス: ✅ 成功
|
||||
- ビルドステータス: PASS: 成功
|
||||
- 修正時間: Z 分
|
||||
- ブロッキング問題: 0 件残存
|
||||
|
||||
@@ -470,19 +470,19 @@ Parameter 'market' implicitly has an 'any' type.
|
||||
|
||||
## ビルドエラーの優先度レベル
|
||||
|
||||
### 🔴 クリティカル(即座に修正)
|
||||
### クリティカル(即座に修正)
|
||||
- ビルドが完全に壊れている
|
||||
- 開発サーバーが起動しない
|
||||
- プロダクションデプロイがブロックされている
|
||||
- 複数のファイルが失敗している
|
||||
|
||||
### 🟡 高(早急に修正)
|
||||
### 高(早急に修正)
|
||||
- 単一ファイルの失敗
|
||||
- 新しいコードの型エラー
|
||||
- インポートエラー
|
||||
- 重要でないビルド警告
|
||||
|
||||
### 🟢 中(可能な時に修正)
|
||||
### 中(可能な時に修正)
|
||||
- リンター警告
|
||||
- 非推奨APIの使用
|
||||
- 非厳格な型の問題
|
||||
@@ -521,13 +521,13 @@ npm install
|
||||
## 成功指標
|
||||
|
||||
ビルドエラー解決後:
|
||||
- ✅ `npx tsc --noEmit` が終了コード0で終了
|
||||
- ✅ `npm run build` が正常に完了
|
||||
- ✅ 新しいエラーが導入されていない
|
||||
- ✅ 最小限の行数変更(影響を受けたファイルの5%未満)
|
||||
- ✅ ビルド時間が大幅に増加していない
|
||||
- ✅ 開発サーバーがエラーなく動作
|
||||
- ✅ テストが依然として成功
|
||||
- PASS: `npx tsc --noEmit` が終了コード0で終了
|
||||
- PASS: `npm run build` が正常に完了
|
||||
- PASS: 新しいエラーが導入されていない
|
||||
- PASS: 最小限の行数変更(影響を受けたファイルの5%未満)
|
||||
- PASS: ビルド時間が大幅に増加していない
|
||||
- PASS: 開発サーバーがエラーなく動作
|
||||
- PASS: テストが依然として成功
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -81,15 +81,15 @@ model: opus
|
||||
問題: APIキーがソースコードに公開されている
|
||||
修正: 環境変数に移動
|
||||
|
||||
const apiKey = "sk-abc123"; // ❌ Bad
|
||||
const apiKey = "sk-abc123"; // FAIL: Bad
|
||||
const apiKey = process.env.API_KEY; // ✓ Good
|
||||
```
|
||||
|
||||
## 承認基準
|
||||
|
||||
- ✅ 承認: CRITICALまたはHIGH問題なし
|
||||
- ⚠️ 警告: MEDIUM問題のみ(注意してマージ可能)
|
||||
- ❌ ブロック: CRITICALまたはHIGH問題が見つかった
|
||||
- PASS: 承認: CRITICALまたはHIGH問題なし
|
||||
- WARNING: 警告: MEDIUM問題のみ(注意してマージ可能)
|
||||
- FAIL: ブロック: CRITICALまたはHIGH問題が見つかった
|
||||
|
||||
## プロジェクト固有のガイドライン(例)
|
||||
|
||||
|
||||
@@ -112,14 +112,14 @@ c) データ保護
|
||||
**影響:** 大きなテーブルで100〜1000倍高速なクエリ
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 外部キーにインデックスがない
|
||||
-- FAIL: 悪い: 外部キーにインデックスがない
|
||||
CREATE TABLE orders (
|
||||
id bigint PRIMARY KEY,
|
||||
customer_id bigint REFERENCES customers(id)
|
||||
-- インデックスが欠落!
|
||||
);
|
||||
|
||||
-- ✅ 良い: 外部キーにインデックス
|
||||
-- PASS: 良い: 外部キーにインデックス
|
||||
CREATE TABLE orders (
|
||||
id bigint PRIMARY KEY,
|
||||
customer_id bigint REFERENCES customers(id)
|
||||
@@ -137,11 +137,11 @@ CREATE INDEX orders_customer_id_idx ON orders (customer_id);
|
||||
| **Hash** | 等価のみ | `=`(B-treeより若干高速) |
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: JSONB包含のためのB-tree
|
||||
-- FAIL: 悪い: JSONB包含のためのB-tree
|
||||
CREATE INDEX products_attrs_idx ON products (attributes);
|
||||
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
|
||||
|
||||
-- ✅ 良い: JSONBのためのGIN
|
||||
-- PASS: 良い: JSONBのためのGIN
|
||||
CREATE INDEX products_attrs_idx ON products USING gin (attributes);
|
||||
```
|
||||
|
||||
@@ -150,11 +150,11 @@ CREATE INDEX products_attrs_idx ON products USING gin (attributes);
|
||||
**影響:** 複数列クエリで5〜10倍高速
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 個別のインデックス
|
||||
-- FAIL: 悪い: 個別のインデックス
|
||||
CREATE INDEX orders_status_idx ON orders (status);
|
||||
CREATE INDEX orders_created_idx ON orders (created_at);
|
||||
|
||||
-- ✅ 良い: 複合インデックス(等価列を最初に、次に範囲)
|
||||
-- PASS: 良い: 複合インデックス(等価列を最初に、次に範囲)
|
||||
CREATE INDEX orders_status_created_idx ON orders (status, created_at);
|
||||
```
|
||||
|
||||
@@ -170,11 +170,11 @@ CREATE INDEX orders_status_created_idx ON orders (status, created_at);
|
||||
**影響:** テーブルルックアップを回避することで2〜5倍高速なクエリ
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: テーブルからnameを取得する必要がある
|
||||
-- FAIL: 悪い: テーブルからnameを取得する必要がある
|
||||
CREATE INDEX users_email_idx ON users (email);
|
||||
SELECT email, name FROM users WHERE email = 'user@example.com';
|
||||
|
||||
-- ✅ 良い: すべての列がインデックスに含まれる
|
||||
-- PASS: 良い: すべての列がインデックスに含まれる
|
||||
CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
|
||||
```
|
||||
|
||||
@@ -183,10 +183,10 @@ CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
|
||||
**影響:** 5〜20倍小さいインデックス、高速な書き込みとクエリ
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 完全なインデックスには削除された行が含まれる
|
||||
-- FAIL: 悪い: 完全なインデックスには削除された行が含まれる
|
||||
CREATE INDEX users_email_idx ON users (email);
|
||||
|
||||
-- ✅ 良い: 部分インデックスは削除された行を除外
|
||||
-- PASS: 良い: 部分インデックスは削除された行を除外
|
||||
CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
|
||||
```
|
||||
|
||||
@@ -202,7 +202,7 @@ CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
|
||||
### 1. データ型の選択
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 不適切な型選択
|
||||
-- FAIL: 悪い: 不適切な型選択
|
||||
CREATE TABLE users (
|
||||
id int, -- 21億でオーバーフロー
|
||||
email varchar(255), -- 人為的な制限
|
||||
@@ -211,7 +211,7 @@ CREATE TABLE users (
|
||||
balance float -- 精度の損失
|
||||
);
|
||||
|
||||
-- ✅ 良い: 適切な型
|
||||
-- PASS: 良い: 適切な型
|
||||
CREATE TABLE users (
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
email text NOT NULL,
|
||||
@@ -224,18 +224,18 @@ CREATE TABLE users (
|
||||
### 2. 主キー戦略
|
||||
|
||||
```sql
|
||||
-- ✅ 単一データベース: IDENTITY(デフォルト、推奨)
|
||||
-- PASS: 単一データベース: IDENTITY(デフォルト、推奨)
|
||||
CREATE TABLE users (
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
|
||||
);
|
||||
|
||||
-- ✅ 分散システム: UUIDv7(時間順)
|
||||
-- PASS: 分散システム: UUIDv7(時間順)
|
||||
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
|
||||
CREATE TABLE orders (
|
||||
id uuid DEFAULT uuid_generate_v7() PRIMARY KEY
|
||||
);
|
||||
|
||||
-- ❌ 避ける: ランダムUUIDはインデックスの断片化を引き起こす
|
||||
-- FAIL: 避ける: ランダムUUIDはインデックスの断片化を引き起こす
|
||||
CREATE TABLE events (
|
||||
id uuid DEFAULT gen_random_uuid() PRIMARY KEY -- 断片化した挿入!
|
||||
);
|
||||
@@ -246,7 +246,7 @@ CREATE TABLE events (
|
||||
**使用する場合:** テーブル > 1億行、時系列データ、古いデータを削除する必要がある
|
||||
|
||||
```sql
|
||||
-- ✅ 良い: 月ごとにパーティション化
|
||||
-- PASS: 良い: 月ごとにパーティション化
|
||||
CREATE TABLE events (
|
||||
id bigint GENERATED ALWAYS AS IDENTITY,
|
||||
created_at timestamptz NOT NULL,
|
||||
@@ -266,11 +266,11 @@ DROP TABLE events_2023_01; -- 数時間かかるDELETEではなく即座に
|
||||
### 4. 小文字の識別子を使用
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 引用符付きの混合ケースは至る所で引用符が必要
|
||||
-- FAIL: 悪い: 引用符付きの混合ケースは至る所で引用符が必要
|
||||
CREATE TABLE "Users" ("userId" bigint, "firstName" text);
|
||||
SELECT "firstName" FROM "Users"; -- 引用符が必須!
|
||||
|
||||
-- ✅ 良い: 小文字は引用符なしで機能
|
||||
-- PASS: 良い: 小文字は引用符なしで機能
|
||||
CREATE TABLE users (user_id bigint, first_name text);
|
||||
SELECT first_name FROM users;
|
||||
```
|
||||
@@ -284,11 +284,11 @@ SELECT first_name FROM users;
|
||||
**影響:** 重要 - データベースで強制されるテナント分離
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: アプリケーションのみのフィルタリング
|
||||
-- FAIL: 悪い: アプリケーションのみのフィルタリング
|
||||
SELECT * FROM orders WHERE user_id = $current_user_id;
|
||||
-- バグはすべての注文が露出することを意味する!
|
||||
|
||||
-- ✅ 良い: データベースで強制されるRLS
|
||||
-- PASS: 良い: データベースで強制されるRLS
|
||||
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
|
||||
|
||||
@@ -308,11 +308,11 @@ CREATE POLICY orders_user_policy ON orders
|
||||
**影響:** 5〜10倍高速なRLSクエリ
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 関数が行ごとに呼び出される
|
||||
-- FAIL: 悪い: 関数が行ごとに呼び出される
|
||||
CREATE POLICY orders_policy ON orders
|
||||
USING (auth.uid() = user_id); -- 100万行に対して100万回呼び出される!
|
||||
|
||||
-- ✅ 良い: SELECTでラップ(キャッシュされ、一度だけ呼び出される)
|
||||
-- PASS: 良い: SELECTでラップ(キャッシュされ、一度だけ呼び出される)
|
||||
CREATE POLICY orders_policy ON orders
|
||||
USING ((SELECT auth.uid()) = user_id); -- 100倍高速
|
||||
|
||||
@@ -323,10 +323,10 @@ CREATE INDEX orders_user_id_idx ON orders (user_id);
|
||||
### 3. 最小権限アクセス
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 過度に許可的
|
||||
-- FAIL: 悪い: 過度に許可的
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES TO app_user;
|
||||
|
||||
-- ✅ 良い: 最小限の権限
|
||||
-- PASS: 良い: 最小限の権限
|
||||
CREATE ROLE app_readonly NOLOGIN;
|
||||
GRANT USAGE ON SCHEMA public TO app_readonly;
|
||||
GRANT SELECT ON public.products, public.categories TO app_readonly;
|
||||
@@ -378,14 +378,14 @@ SELECT pg_reload_conf();
|
||||
### 1. トランザクションを短く保つ
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 外部APIコール中にロックを保持
|
||||
-- FAIL: 悪い: 外部APIコール中にロックを保持
|
||||
BEGIN;
|
||||
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
|
||||
-- HTTPコールに5秒かかる...
|
||||
UPDATE orders SET status = 'paid' WHERE id = 1;
|
||||
COMMIT;
|
||||
|
||||
-- ✅ 良い: 最小限のロック期間
|
||||
-- PASS: 良い: 最小限のロック期間
|
||||
-- トランザクション外で最初にAPIコールを実行
|
||||
BEGIN;
|
||||
UPDATE orders SET status = 'paid', payment_id = $1
|
||||
@@ -397,12 +397,12 @@ COMMIT; -- ミリ秒でロックを保持
|
||||
### 2. デッドロックを防ぐ
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 一貫性のないロック順序がデッドロックを引き起こす
|
||||
-- FAIL: 悪い: 一貫性のないロック順序がデッドロックを引き起こす
|
||||
-- トランザクションA: 行1をロック、次に行2
|
||||
-- トランザクションB: 行2をロック、次に行1
|
||||
-- デッドロック!
|
||||
|
||||
-- ✅ 良い: 一貫したロック順序
|
||||
-- PASS: 良い: 一貫したロック順序
|
||||
BEGIN;
|
||||
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
|
||||
-- これで両方の行がロックされ、任意の順序で更新可能
|
||||
@@ -416,10 +416,10 @@ COMMIT;
|
||||
**影響:** ワーカーキューで10倍のスループット
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: ワーカーが互いを待つ
|
||||
-- FAIL: 悪い: ワーカーが互いを待つ
|
||||
SELECT * FROM jobs WHERE status = 'pending' LIMIT 1 FOR UPDATE;
|
||||
|
||||
-- ✅ 良い: ワーカーはロックされた行をスキップ
|
||||
-- PASS: 良い: ワーカーはロックされた行をスキップ
|
||||
UPDATE jobs
|
||||
SET status = 'processing', worker_id = $1, started_at = now()
|
||||
WHERE id = (
|
||||
@@ -441,36 +441,36 @@ RETURNING *;
|
||||
**影響:** バルク挿入が10〜50倍高速
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 個別の挿入
|
||||
-- FAIL: 悪い: 個別の挿入
|
||||
INSERT INTO events (user_id, action) VALUES (1, 'click');
|
||||
INSERT INTO events (user_id, action) VALUES (2, 'view');
|
||||
-- 1000回のラウンドトリップ
|
||||
|
||||
-- ✅ 良い: バッチ挿入
|
||||
-- PASS: 良い: バッチ挿入
|
||||
INSERT INTO events (user_id, action) VALUES
|
||||
(1, 'click'),
|
||||
(2, 'view'),
|
||||
(3, 'click');
|
||||
-- 1回のラウンドトリップ
|
||||
|
||||
-- ✅ 最良: 大きなデータセットにはCOPY
|
||||
-- PASS: 最良: 大きなデータセットにはCOPY
|
||||
COPY events (user_id, action) FROM '/path/to/data.csv' WITH (FORMAT csv);
|
||||
```
|
||||
|
||||
### 2. N+1クエリの排除
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: N+1パターン
|
||||
-- FAIL: 悪い: N+1パターン
|
||||
SELECT id FROM users WHERE active = true; -- 100件のIDを返す
|
||||
-- 次に100回のクエリ:
|
||||
SELECT * FROM orders WHERE user_id = 1;
|
||||
SELECT * FROM orders WHERE user_id = 2;
|
||||
-- ... 98回以上
|
||||
|
||||
-- ✅ 良い: ANYを使用した単一クエリ
|
||||
-- PASS: 良い: ANYを使用した単一クエリ
|
||||
SELECT * FROM orders WHERE user_id = ANY(ARRAY[1, 2, 3, ...]);
|
||||
|
||||
-- ✅ 良い: JOIN
|
||||
-- PASS: 良い: JOIN
|
||||
SELECT u.id, u.name, o.*
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON o.user_id = u.id
|
||||
@@ -482,11 +482,11 @@ WHERE u.active = true;
|
||||
**影響:** ページの深さに関係なく一貫したO(1)パフォーマンス
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: OFFSETは深さとともに遅くなる
|
||||
-- FAIL: 悪い: OFFSETは深さとともに遅くなる
|
||||
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 199980;
|
||||
-- 200,000行をスキャン!
|
||||
|
||||
-- ✅ 良い: カーソルベース(常に高速)
|
||||
-- PASS: 良い: カーソルベース(常に高速)
|
||||
SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
|
||||
-- インデックスを使用、O(1)
|
||||
```
|
||||
@@ -494,11 +494,11 @@ SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
|
||||
### 4. 挿入または更新のためのUPSERT
|
||||
|
||||
```sql
|
||||
-- ❌ 悪い: 競合状態
|
||||
-- FAIL: 悪い: 競合状態
|
||||
SELECT * FROM settings WHERE user_id = 123 AND key = 'theme';
|
||||
-- 両方のスレッドが何も見つけず、両方が挿入、一方が失敗
|
||||
|
||||
-- ✅ 良い: アトミックなUPSERT
|
||||
-- PASS: 良い: アトミックなUPSERT
|
||||
INSERT INTO settings (user_id, key, value)
|
||||
VALUES (123, 'theme', 'dark')
|
||||
ON CONFLICT (user_id, key)
|
||||
@@ -605,27 +605,27 @@ ORDER BY rank DESC;
|
||||
|
||||
## フラグを立てるべきアンチパターン
|
||||
|
||||
### ❌ クエリアンチパターン
|
||||
### FAIL: クエリアンチパターン
|
||||
- 本番コードでの`SELECT *`
|
||||
- WHERE/JOIN列にインデックスがない
|
||||
- 大きなテーブルでのOFFSETページネーション
|
||||
- N+1クエリパターン
|
||||
- パラメータ化されていないクエリ(SQLインジェクションリスク)
|
||||
|
||||
### ❌ スキーマアンチパターン
|
||||
### FAIL: スキーマアンチパターン
|
||||
- IDに`int`(`bigint`を使用)
|
||||
- 理由なく`varchar(255)`(`text`を使用)
|
||||
- タイムゾーンなしの`timestamp`(`timestamptz`を使用)
|
||||
- 主キーとしてのランダムUUID(UUIDv7またはIDENTITYを使用)
|
||||
- 引用符を必要とする混合ケースの識別子
|
||||
|
||||
### ❌ セキュリティアンチパターン
|
||||
### FAIL: セキュリティアンチパターン
|
||||
- アプリケーションユーザーへの`GRANT ALL`
|
||||
- マルチテナントテーブルでRLSが欠落
|
||||
- 行ごとに関数を呼び出すRLSポリシー(SELECTでラップされていない)
|
||||
- RLSポリシー列にインデックスがない
|
||||
|
||||
### ❌ 接続アンチパターン
|
||||
### FAIL: 接続アンチパターン
|
||||
- 接続プーリングなし
|
||||
- アイドルタイムアウトなし
|
||||
- トランザクションモードプーリングでのプリペアドステートメント
|
||||
|
||||
@@ -386,7 +386,7 @@ function extractJSDoc(pattern: string) {
|
||||
- [x] 古い参照なし
|
||||
|
||||
### 影響
|
||||
🟢 低 - ドキュメントのみ、コード変更なし
|
||||
低 - ドキュメントのみ、コード変更なし
|
||||
|
||||
完全なアーキテクチャ概要についてはdocs/CODEMAPS/INDEX.mdを参照してください。
|
||||
```
|
||||
|
||||
@@ -428,28 +428,28 @@ test('market search with complex query', async ({ page }) => {
|
||||
|
||||
**1. 競合状態**
|
||||
```typescript
|
||||
// ❌ 不安定: 要素が準備完了であると仮定しない
|
||||
// FAIL: 不安定: 要素が準備完了であると仮定しない
|
||||
await page.click('[data-testid="button"]')
|
||||
|
||||
// ✅ 安定: 要素が準備完了になるのを待つ
|
||||
// PASS: 安定: 要素が準備完了になるのを待つ
|
||||
await page.locator('[data-testid="button"]').click() // 組み込みの自動待機
|
||||
```
|
||||
|
||||
**2. ネットワークタイミング**
|
||||
```typescript
|
||||
// ❌ 不安定: 任意のタイムアウト
|
||||
// FAIL: 不安定: 任意のタイムアウト
|
||||
await page.waitForTimeout(5000)
|
||||
|
||||
// ✅ 安定: 特定の条件を待つ
|
||||
// PASS: 安定: 特定の条件を待つ
|
||||
await page.waitForResponse(resp => resp.url().includes('/api/markets'))
|
||||
```
|
||||
|
||||
**3. アニメーションタイミング**
|
||||
```typescript
|
||||
// ❌ 不安定: アニメーション中にクリック
|
||||
// FAIL: 不安定: アニメーション中にクリック
|
||||
await page.click('[data-testid="menu-item"]')
|
||||
|
||||
// ✅ 安定: アニメーションが完了するのを待つ
|
||||
// PASS: 安定: アニメーションが完了するのを待つ
|
||||
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.click('[data-testid="menu-item"]')
|
||||
@@ -548,7 +548,7 @@ jobs:
|
||||
|
||||
**日付:** YYYY-MM-DD HH:MM
|
||||
**期間:** Xm Ys
|
||||
**ステータス:** ✅ 成功 / ❌ 失敗
|
||||
**ステータス:** PASS: 成功 / FAIL: 失敗
|
||||
|
||||
## まとめ
|
||||
|
||||
@@ -561,20 +561,20 @@ jobs:
|
||||
## スイート別テスト結果
|
||||
|
||||
### Markets - ブラウズと検索
|
||||
- ✅ user can browse markets (2.3s)
|
||||
- ✅ semantic search returns relevant results (1.8s)
|
||||
- ✅ search handles no results (1.2s)
|
||||
- ❌ search with special characters (0.9s)
|
||||
- PASS: user can browse markets (2.3s)
|
||||
- PASS: semantic search returns relevant results (1.8s)
|
||||
- PASS: search handles no results (1.2s)
|
||||
- FAIL: search with special characters (0.9s)
|
||||
|
||||
### Wallet - 接続
|
||||
- ✅ user can connect MetaMask (3.1s)
|
||||
- ⚠️ user can connect Phantom (2.8s) - 不安定
|
||||
- ✅ user can disconnect wallet (1.5s)
|
||||
- PASS: user can connect MetaMask (3.1s)
|
||||
- WARNING: user can connect Phantom (2.8s) - 不安定
|
||||
- PASS: user can disconnect wallet (1.5s)
|
||||
|
||||
### Trading - コアフロー
|
||||
- ✅ user can place buy order (5.2s)
|
||||
- ❌ user can place sell order (4.8s)
|
||||
- ✅ insufficient balance shows error (1.9s)
|
||||
- PASS: user can place buy order (5.2s)
|
||||
- FAIL: user can place sell order (4.8s)
|
||||
- PASS: insufficient balance shows error (1.9s)
|
||||
|
||||
## 失敗したテスト
|
||||
|
||||
@@ -623,13 +623,13 @@ jobs:
|
||||
## 成功指標
|
||||
|
||||
E2Eテスト実行後:
|
||||
- ✅ すべての重要なジャーニーが成功(100%)
|
||||
- ✅ 全体の成功率 > 95%
|
||||
- ✅ 不安定率 < 5%
|
||||
- ✅ デプロイをブロックする失敗したテストなし
|
||||
- ✅ アーティファクトがアップロードされアクセス可能
|
||||
- ✅ テスト時間 < 10分
|
||||
- ✅ HTMLレポートが生成された
|
||||
- PASS: すべての重要なジャーニーが成功(100%)
|
||||
- PASS: 全体の成功率 > 95%
|
||||
- PASS: 不安定率 < 5%
|
||||
- PASS: デプロイをブロックする失敗したテストなし
|
||||
- PASS: アーティファクトがアップロードされアクセス可能
|
||||
- PASS: テスト時間 < 10分
|
||||
- PASS: HTMLレポートが生成された
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -146,22 +146,22 @@ e) テストがまだ合格することを確認
|
||||
|
||||
### 1. 未使用のインポート
|
||||
```typescript
|
||||
// ❌ 未使用のインポートを削除
|
||||
// FAIL: 未使用のインポートを削除
|
||||
import { useState, useEffect, useMemo } from 'react' // useStateのみ使用
|
||||
|
||||
// ✅ 使用されているもののみを保持
|
||||
// PASS: 使用されているもののみを保持
|
||||
import { useState } from 'react'
|
||||
```
|
||||
|
||||
### 2. デッドコードブランチ
|
||||
```typescript
|
||||
// ❌ 到達不可能なコードを削除
|
||||
// FAIL: 到達不可能なコードを削除
|
||||
if (false) {
|
||||
// これは決して実行されない
|
||||
doSomething()
|
||||
}
|
||||
|
||||
// ❌ 未使用の関数を削除
|
||||
// FAIL: 未使用の関数を削除
|
||||
export function unusedHelper() {
|
||||
// コードベースに参照なし
|
||||
}
|
||||
@@ -169,18 +169,18 @@ export function unusedHelper() {
|
||||
|
||||
### 3. 重複コンポーネント
|
||||
```typescript
|
||||
// ❌ 複数の類似コンポーネント
|
||||
// FAIL: 複数の類似コンポーネント
|
||||
components/Button.tsx
|
||||
components/PrimaryButton.tsx
|
||||
components/NewButton.tsx
|
||||
|
||||
// ✅ 1つに統合
|
||||
// PASS: 1つに統合
|
||||
components/Button.tsx (variantプロップ付き)
|
||||
```
|
||||
|
||||
### 4. 未使用の依存関係
|
||||
```json
|
||||
// ❌ インストールされているがインポートされていないパッケージ
|
||||
// FAIL: インストールされているがインポートされていないパッケージ
|
||||
{
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21", // どこでも使用されていない
|
||||
@@ -240,7 +240,7 @@ components/Button.tsx (variantプロップ付き)
|
||||
- 依存関係: -Xパッケージ
|
||||
|
||||
### リスクレベル
|
||||
🟢 低 - 検証可能な未使用コードのみを削除
|
||||
低 - 検証可能な未使用コードのみを削除
|
||||
|
||||
詳細はDELETION_LOG.mdを参照してください。
|
||||
```
|
||||
@@ -294,12 +294,12 @@ components/Button.tsx (variantプロップ付き)
|
||||
## 成功指標
|
||||
|
||||
クリーンアップセッション後:
|
||||
- ✅ すべてのテストが合格
|
||||
- ✅ ビルドが成功
|
||||
- ✅ コンソールエラーなし
|
||||
- ✅ DELETION_LOG.mdが更新された
|
||||
- ✅ バンドルサイズが削減された
|
||||
- ✅ 本番環境で回帰なし
|
||||
- PASS: すべてのテストが合格
|
||||
- PASS: ビルドが成功
|
||||
- PASS: コンソールエラーなし
|
||||
- PASS: DELETION_LOG.mdが更新された
|
||||
- PASS: バンドルサイズが削減された
|
||||
- PASS: 本番環境で回帰なし
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -184,12 +184,12 @@ APIセキュリティ:
|
||||
### 1. ハードコードされたシークレット(重要)
|
||||
|
||||
```javascript
|
||||
// ❌ 重要: ハードコードされたシークレット
|
||||
// FAIL: 重要: ハードコードされたシークレット
|
||||
const apiKey = "sk-proj-xxxxx"
|
||||
const password = "admin123"
|
||||
const token = "ghp_xxxxxxxxxxxx"
|
||||
|
||||
// ✅ 正しい: 環境変数
|
||||
// PASS: 正しい: 環境変数
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
if (!apiKey) {
|
||||
throw new Error('OPENAI_API_KEY not configured')
|
||||
@@ -199,11 +199,11 @@ if (!apiKey) {
|
||||
### 2. SQLインジェクション(重要)
|
||||
|
||||
```javascript
|
||||
// ❌ 重要: SQLインジェクションの脆弱性
|
||||
// FAIL: 重要: SQLインジェクションの脆弱性
|
||||
const query = `SELECT * FROM users WHERE id = ${userId}`
|
||||
await db.query(query)
|
||||
|
||||
// ✅ 正しい: パラメータ化されたクエリ
|
||||
// PASS: 正しい: パラメータ化されたクエリ
|
||||
const { data } = await supabase
|
||||
.from('users')
|
||||
.select('*')
|
||||
@@ -213,11 +213,11 @@ const { data } = await supabase
|
||||
### 3. コマンドインジェクション(重要)
|
||||
|
||||
```javascript
|
||||
// ❌ 重要: コマンドインジェクション
|
||||
// FAIL: 重要: コマンドインジェクション
|
||||
const { exec } = require('child_process')
|
||||
exec(`ping ${userInput}`, callback)
|
||||
|
||||
// ✅ 正しい: シェルコマンドではなくライブラリを使用
|
||||
// PASS: 正しい: シェルコマンドではなくライブラリを使用
|
||||
const dns = require('dns')
|
||||
dns.lookup(userInput, callback)
|
||||
```
|
||||
@@ -225,10 +225,10 @@ dns.lookup(userInput, callback)
|
||||
### 4. クロスサイトスクリプティング(XSS)(高)
|
||||
|
||||
```javascript
|
||||
// ❌ 高: XSS脆弱性
|
||||
// FAIL: 高: XSS脆弱性
|
||||
element.innerHTML = userInput
|
||||
|
||||
// ✅ 正しい: textContentを使用またはサニタイズ
|
||||
// PASS: 正しい: textContentを使用またはサニタイズ
|
||||
element.textContent = userInput
|
||||
// または
|
||||
import DOMPurify from 'dompurify'
|
||||
@@ -238,10 +238,10 @@ element.innerHTML = DOMPurify.sanitize(userInput)
|
||||
### 5. サーバーサイドリクエストフォージェリ(SSRF)(高)
|
||||
|
||||
```javascript
|
||||
// ❌ 高: SSRF脆弱性
|
||||
// FAIL: 高: SSRF脆弱性
|
||||
const response = await fetch(userProvidedUrl)
|
||||
|
||||
// ✅ 正しい: URLを検証してホワイトリスト
|
||||
// PASS: 正しい: URLを検証してホワイトリスト
|
||||
const allowedDomains = ['api.example.com', 'cdn.example.com']
|
||||
const url = new URL(userProvidedUrl)
|
||||
if (!allowedDomains.includes(url.hostname)) {
|
||||
@@ -253,10 +253,10 @@ const response = await fetch(url.toString())
|
||||
### 6. 安全でない認証(重要)
|
||||
|
||||
```javascript
|
||||
// ❌ 重要: 平文パスワード比較
|
||||
// FAIL: 重要: 平文パスワード比較
|
||||
if (password === storedPassword) { /* ログイン */ }
|
||||
|
||||
// ✅ 正しい: ハッシュ化されたパスワード比較
|
||||
// PASS: 正しい: ハッシュ化されたパスワード比較
|
||||
import bcrypt from 'bcrypt'
|
||||
const isValid = await bcrypt.compare(password, hashedPassword)
|
||||
```
|
||||
@@ -264,13 +264,13 @@ const isValid = await bcrypt.compare(password, hashedPassword)
|
||||
### 7. 不十分な認可(重要)
|
||||
|
||||
```javascript
|
||||
// ❌ 重要: 認可チェックなし
|
||||
// FAIL: 重要: 認可チェックなし
|
||||
app.get('/api/user/:id', async (req, res) => {
|
||||
const user = await getUser(req.params.id)
|
||||
res.json(user)
|
||||
})
|
||||
|
||||
// ✅ 正しい: ユーザーがリソースにアクセスできることを確認
|
||||
// PASS: 正しい: ユーザーがリソースにアクセスできることを確認
|
||||
app.get('/api/user/:id', authenticateUser, async (req, res) => {
|
||||
if (req.user.id !== req.params.id && !req.user.isAdmin) {
|
||||
return res.status(403).json({ error: 'Forbidden' })
|
||||
@@ -283,13 +283,13 @@ app.get('/api/user/:id', authenticateUser, async (req, res) => {
|
||||
### 8. 金融操作の競合状態(重要)
|
||||
|
||||
```javascript
|
||||
// ❌ 重要: 残高チェックの競合状態
|
||||
// FAIL: 重要: 残高チェックの競合状態
|
||||
const balance = await getBalance(userId)
|
||||
if (balance >= amount) {
|
||||
await withdraw(userId, amount) // 別のリクエストが並行して出金できる!
|
||||
}
|
||||
|
||||
// ✅ 正しい: ロック付きアトミックトランザクション
|
||||
// PASS: 正しい: ロック付きアトミックトランザクション
|
||||
await db.transaction(async (trx) => {
|
||||
const balance = await trx('balances')
|
||||
.where({ user_id: userId })
|
||||
@@ -309,13 +309,13 @@ await db.transaction(async (trx) => {
|
||||
### 9. 不十分なレート制限(高)
|
||||
|
||||
```javascript
|
||||
// ❌ 高: レート制限なし
|
||||
// FAIL: 高: レート制限なし
|
||||
app.post('/api/trade', async (req, res) => {
|
||||
await executeTrade(req.body)
|
||||
res.json({ success: true })
|
||||
})
|
||||
|
||||
// ✅ 正しい: レート制限
|
||||
// PASS: 正しい: レート制限
|
||||
import rateLimit from 'express-rate-limit'
|
||||
|
||||
const tradeLimiter = rateLimit({
|
||||
@@ -333,10 +333,10 @@ app.post('/api/trade', tradeLimiter, async (req, res) => {
|
||||
### 10. 機密データのロギング(中)
|
||||
|
||||
```javascript
|
||||
// ❌ 中: 機密データのロギング
|
||||
// FAIL: 中: 機密データのロギング
|
||||
console.log('User login:', { email, password, apiKey })
|
||||
|
||||
// ✅ 正しい: ログをサニタイズ
|
||||
// PASS: 正しい: ログをサニタイズ
|
||||
console.log('User login:', {
|
||||
email: email.replace(/(?<=.).(?=.*@)/g, '*'),
|
||||
passwordProvided: !!password
|
||||
@@ -358,7 +358,7 @@ console.log('User login:', {
|
||||
- **高い問題:** Y
|
||||
- **中程度の問題:** Z
|
||||
- **低い問題:** W
|
||||
- **リスクレベル:** 🔴 高 / 🟡 中 / 🟢 低
|
||||
- **リスクレベル:** 高 / 中 / 低
|
||||
|
||||
## 重要な問題(即座に修正)
|
||||
|
||||
@@ -380,7 +380,7 @@ console.log('User login:', {
|
||||
|
||||
**修復:**
|
||||
```javascript
|
||||
// ✅ 安全な実装
|
||||
// PASS: 安全な実装
|
||||
```
|
||||
|
||||
**参考資料:**
|
||||
@@ -433,7 +433,7 @@ PRをレビューする際、インラインコメントを投稿:
|
||||
## セキュリティレビュー
|
||||
|
||||
**レビューアー:** security-reviewer agent
|
||||
**リスクレベル:** 🔴 高 / 🟡 中 / 🟢 低
|
||||
**リスクレベル:** 高 / 中 / 低
|
||||
|
||||
### ブロッキング問題
|
||||
- [ ] **重要**: [説明] @ `file:line`
|
||||
@@ -532,13 +532,13 @@ npm install --save-dev audit-ci
|
||||
## 成功指標
|
||||
|
||||
セキュリティレビュー後:
|
||||
- ✅ 重要な問題が見つからない
|
||||
- ✅ すべての高い問題が対処されている
|
||||
- ✅ セキュリティチェックリストが完了
|
||||
- ✅ コードにシークレットがない
|
||||
- ✅ 依存関係が最新
|
||||
- ✅ テストにセキュリティシナリオが含まれている
|
||||
- ✅ ドキュメントが更新されている
|
||||
- PASS: 重要な問題が見つからない
|
||||
- PASS: すべての高い問題が対処されている
|
||||
- PASS: セキュリティチェックリストが完了
|
||||
- PASS: コードにシークレットがない
|
||||
- PASS: 依存関係が最新
|
||||
- PASS: テストにセキュリティシナリオが含まれている
|
||||
- PASS: ドキュメントが更新されている
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -220,26 +220,26 @@ jest.mock('@/lib/openai', () => ({
|
||||
|
||||
## テストの悪臭(アンチパターン)
|
||||
|
||||
### ❌ 実装の詳細をテスト
|
||||
### FAIL: 実装の詳細をテスト
|
||||
```typescript
|
||||
// 内部状態をテストしない
|
||||
expect(component.state.count).toBe(5)
|
||||
```
|
||||
|
||||
### ✅ ユーザーに見える動作をテスト
|
||||
### PASS: ユーザーに見える動作をテスト
|
||||
```typescript
|
||||
// ユーザーが見るものをテストする
|
||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||
```
|
||||
|
||||
### ❌ テストが互いに依存
|
||||
### FAIL: テストが互いに依存
|
||||
```typescript
|
||||
// 前のテストに依存しない
|
||||
test('creates user', () => { /* ... */ })
|
||||
test('updates same user', () => { /* 前のテストが必要 */ })
|
||||
```
|
||||
|
||||
### ✅ 独立したテスト
|
||||
### PASS: 独立したテスト
|
||||
```typescript
|
||||
// 各テストでデータをセットアップ
|
||||
test('updates user', () => {
|
||||
|
||||
@@ -183,7 +183,7 @@ Artifacts generated:
|
||||
╔══════════════════════════════════════════════════════════════╗
|
||||
║ E2Eテスト結果 ║
|
||||
╠══════════════════════════════════════════════════════════════╣
|
||||
║ ステータス: ✅ 全テスト合格 ║
|
||||
║ ステータス: PASS: 全テスト合格 ║
|
||||
║ 合計: 3テスト ║
|
||||
║ 合格: 3 (100%) ║
|
||||
║ 失敗: 0 ║
|
||||
@@ -192,15 +192,15 @@ Artifacts generated:
|
||||
╚══════════════════════════════════════════════════════════════╝
|
||||
|
||||
アーティファクト:
|
||||
📸 スクリーンショット: 2ファイル
|
||||
📹 ビデオ: 0ファイル (失敗時のみ)
|
||||
🔍 トレース: 0ファイル (失敗時のみ)
|
||||
📊 HTMLレポート: playwright-report/index.html
|
||||
スクリーンショット: 2ファイル
|
||||
ビデオ: 0ファイル (失敗時のみ)
|
||||
トレース: 0ファイル (失敗時のみ)
|
||||
HTMLレポート: playwright-report/index.html
|
||||
|
||||
レポート表示: npx playwright show-report
|
||||
```
|
||||
|
||||
✅ E2E テストスイートは CI/CD 統合の準備ができました!
|
||||
PASS: E2E テストスイートは CI/CD 統合の準備ができました!
|
||||
|
||||
````
|
||||
|
||||
@@ -237,7 +237,7 @@ open artifacts/search-results.png
|
||||
テストが断続的に失敗する場合:
|
||||
|
||||
```
|
||||
⚠️ FLAKY TEST DETECTED: tests/e2e/markets/trade.spec.ts
|
||||
WARNING: FLAKY TEST DETECTED: tests/e2e/markets/trade.spec.ts
|
||||
|
||||
テストは10回中7回合格 (合格率70%)
|
||||
|
||||
@@ -257,10 +257,10 @@ open artifacts/search-results.png
|
||||
|
||||
デフォルトでは、テストは複数のブラウザで実行されます:
|
||||
|
||||
* ✅ Chromium(デスクトップ Chrome)
|
||||
* ✅ Firefox(デスクトップ)
|
||||
* ✅ WebKit(デスクトップ Safari)
|
||||
* ✅ Mobile Chrome(オプション)
|
||||
* PASS: Chromium(デスクトップ Chrome)
|
||||
* PASS: Firefox(デスクトップ)
|
||||
* PASS: WebKit(デスクトップ Safari)
|
||||
* PASS: Mobile Chrome(オプション)
|
||||
|
||||
`playwright.config.ts` で設定してブラウザを調整します。
|
||||
|
||||
@@ -288,7 +288,7 @@ CI パイプラインに追加:
|
||||
|
||||
PMX の場合、以下の E2E テストを優先:
|
||||
|
||||
**🔴 重大(常に成功する必要):**
|
||||
**重大(常に成功する必要):**
|
||||
|
||||
1. ユーザーがウォレットを接続できる
|
||||
2. ユーザーが市場をブラウズできる
|
||||
@@ -298,7 +298,7 @@ PMX の場合、以下の E2E テストを優先:
|
||||
6. 市場が正しく決済される
|
||||
7. ユーザーが資金を引き出せる
|
||||
|
||||
**🟡 重要:**
|
||||
**重要:**
|
||||
|
||||
1. 市場作成フロー
|
||||
2. ユーザープロフィール更新
|
||||
@@ -311,21 +311,21 @@ PMX の場合、以下の E2E テストを優先:
|
||||
|
||||
**すべき事:**
|
||||
|
||||
* ✅ 保守性を高めるためページオブジェクトモデルを使用します
|
||||
* ✅ セレクタとして data-testid 属性を使用します
|
||||
* ✅ 任意のタイムアウトではなく API レスポンスを待機
|
||||
* ✅ 重要なユーザージャーニーのエンドツーエンドテスト
|
||||
* ✅ main にマージする前にテストを実行
|
||||
* ✅ テスト失敗時にアーティファクトをレビュー
|
||||
* PASS: 保守性を高めるためページオブジェクトモデルを使用します
|
||||
* PASS: セレクタとして data-testid 属性を使用します
|
||||
* PASS: 任意のタイムアウトではなく API レスポンスを待機
|
||||
* PASS: 重要なユーザージャーニーのエンドツーエンドテスト
|
||||
* PASS: main にマージする前にテストを実行
|
||||
* PASS: テスト失敗時にアーティファクトをレビュー
|
||||
|
||||
**すべきでない事:**
|
||||
|
||||
* ❌ 不安定なセレクタを使用します(CSS クラスは変わる可能性)
|
||||
* ❌ 実装の詳細をテスト
|
||||
* ❌ 本番環境に対してテストを実行
|
||||
* ❌ 不安定なテストを無視
|
||||
* ❌ 失敗時にアーティファクトレビューをスキップ
|
||||
* ❌ E2E テストですべてのエッジケースをテスト(単体テストを使用します)
|
||||
* FAIL: 不安定なセレクタを使用します(CSS クラスは変わる可能性)
|
||||
* FAIL: 実装の詳細をテスト
|
||||
* FAIL: 本番環境に対してテストを実行
|
||||
* FAIL: 不安定なテストを無視
|
||||
* FAIL: 失敗時にアーティファクトレビューをスキップ
|
||||
* FAIL: E2E テストですべてのエッジケースをテスト(単体テストを使用します)
|
||||
|
||||
## 重要な注意事項
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ instinctsが分離の恩恵を受ける複雑な複数ステップのプロセ
|
||||
## 出力フォーマット
|
||||
|
||||
```
|
||||
🧬 進化分析
|
||||
進化分析
|
||||
==================
|
||||
|
||||
進化の準備ができた3つのクラスターを発見:
|
||||
|
||||
@@ -140,7 +140,7 @@ ok project/internal/handler 0.023s
|
||||
| 変更されたファイル | 2 |
|
||||
| 残存問題 | 0 |
|
||||
|
||||
ビルドステータス: ✅ 成功
|
||||
ビルドステータス: PASS: 成功
|
||||
```
|
||||
|
||||
## 修正される一般的なエラー
|
||||
|
||||
@@ -124,16 +124,16 @@ return fmt.Errorf("get user %s: %w", userID, err)
|
||||
- HIGH: 1
|
||||
- MEDIUM: 0
|
||||
|
||||
推奨: ❌ CRITICAL問題が修正されるまでマージをブロック
|
||||
推奨: FAIL: CRITICAL問題が修正されるまでマージをブロック
|
||||
```
|
||||
|
||||
## 承認基準
|
||||
|
||||
| ステータス | 条件 |
|
||||
|--------|-----------|
|
||||
| ✅ 承認 | CRITICALまたはHIGH問題なし |
|
||||
| ⚠️ 警告 | MEDIUM問題のみ(注意してマージ) |
|
||||
| ❌ ブロック | CRITICALまたはHIGH問題が発見された |
|
||||
| PASS: 承認 | CRITICALまたはHIGH問題なし |
|
||||
| WARNING: 警告 | MEDIUM問題のみ(注意してマージ) |
|
||||
| FAIL: ブロック | CRITICALまたはHIGH問題が発見された |
|
||||
|
||||
## 他のコマンドとの統合
|
||||
|
||||
|
||||
@@ -70,17 +70,17 @@ instincts:
|
||||
## プライバシーに関する考慮事項
|
||||
|
||||
エクスポートに含まれる内容:
|
||||
- ✅ トリガーパターン
|
||||
- ✅ アクション
|
||||
- ✅ 信頼度スコア
|
||||
- ✅ ドメイン
|
||||
- ✅ 観察回数
|
||||
- PASS: トリガーパターン
|
||||
- PASS: アクション
|
||||
- PASS: 信頼度スコア
|
||||
- PASS: ドメイン
|
||||
- PASS: 観察回数
|
||||
|
||||
エクスポートに含まれない内容:
|
||||
- ❌ 実際のコードスニペット
|
||||
- ❌ ファイルパス
|
||||
- ❌ セッション記録
|
||||
- ❌ 個人識別情報
|
||||
- FAIL: 実際のコードスニペット
|
||||
- FAIL: ファイルパス
|
||||
- FAIL: セッション記録
|
||||
- FAIL: 個人識別情報
|
||||
|
||||
## フラグ
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import <
|
||||
## インポートプロセス
|
||||
|
||||
```
|
||||
📥 instinctsをインポート中: team-instincts.yaml
|
||||
instinctsをインポート中: team-instincts.yaml
|
||||
================================================
|
||||
|
||||
12件のinstinctsが見つかりました。
|
||||
@@ -61,19 +61,19 @@ python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import <
|
||||
|
||||
## 重複instincts (3)
|
||||
類似のinstinctsが既に存在:
|
||||
⚠️ prefer-functional-style
|
||||
WARNING: prefer-functional-style
|
||||
ローカル: 信頼度0.8, 12回の観測
|
||||
インポート: 信頼度0.7
|
||||
→ ローカルを保持 (信頼度が高い)
|
||||
|
||||
⚠️ test-first-workflow
|
||||
WARNING: test-first-workflow
|
||||
ローカル: 信頼度0.75
|
||||
インポート: 信頼度0.9
|
||||
→ インポートに更新 (信頼度が高い)
|
||||
|
||||
## 競合instincts (1)
|
||||
ローカルのinstinctsと矛盾:
|
||||
❌ use-classes-for-services
|
||||
FAIL: use-classes-for-services
|
||||
競合: avoid-classes
|
||||
→ スキップ (手動解決が必要)
|
||||
|
||||
@@ -130,7 +130,7 @@ Skill Creatorからインポートする場合:
|
||||
|
||||
インポート後:
|
||||
```
|
||||
✅ インポート完了!
|
||||
PASS: インポート完了!
|
||||
|
||||
追加: 8件のinstincts
|
||||
更新: 1件のinstinct
|
||||
|
||||
@@ -39,7 +39,7 @@ python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status
|
||||
## 出力形式
|
||||
|
||||
```
|
||||
📊 instinctステータス
|
||||
instinctステータス
|
||||
==================
|
||||
|
||||
## コードスタイル (4 instincts)
|
||||
|
||||
@@ -204,9 +204,9 @@ Claudeの統合計画での欠落リスクを減らすために、両方のモ
|
||||
3. **太字テキスト**でプロンプトを出力(**保存された実際のファイルパスを使用する必要があります**):
|
||||
|
||||
---
|
||||
**計画が生成され、`.claude/plan/actual-feature-name.md`に保存されました**
|
||||
**計画が生成され、`.claude/plan/actual-feature-name.md`に保存されました**
|
||||
|
||||
**上記の計画をレビューしてください。以下のことができます:**
|
||||
**上記の計画をレビューしてください。以下のことができます:**
|
||||
- **計画を変更**: 調整が必要なことを教えてください、計画を更新します
|
||||
- **計画を実行**: 以下のコマンドを新しいセッションにコピー
|
||||
|
||||
@@ -215,7 +215,7 @@ Claudeの統合計画での欠落リスクを減らすために、両方のモ
|
||||
```
|
||||
---
|
||||
|
||||
**注意**: 上記の`actual-feature-name.md`は実際に保存されたファイル名で置き換える必要があります!
|
||||
**注意**: 上記の`actual-feature-name.md`は実際に保存されたファイル名で置き換える必要があります!
|
||||
|
||||
4. **現在のレスポンスを直ちに終了**(ここで停止。これ以上のツール呼び出しはありません。)
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ Agent:
|
||||
## 静的解析結果
|
||||
✓ ruff: 問題なし
|
||||
✓ mypy: エラーなし
|
||||
⚠️ black: 2ファイルが再フォーマット必要
|
||||
WARNING: black: 2ファイルが再フォーマット必要
|
||||
✓ bandit: セキュリティ問題なし
|
||||
|
||||
## 発見された問題
|
||||
@@ -155,7 +155,7 @@ with open("config.json") as f: # 良い
|
||||
- HIGH: 1
|
||||
- MEDIUM: 2
|
||||
|
||||
推奨: ❌ CRITICAL問題が修正されるまでマージをブロック
|
||||
推奨: FAIL: CRITICAL問題が修正されるまでマージをブロック
|
||||
|
||||
## フォーマット必要
|
||||
実行: `black app/routes/user.py app/services/auth.py`
|
||||
@@ -165,9 +165,9 @@ with open("config.json") as f: # 良い
|
||||
|
||||
| ステータス | 条件 |
|
||||
|--------|-----------|
|
||||
| ✅ 承認 | CRITICALまたはHIGH問題なし |
|
||||
| ⚠️ 警告 | MEDIUM問題のみ(注意してマージ) |
|
||||
| ❌ ブロック | CRITICALまたはHIGH問題が発見された |
|
||||
| PASS: 承認 | CRITICALまたはHIGH問題なし |
|
||||
| WARNING: 警告 | MEDIUM問題のみ(注意してマージ) |
|
||||
| FAIL: ブロック | CRITICALまたはHIGH問題が発見された |
|
||||
|
||||
## 他のコマンドとの統合
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ FAIL lib/liquidity.test.ts
|
||||
1 test failed, 0 passed
|
||||
```
|
||||
|
||||
✅ テストは期待通りに失敗しました。実装の準備ができました。
|
||||
PASS: テストは期待通りに失敗しました。実装の準備ができました。
|
||||
|
||||
## ステップ4: 最小限のコードを実装(GREEN)
|
||||
|
||||
@@ -179,7 +179,7 @@ PASS lib/liquidity.test.ts
|
||||
3 tests passed
|
||||
```
|
||||
|
||||
✅ すべてのテストが合格しました!
|
||||
PASS: すべてのテストが合格しました!
|
||||
|
||||
## ステップ6: リファクタリング(IMPROVE)
|
||||
|
||||
@@ -236,7 +236,7 @@ PASS lib/liquidity.test.ts
|
||||
3 tests passed
|
||||
```
|
||||
|
||||
✅ リファクタリング完了、テストはまだ合格しています!
|
||||
PASS: リファクタリング完了、テストはまだ合格しています!
|
||||
|
||||
## ステップ8: カバレッジの確認
|
||||
|
||||
@@ -247,29 +247,29 @@ File | % Stmts | % Branch | % Funcs | % Lines
|
||||
---------------|---------|----------|---------|--------
|
||||
liquidity.ts | 100 | 100 | 100 | 100
|
||||
|
||||
Coverage: 100% ✅ (Target: 80%)
|
||||
Coverage: 100% PASS: (Target: 80%)
|
||||
```
|
||||
|
||||
✅ TDDセッション完了!
|
||||
PASS: TDDセッション完了!
|
||||
```
|
||||
|
||||
## TDDベストプラクティス
|
||||
|
||||
**すべきこと:**
|
||||
- ✅ 実装の前にまずテストを書く
|
||||
- ✅ テストを実行し、実装前に失敗することを確認
|
||||
- ✅ テストに合格するための最小限のコードを書く
|
||||
- ✅ テストが緑色になってからのみリファクタリング
|
||||
- ✅ エッジケースとエラーシナリオを追加
|
||||
- ✅ 80%以上のカバレッジを目指す(重要なコードは100%)
|
||||
- PASS: 実装の前にまずテストを書く
|
||||
- PASS: テストを実行し、実装前に失敗することを確認
|
||||
- PASS: テストに合格するための最小限のコードを書く
|
||||
- PASS: テストが緑色になってからのみリファクタリング
|
||||
- PASS: エッジケースとエラーシナリオを追加
|
||||
- PASS: 80%以上のカバレッジを目指す(重要なコードは100%)
|
||||
|
||||
**してはいけないこと:**
|
||||
- ❌ テストの前に実装を書く
|
||||
- ❌ 各変更後のテスト実行をスキップ
|
||||
- ❌ 一度に多くのコードを書く
|
||||
- ❌ 失敗するテストを無視
|
||||
- ❌ 実装の詳細をテスト(動作をテスト)
|
||||
- ❌ すべてをモック化(統合テストを優先)
|
||||
- FAIL: テストの前に実装を書く
|
||||
- FAIL: 各変更後のテスト実行をスキップ
|
||||
- FAIL: 一度に多くのコードを書く
|
||||
- FAIL: 失敗するテストを無視
|
||||
- FAIL: 実装の詳細をテスト(動作をテスト)
|
||||
- FAIL: すべてをモック化(統合テストを優先)
|
||||
|
||||
## 含めるべきテストタイプ
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ description: Backend architecture patterns, API design, database optimization, a
|
||||
### RESTful API構造
|
||||
|
||||
```typescript
|
||||
// ✅ リソースベースのURL
|
||||
// PASS: リソースベースのURL
|
||||
GET /api/markets # リソースのリスト
|
||||
GET /api/markets/:id # 単一リソースの取得
|
||||
POST /api/markets # リソースの作成
|
||||
@@ -20,7 +20,7 @@ PUT /api/markets/:id # リソースの置換
|
||||
PATCH /api/markets/:id # リソースの更新
|
||||
DELETE /api/markets/:id # リソースの削除
|
||||
|
||||
// ✅ フィルタリング、ソート、ページネーション用のクエリパラメータ
|
||||
// PASS: フィルタリング、ソート、ページネーション用のクエリパラメータ
|
||||
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||
```
|
||||
|
||||
@@ -120,7 +120,7 @@ export default withAuth(async (req, res) => {
|
||||
### クエリ最適化
|
||||
|
||||
```typescript
|
||||
// ✅ 良い: 必要な列のみを選択
|
||||
// PASS: 良い: 必要な列のみを選択
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status, volume')
|
||||
@@ -128,7 +128,7 @@ const { data } = await supabase
|
||||
.order('volume', { ascending: false })
|
||||
.limit(10)
|
||||
|
||||
// ❌ 悪い: すべてを選択
|
||||
// FAIL: 悪い: すべてを選択
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -137,13 +137,13 @@ const { data } = await supabase
|
||||
### N+1クエリ防止
|
||||
|
||||
```typescript
|
||||
// ❌ 悪い: N+1クエリ問題
|
||||
// FAIL: 悪い: N+1クエリ問題
|
||||
const markets = await getMarkets()
|
||||
for (const market of markets) {
|
||||
market.creator = await getUser(market.creator_id) // Nクエリ
|
||||
}
|
||||
|
||||
// ✅ 良い: バッチフェッチ
|
||||
// PASS: 良い: バッチフェッチ
|
||||
const markets = await getMarkets()
|
||||
const creatorIds = markets.map(m => m.creator_id)
|
||||
const creators = await getUsers(creatorIds) // 1クエリ
|
||||
|
||||
@@ -86,7 +86,7 @@ ORDER BY hour DESC;
|
||||
### 効率的なフィルタリング
|
||||
|
||||
```sql
|
||||
-- ✅ 良い: インデックス列を最初に使用
|
||||
-- PASS: 良い: インデックス列を最初に使用
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE date >= '2025-01-01'
|
||||
@@ -95,7 +95,7 @@ WHERE date >= '2025-01-01'
|
||||
ORDER BY date DESC
|
||||
LIMIT 100;
|
||||
|
||||
-- ❌ 悪い: インデックスのない列を最初にフィルタリング
|
||||
-- FAIL: 悪い: インデックスのない列を最初にフィルタリング
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE volume > 1000
|
||||
@@ -106,7 +106,7 @@ WHERE volume > 1000
|
||||
### 集計
|
||||
|
||||
```sql
|
||||
-- ✅ 良い: ClickHouse固有の集計関数を使用
|
||||
-- PASS: 良い: ClickHouse固有の集計関数を使用
|
||||
SELECT
|
||||
toStartOfDay(created_at) AS day,
|
||||
market_id,
|
||||
@@ -119,7 +119,7 @@ WHERE created_at >= today() - INTERVAL 7 DAY
|
||||
GROUP BY day, market_id
|
||||
ORDER BY day DESC, total_volume DESC;
|
||||
|
||||
-- ✅ パーセンタイルにはquantileを使用(percentileより効率的)
|
||||
-- PASS: パーセンタイルにはquantileを使用(percentileより効率的)
|
||||
SELECT
|
||||
quantile(0.50)(trade_size) AS median,
|
||||
quantile(0.95)(trade_size) AS p95,
|
||||
@@ -162,7 +162,7 @@ const clickhouse = new ClickHouse({
|
||||
}
|
||||
})
|
||||
|
||||
// ✅ バッチ挿入(効率的)
|
||||
// PASS: バッチ挿入(効率的)
|
||||
async function bulkInsertTrades(trades: Trade[]) {
|
||||
const values = trades.map(trade => `(
|
||||
'${trade.id}',
|
||||
@@ -178,7 +178,7 @@ async function bulkInsertTrades(trades: Trade[]) {
|
||||
`).toPromise()
|
||||
}
|
||||
|
||||
// ❌ 個別挿入(低速)
|
||||
// FAIL: 個別挿入(低速)
|
||||
async function insertTrade(trade: Trade) {
|
||||
// ループ内でこれをしないでください!
|
||||
await clickhouse.query(`
|
||||
|
||||
@@ -42,12 +42,12 @@ description: TypeScript、JavaScript、React、Node.js開発のための汎用
|
||||
### 変数の命名
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Descriptive names
|
||||
// PASS: GOOD: Descriptive names
|
||||
const marketSearchQuery = 'election'
|
||||
const isUserAuthenticated = true
|
||||
const totalRevenue = 1000
|
||||
|
||||
// ❌ BAD: Unclear names
|
||||
// FAIL: BAD: Unclear names
|
||||
const q = 'election'
|
||||
const flag = true
|
||||
const x = 1000
|
||||
@@ -56,12 +56,12 @@ const x = 1000
|
||||
### 関数の命名
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Verb-noun pattern
|
||||
// PASS: GOOD: Verb-noun pattern
|
||||
async function fetchMarketData(marketId: string) { }
|
||||
function calculateSimilarity(a: number[], b: number[]) { }
|
||||
function isValidEmail(email: string): boolean { }
|
||||
|
||||
// ❌ BAD: Unclear or noun-only
|
||||
// FAIL: BAD: Unclear or noun-only
|
||||
async function market(id: string) { }
|
||||
function similarity(a, b) { }
|
||||
function email(e) { }
|
||||
@@ -70,7 +70,7 @@ function email(e) { }
|
||||
### 不変性パターン(重要)
|
||||
|
||||
```typescript
|
||||
// ✅ ALWAYS use spread operator
|
||||
// PASS: ALWAYS use spread operator
|
||||
const updatedUser = {
|
||||
...user,
|
||||
name: 'New Name'
|
||||
@@ -78,7 +78,7 @@ const updatedUser = {
|
||||
|
||||
const updatedArray = [...items, newItem]
|
||||
|
||||
// ❌ NEVER mutate directly
|
||||
// FAIL: NEVER mutate directly
|
||||
user.name = 'New Name' // BAD
|
||||
items.push(newItem) // BAD
|
||||
```
|
||||
@@ -86,7 +86,7 @@ items.push(newItem) // BAD
|
||||
### エラーハンドリング
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Comprehensive error handling
|
||||
// PASS: GOOD: Comprehensive error handling
|
||||
async function fetchData(url: string) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
@@ -102,7 +102,7 @@ async function fetchData(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ BAD: No error handling
|
||||
// FAIL: BAD: No error handling
|
||||
async function fetchData(url) {
|
||||
const response = await fetch(url)
|
||||
return response.json()
|
||||
@@ -112,14 +112,14 @@ async function fetchData(url) {
|
||||
### Async/Awaitベストプラクティス
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Parallel execution when possible
|
||||
// PASS: GOOD: Parallel execution when possible
|
||||
const [users, markets, stats] = await Promise.all([
|
||||
fetchUsers(),
|
||||
fetchMarkets(),
|
||||
fetchStats()
|
||||
])
|
||||
|
||||
// ❌ BAD: Sequential when unnecessary
|
||||
// FAIL: BAD: Sequential when unnecessary
|
||||
const users = await fetchUsers()
|
||||
const markets = await fetchMarkets()
|
||||
const stats = await fetchStats()
|
||||
@@ -128,7 +128,7 @@ const stats = await fetchStats()
|
||||
### 型安全性
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Proper types
|
||||
// PASS: GOOD: Proper types
|
||||
interface Market {
|
||||
id: string
|
||||
name: string
|
||||
@@ -140,7 +140,7 @@ function getMarket(id: string): Promise<Market> {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// ❌ BAD: Using 'any'
|
||||
// FAIL: BAD: Using 'any'
|
||||
function getMarket(id: any): Promise<any> {
|
||||
// Implementation
|
||||
}
|
||||
@@ -151,7 +151,7 @@ function getMarket(id: any): Promise<any> {
|
||||
### コンポーネント構造
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Functional component with types
|
||||
// PASS: GOOD: Functional component with types
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode
|
||||
onClick: () => void
|
||||
@@ -176,7 +176,7 @@ export function Button({
|
||||
)
|
||||
}
|
||||
|
||||
// ❌ BAD: No types, unclear structure
|
||||
// FAIL: BAD: No types, unclear structure
|
||||
export function Button(props) {
|
||||
return <button onClick={props.onClick}>{props.children}</button>
|
||||
}
|
||||
@@ -185,7 +185,7 @@ export function Button(props) {
|
||||
### カスタムフック
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Reusable custom hook
|
||||
// PASS: GOOD: Reusable custom hook
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||
|
||||
@@ -207,25 +207,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
|
||||
### 状態管理
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Proper state updates
|
||||
// PASS: GOOD: Proper state updates
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
// Functional update for state based on previous state
|
||||
setCount(prev => prev + 1)
|
||||
|
||||
// ❌ BAD: Direct state reference
|
||||
// FAIL: BAD: Direct state reference
|
||||
setCount(count + 1) // Can be stale in async scenarios
|
||||
```
|
||||
|
||||
### 条件付きレンダリング
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Clear conditional rendering
|
||||
// PASS: GOOD: Clear conditional rendering
|
||||
{isLoading && <Spinner />}
|
||||
{error && <ErrorMessage error={error} />}
|
||||
{data && <DataDisplay data={data} />}
|
||||
|
||||
// ❌ BAD: Ternary hell
|
||||
// FAIL: BAD: Ternary hell
|
||||
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
||||
```
|
||||
|
||||
@@ -248,7 +248,7 @@ GET /api/markets?status=active&limit=10&offset=0
|
||||
### レスポンス形式
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Consistent response structure
|
||||
// PASS: GOOD: Consistent response structure
|
||||
interface ApiResponse<T> {
|
||||
success: boolean
|
||||
data?: T
|
||||
@@ -279,7 +279,7 @@ return NextResponse.json({
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
// ✅ GOOD: Schema validation
|
||||
// PASS: GOOD: Schema validation
|
||||
const CreateMarketSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
description: z.string().min(1).max(2000),
|
||||
@@ -342,14 +342,14 @@ types/market.types.ts # 型定義は .types サフィックス付き cam
|
||||
### コメントを追加するタイミング
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Explain WHY, not WHAT
|
||||
// PASS: GOOD: Explain WHY, not WHAT
|
||||
// Use exponential backoff to avoid overwhelming the API during outages
|
||||
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
||||
|
||||
// Deliberately using mutation here for performance with large arrays
|
||||
items.push(newItem)
|
||||
|
||||
// ❌ BAD: Stating the obvious
|
||||
// FAIL: BAD: Stating the obvious
|
||||
// Increment counter by 1
|
||||
count++
|
||||
|
||||
@@ -389,12 +389,12 @@ export async function searchMarkets(
|
||||
```typescript
|
||||
import { useMemo, useCallback } from 'react'
|
||||
|
||||
// ✅ GOOD: Memoize expensive computations
|
||||
// PASS: GOOD: Memoize expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ GOOD: Memoize callbacks
|
||||
// PASS: GOOD: Memoize callbacks
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
@@ -405,7 +405,7 @@ const handleSearch = useCallback((query: string) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ GOOD: Lazy load heavy components
|
||||
// PASS: GOOD: Lazy load heavy components
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
|
||||
export function Dashboard() {
|
||||
@@ -420,13 +420,13 @@ export function Dashboard() {
|
||||
### データベースクエリ
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Select only needed columns
|
||||
// PASS: GOOD: Select only needed columns
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status')
|
||||
.limit(10)
|
||||
|
||||
// ❌ BAD: Select everything
|
||||
// FAIL: BAD: Select everything
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -453,12 +453,12 @@ test('calculates similarity correctly', () => {
|
||||
### テストの命名
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Descriptive test names
|
||||
// PASS: GOOD: Descriptive test names
|
||||
test('returns empty array when no markets match query', () => { })
|
||||
test('throws error when OpenAI API key is missing', () => { })
|
||||
test('falls back to substring search when Redis unavailable', () => { })
|
||||
|
||||
// ❌ BAD: Vague test names
|
||||
// FAIL: BAD: Vague test names
|
||||
test('works', () => { })
|
||||
test('test search', () => { })
|
||||
```
|
||||
@@ -470,12 +470,12 @@ test('test search', () => { })
|
||||
### 1. 長い関数
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Function > 50 lines
|
||||
// FAIL: BAD: Function > 50 lines
|
||||
function processMarketData() {
|
||||
// 100 lines of code
|
||||
}
|
||||
|
||||
// ✅ GOOD: Split into smaller functions
|
||||
// PASS: GOOD: Split into smaller functions
|
||||
function processMarketData() {
|
||||
const validated = validateData()
|
||||
const transformed = transformData(validated)
|
||||
@@ -486,7 +486,7 @@ function processMarketData() {
|
||||
### 2. 深いネスト
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: 5+ levels of nesting
|
||||
// FAIL: BAD: 5+ levels of nesting
|
||||
if (user) {
|
||||
if (user.isAdmin) {
|
||||
if (market) {
|
||||
@@ -499,7 +499,7 @@ if (user) {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ GOOD: Early returns
|
||||
// PASS: GOOD: Early returns
|
||||
if (!user) return
|
||||
if (!user.isAdmin) return
|
||||
if (!market) return
|
||||
@@ -512,11 +512,11 @@ if (!hasPermission) return
|
||||
### 3. マジックナンバー
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Unexplained numbers
|
||||
// FAIL: BAD: Unexplained numbers
|
||||
if (retryCount > 3) { }
|
||||
setTimeout(callback, 500)
|
||||
|
||||
// ✅ GOOD: Named constants
|
||||
// PASS: GOOD: Named constants
|
||||
const MAX_RETRIES = 3
|
||||
const DEBOUNCE_DELAY_MS = 500
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ DJANGO 検証レポート
|
||||
✓ ハードコードされたシークレットなし
|
||||
✓ マイグレーションが含まれる
|
||||
|
||||
推奨: ⚠️ デプロイ前にpip-auditの脆弱性を修正してください
|
||||
推奨: WARNING: デプロイ前にpip-auditの脆弱性を修正してください
|
||||
|
||||
次のステップ:
|
||||
1. 脆弱な依存関係を更新
|
||||
|
||||
@@ -12,7 +12,7 @@ React、Next.js、高性能ユーザーインターフェースのためのモ
|
||||
### 継承よりコンポジション
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Component composition
|
||||
// PASS: GOOD: Component composition
|
||||
interface CardProps {
|
||||
children: React.ReactNode
|
||||
variant?: 'default' | 'outlined'
|
||||
@@ -283,17 +283,17 @@ export function useMarkets() {
|
||||
### メモ化
|
||||
|
||||
```typescript
|
||||
// ✅ useMemo for expensive computations
|
||||
// PASS: useMemo for expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ useCallback for functions passed to children
|
||||
// PASS: useCallback for functions passed to children
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
|
||||
// ✅ React.memo for pure components
|
||||
// PASS: React.memo for pure components
|
||||
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
return (
|
||||
<div className="market-card">
|
||||
@@ -309,7 +309,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ Lazy load heavy components
|
||||
// PASS: Lazy load heavy components
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
||||
|
||||
@@ -504,7 +504,7 @@ export class ErrorBoundary extends React.Component<
|
||||
```typescript
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
|
||||
// ✅ List animations
|
||||
// PASS: List animations
|
||||
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
@@ -523,7 +523,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ Modal animations
|
||||
// PASS: Modal animations
|
||||
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
|
||||
@@ -27,12 +27,12 @@ description: サブエージェントのコンテキスト問題を解決する
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ DISPATCH │─────▶│ EVALUATE │ │
|
||||
│ │ DISPATCH │─────│ EVALUATE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ ▲ │ │
|
||||
│ │ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ LOOP │◀─────│ REFINE │ │
|
||||
│ │ LOOP │─────│ REFINE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ 最大3サイクル、その後続行 │
|
||||
|
||||
@@ -17,22 +17,22 @@ Spring Bootサービスにおける読みやすく保守可能なJava(17+)コー
|
||||
## 命名
|
||||
|
||||
```java
|
||||
// ✅ クラス/レコード: PascalCase
|
||||
// PASS: クラス/レコード: PascalCase
|
||||
public class MarketService {}
|
||||
public record Money(BigDecimal amount, Currency currency) {}
|
||||
|
||||
// ✅ メソッド/フィールド: camelCase
|
||||
// PASS: メソッド/フィールド: camelCase
|
||||
private final MarketRepository marketRepository;
|
||||
public Market findBySlug(String slug) {}
|
||||
|
||||
// ✅ 定数: UPPER_SNAKE_CASE
|
||||
// PASS: 定数: UPPER_SNAKE_CASE
|
||||
private static final int MAX_PAGE_SIZE = 100;
|
||||
```
|
||||
|
||||
## 不変性
|
||||
|
||||
```java
|
||||
// ✅ recordとfinalフィールドを優先
|
||||
// PASS: recordとfinalフィールドを優先
|
||||
public record MarketDto(Long id, String name, MarketStatus status) {}
|
||||
|
||||
public class Market {
|
||||
@@ -45,10 +45,10 @@ public class Market {
|
||||
## Optionalの使用
|
||||
|
||||
```java
|
||||
// ✅ find*メソッドからOptionalを返す
|
||||
// PASS: find*メソッドからOptionalを返す
|
||||
Optional<Market> market = marketRepository.findBySlug(slug);
|
||||
|
||||
// ✅ get()の代わりにmap/flatMapを使用
|
||||
// PASS: get()の代わりにmap/flatMapを使用
|
||||
return market
|
||||
.map(MarketResponse::from)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
|
||||
@@ -57,13 +57,13 @@ return market
|
||||
## ストリームのベストプラクティス
|
||||
|
||||
```java
|
||||
// ✅ 変換にストリームを使用し、パイプラインを短く保つ
|
||||
// PASS: 変換にストリームを使用し、パイプラインを短く保つ
|
||||
List<String> names = markets.stream()
|
||||
.map(Market::name)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
// ❌ 複雑なネストされたストリームを避ける; 明確性のためにループを優先
|
||||
// FAIL: 複雑なネストされたストリームを避ける; 明確性のためにループを優先
|
||||
```
|
||||
|
||||
## 例外
|
||||
|
||||
@@ -21,13 +21,13 @@ description: 認証の追加、ユーザー入力の処理、シークレット
|
||||
|
||||
### 1. シークレット管理
|
||||
|
||||
#### ❌ 絶対にしないこと
|
||||
#### FAIL: 絶対にしないこと
|
||||
```typescript
|
||||
const apiKey = "sk-proj-xxxxx" // ハードコードされたシークレット
|
||||
const dbPassword = "password123" // ソースコード内
|
||||
```
|
||||
|
||||
#### ✅ 常にすること
|
||||
#### PASS: 常にすること
|
||||
```typescript
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
const dbUrl = process.env.DATABASE_URL
|
||||
@@ -107,14 +107,14 @@ function validateFileUpload(file: File) {
|
||||
|
||||
### 3. SQLインジェクション防止
|
||||
|
||||
#### ❌ 絶対にSQLを連結しない
|
||||
#### FAIL: 絶対にSQLを連結しない
|
||||
```typescript
|
||||
// 危険 - SQLインジェクションの脆弱性
|
||||
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
||||
await db.query(query)
|
||||
```
|
||||
|
||||
#### ✅ 常にパラメータ化されたクエリを使用
|
||||
#### PASS: 常にパラメータ化されたクエリを使用
|
||||
```typescript
|
||||
// 安全 - パラメータ化されたクエリ
|
||||
const { data } = await supabase
|
||||
@@ -139,10 +139,10 @@ await db.query(
|
||||
|
||||
#### JWTトークン処理
|
||||
```typescript
|
||||
// ❌ 誤り:localStorage(XSSに脆弱)
|
||||
// FAIL: 誤り:localStorage(XSSに脆弱)
|
||||
localStorage.setItem('token', token)
|
||||
|
||||
// ✅ 正解:httpOnly Cookie
|
||||
// PASS: 正解:httpOnly Cookie
|
||||
res.setHeader('Set-Cookie',
|
||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
||||
```
|
||||
@@ -299,18 +299,18 @@ app.use('/api/search', searchLimiter)
|
||||
|
||||
#### ロギング
|
||||
```typescript
|
||||
// ❌ 誤り:機密データをログに記録
|
||||
// FAIL: 誤り:機密データをログに記録
|
||||
console.log('User login:', { email, password })
|
||||
console.log('Payment:', { cardNumber, cvv })
|
||||
|
||||
// ✅ 正解:機密データを編集
|
||||
// PASS: 正解:機密データを編集
|
||||
console.log('User login:', { email, userId })
|
||||
console.log('Payment:', { last4: card.last4, userId })
|
||||
```
|
||||
|
||||
#### エラーメッセージ
|
||||
```typescript
|
||||
// ❌ 誤り:内部詳細を露出
|
||||
// FAIL: 誤り:内部詳細を露出
|
||||
catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, stack: error.stack },
|
||||
@@ -318,7 +318,7 @@ catch (error) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ 正解:一般的なエラーメッセージ
|
||||
// PASS: 正解:一般的なエラーメッセージ
|
||||
catch (error) {
|
||||
console.error('Internal error:', error)
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#### 最小権限の原則
|
||||
|
||||
```yaml
|
||||
# ✅ 正解:最小限の権限
|
||||
# PASS: 正解:最小限の権限
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:GetObject # 読み取りアクセスのみ
|
||||
@@ -32,7 +32,7 @@ iam_role:
|
||||
resources:
|
||||
- arn:aws:s3:::my-bucket/* # 特定のバケットのみ
|
||||
|
||||
# ❌ 誤り:過度に広範な権限
|
||||
# FAIL: 誤り:過度に広範な権限
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:* # すべてのS3アクション
|
||||
@@ -65,14 +65,14 @@ aws iam enable-mfa-device \
|
||||
#### クラウドシークレットマネージャー
|
||||
|
||||
```typescript
|
||||
// ✅ 正解:クラウドシークレットマネージャーを使用
|
||||
// PASS: 正解:クラウドシークレットマネージャーを使用
|
||||
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
|
||||
|
||||
const client = new SecretsManager({ region: 'us-east-1' });
|
||||
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
|
||||
const apiKey = JSON.parse(secret.SecretString).key;
|
||||
|
||||
// ❌ 誤り:ハードコードまたは環境変数のみ
|
||||
// FAIL: 誤り:ハードコードまたは環境変数のみ
|
||||
const apiKey = process.env.API_KEY; // ローテーションされず、監査されない
|
||||
```
|
||||
|
||||
@@ -99,7 +99,7 @@ aws secretsmanager rotate-secret \
|
||||
#### VPCとファイアウォール設定
|
||||
|
||||
```terraform
|
||||
# ✅ 正解:制限されたセキュリティグループ
|
||||
# PASS: 正解:制限されたセキュリティグループ
|
||||
resource "aws_security_group" "app" {
|
||||
name = "app-sg"
|
||||
|
||||
@@ -118,7 +118,7 @@ resource "aws_security_group" "app" {
|
||||
}
|
||||
}
|
||||
|
||||
# ❌ 誤り:インターネットに公開
|
||||
# FAIL: 誤り:インターネットに公開
|
||||
resource "aws_security_group" "bad" {
|
||||
ingress {
|
||||
from_port = 0
|
||||
@@ -142,7 +142,7 @@ resource "aws_security_group" "bad" {
|
||||
#### CloudWatch/ロギング設定
|
||||
|
||||
```typescript
|
||||
// ✅ 正解:包括的なロギング
|
||||
// PASS: 正解:包括的なロギング
|
||||
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
||||
|
||||
const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
@@ -177,7 +177,7 @@ const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
#### 安全なパイプライン設定
|
||||
|
||||
```yaml
|
||||
# ✅ 正解:安全なGitHub Actionsワークフロー
|
||||
# PASS: 正解:安全なGitHub Actionsワークフロー
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
@@ -237,7 +237,7 @@ jobs:
|
||||
#### Cloudflareセキュリティ設定
|
||||
|
||||
```typescript
|
||||
// ✅ 正解:セキュリティヘッダー付きCloudflare Workers
|
||||
// PASS: 正解:セキュリティヘッダー付きCloudflare Workers
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const response = await fetch(request);
|
||||
@@ -281,7 +281,7 @@ export default {
|
||||
#### 自動バックアップ
|
||||
|
||||
```terraform
|
||||
# ✅ 正解:自動RDSバックアップ
|
||||
# PASS: 正解:自動RDSバックアップ
|
||||
resource "aws_db_instance" "main" {
|
||||
allocated_storage = 20
|
||||
engine = "postgres"
|
||||
@@ -327,10 +327,10 @@ resource "aws_db_instance" "main" {
|
||||
### S3バケットの露出
|
||||
|
||||
```bash
|
||||
# ❌ 誤り:公開バケット
|
||||
# FAIL: 誤り:公開バケット
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
|
||||
|
||||
# ✅ 正解:特定のアクセス付きプライベートバケット
|
||||
# PASS: 正解:特定のアクセス付きプライベートバケット
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl private
|
||||
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
```
|
||||
@@ -338,12 +338,12 @@ aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
### RDS公開アクセス
|
||||
|
||||
```terraform
|
||||
# ❌ 誤り
|
||||
# FAIL: 誤り
|
||||
resource "aws_db_instance" "bad" {
|
||||
publicly_accessible = true # 絶対にこれをしない!
|
||||
}
|
||||
|
||||
# ✅ 正解
|
||||
# PASS: 正解
|
||||
resource "aws_db_instance" "good" {
|
||||
publicly_accessible = false
|
||||
vpc_security_group_ids = [aws_security_group.db.id]
|
||||
|
||||
@@ -313,39 +313,39 @@ npm run test:coverage
|
||||
|
||||
## 避けるべき一般的なテストの誤り
|
||||
|
||||
### ❌ 誤り:実装の詳細をテスト
|
||||
### FAIL: 誤り:実装の詳細をテスト
|
||||
```typescript
|
||||
// 内部状態をテストしない
|
||||
expect(component.state.count).toBe(5)
|
||||
```
|
||||
|
||||
### ✅ 正解:ユーザーに見える動作をテスト
|
||||
### PASS: 正解:ユーザーに見える動作をテスト
|
||||
```typescript
|
||||
// ユーザーが見るものをテスト
|
||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||
```
|
||||
|
||||
### ❌ 誤り:脆弱なセレクタ
|
||||
### FAIL: 誤り:脆弱なセレクタ
|
||||
```typescript
|
||||
// 簡単に壊れる
|
||||
await page.click('.css-class-xyz')
|
||||
```
|
||||
|
||||
### ✅ 正解:セマンティックセレクタ
|
||||
### PASS: 正解:セマンティックセレクタ
|
||||
```typescript
|
||||
// 変更に強い
|
||||
await page.click('button:has-text("Submit")')
|
||||
await page.click('[data-testid="submit-button"]')
|
||||
```
|
||||
|
||||
### ❌ 誤り:テストの分離なし
|
||||
### FAIL: 誤り:テストの分離なし
|
||||
```typescript
|
||||
// テストが互いに依存
|
||||
test('creates user', () => { /* ... */ })
|
||||
test('updates same user', () => { /* 前のテストに依存 */ })
|
||||
```
|
||||
|
||||
### ✅ 正解:独立したテスト
|
||||
### PASS: 正解:独立したテスト
|
||||
```typescript
|
||||
// 各テストが独自のデータをセットアップ
|
||||
test('creates user', () => {
|
||||
|
||||
Reference in New Issue
Block a user