code-deduplication について
Claude向けのスキルで、機能別の `CODE_INDEX.md` を確認・更新し、新機能を追加する前に既存実装を調べて意味上の重複を抑えます。ファイルや関数の文書化規則と、重複を監査する手順も示します。
主な機能
- 01機能別コードインデックス
- 02実装前の確認フロー
- 03ファイル・関数の文書化
- 04定期的な重複監査
- 05任意のベクトル検索
活用シーン
- 01機能実装前に既存実装を確認
- 02コードベースのユーティリティ重複を監査
- 03変更に合わせて検索可能な索引を維持
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ alinaqi/ maggy --skill code-deduplicationインストール後、Claude Code で/code-deduplicationを入力して読み込まれたか確認します。
詳細
コード重複防止スキル
目的: 意味的な重複とコードの肥大化を防ぎます。機能インデックスを維持し、新しいものを書く前に、Claude が既存の機能を常に把握できるようにします。
基本理念
┌─────────────────────────────────────────────────────────────────┐
│ CHECK BEFORE YOU WRITE │
│ ───────────────────────────────────────────────────────────── │
│ AI doesn't copy/paste - it reimplements. │
│ The problem isn't duplicate code, it's duplicate PURPOSE. │
│ │
│ Before writing ANY new function: │
│ 1. Check CODE_INDEX.md for existing capabilities │
│ 2. Search codebase for similar functionality │
│ 3. Extend existing code if possible │
│ 4. Only create new if nothing suitable exists │
├─────────────────────────────────────────────────────────────────┤
│ AFTER WRITING: Update the index immediately. │
│ PERIODICALLY: Run /audit-duplicates to catch overlap. │
└─────────────────────────────────────────────────────────────────┘
コードインデックスの構成
プロジェクトのルートに CODE_INDEX.md を維持し、ファイルの場所ではなく機能別に整理します。
# Code Index
*Last updated: [timestamp]*
*Run `/update-code-index` to regenerate*
## Quick Reference
| Category | Count | Location |
|----------|-------|----------|
| Date/Time | 5 functions | src/utils/dates.ts |
| Validation | 8 functions | src/utils/validate.ts |
| API Clients | 12 functions | src/api/*.ts |
| Auth | 6 functions | src/auth/*.ts |
---
## Date/Time Operations
| Function | Location | Does What | Params |
|----------|----------|-----------|--------|
| `formatDate()` | utils/dates.ts:15 | Formats Date → "Jan 15, 2024" | `(date: Date, format?: string)` |
| `formatRelative()` | utils/dates.ts:32 | Formats Date → "2 days ago" | `(date: Date)` |
| `parseDate()` | utils/dates.ts:48 | Parses string → Date | `(str: string, format?: string)` |
| `isExpired()` | auth/tokens.ts:22 | Checks if timestamp past now | `(timestamp: number)` |
| `addDays()` | utils/dates.ts:61 | Adds days to date | `(date: Date, days: number)` |
---
## Validation
| Function | Location | Does What | Params |
|----------|----------|-----------|--------|
| `isEmail()` | utils/validate.ts:10 | Validates email format | `(email: string)` |
| `isPhone()` | utils/validate.ts:25 | Validates phone with country | `(phone: string, country?: string)` |
| `isURL()` | utils/validate.ts:42 | Validates URL format | `(url: string)` |
| `isUUID()` | utils/validate.ts:55 | Validates UUID v4 | `(id: string)` |
| `sanitizeHTML()` | utils/sanitize.ts:12 | Strips XSS from input | `(html: string)` |
| `sanitizeSQL()` | utils/sanitize.ts:28 | Escapes SQL special chars | `(input: string)` |
---
## String Operations
| Function | Location | Does What | Params |
|----------|----------|-----------|--------|
| `slugify()` | utils/strings.ts:8 | Converts to URL slug | `(str: string)` |
| `truncate()` | utils/strings.ts:20 | Truncates with ellipsis | `(str: string, len: number)` |
| `capitalize()` | utils/strings.ts:32 | Capitalizes first letter | `(str: string)` |
| `pluralize()` | utils/strings.ts:40 | Adds s/es correctly | `(word: string, count: number)` |
---
## API Clients
| Function | Location | Does What | Returns |
|----------|----------|-----------|---------|
| `fetchUser()` | api/users.ts:15 | GET /users/:id | `Promise<User>` |
| `fetchUsers()` | api/users.ts:28 | GET /users with pagination | `Promise<User[]>` |
| `createUser()` | api/users.ts:45 | POST /users | `Promise<User>` |
| `updateUser()` | api/users.ts:62 | PATCH /users/:id | `Promise<User>` |
| `deleteUser()` | api/users.ts:78 | DELETE /users/:id | `Promise<void>` |
---
## Error Handling
| Function/Class | Location | Does What |
|----------------|----------|-----------|
| `AppError` | utils/errors.ts:5 | Base error class with code |
| `ValidationError` | utils/errors.ts:20 | Input validation failures |
| `NotFoundError` | utils/errors.ts:32 | Resource not found |
| `handleAsync()` | utils/errors.ts:45 | Wraps async route handlers |
| `errorMiddleware()` | middleware/error.ts:10 | Express error handler |
---
## Hooks (React)
| Hook | Location | Does What |
|------|----------|-----------|
| `useAuth()` | hooks/useAuth.ts | Auth state + login/logout |
| `useUser()` | hooks/useUser.ts | Current user data |
| `useDebounce()` | hooks/useDebounce.ts | Debounces value changes |
| `useLocalStorage()` | hooks/useLocalStorage.ts | Persisted state |
| `useFetch()` | hooks/useFetch.ts | Data fetching with loading/error |
---
## Components (React)
| Component | Location | Does What |
|-----------|----------|-----------|
| `Button` | components/Button.tsx | Styled button with variants |
| `Input` | components/Input.tsx | Form input with validation |
| `Modal` | components/Modal.tsx | Dialog overlay |
| `Toast` | components/Toast.tsx | Notification popup |
| `Spinner` | components/Spinner.tsx | Loading indicator |
ファイルヘッダーの形式
すべてのファイルに概要ヘッダーを付けます。
TypeScript/JavaScript
/**
* @file User authentication utilities
* @description Handles login, logout, session management, and token refresh.
*
* Key exports:
* - login(email, password) - Authenticates user, returns tokens
* - logout() - Clears session and tokens
* - refreshToken() - Gets new access token
* - validateSession() - Checks if session is valid
*
* @see src/api/auth.ts for API endpoints
* @see src/hooks/useAuth.ts for React hook
*/
import { ... } from '...';
Python
"""
User authentication utilities.
Handles login, logout, session management, and token refresh.
Key exports:
- login(email, password) - Authenticates user, returns tokens
- logout() - Clears session and tokens
- refresh_token() - Gets new access token
- validate_session() - Checks if session is valid
See Also:
- src/api/auth.py for API endpoints
- src/services/user.py for user operations
"""
from typing import ...
関数のドキュメント
すべての関数に1行の概要を付けます。
TypeScript
/**
* Formats a date into a human-readable relative string.
* Examples: "2 minutes ago", "yesterday", "3 months ago"
*/
export function formatRelative(date: Date): string {
// ...
}
/**
* Validates email format and checks for disposable domains.
* Returns true for valid non-disposable emails.
*/
export function isValidEmail(email: string): boolean {
// ...
}
Python
def format_relative(date: datetime) -> str:
"""Formats a date into a human-readable relative string.
Examples: "2 minutes ago", "yesterday", "3 months ago"
"""
...
def is_valid_email(email: str) -> bool:
"""Validates email format and checks for disposable domains.
Returns True for valid non-disposable emails.
"""
...
作成前の確認プロセス
新しい関数を作成する前に必ず行うこと
┌─────────────────────────────────────────────────────────────────┐
│ BEFORE WRITING NEW CODE │
│ ───────────────────────────────────────────────────────────── │
│ │
│ 1. DESCRIBE what you need in plain English │
│ "I need to format a date as relative time" │
│ │
│ 2. CHECK CODE_INDEX.md │
│ Search for: date, time, format, relative │
│ → Found: formatRelative() in utils/dates.ts │
│ │
│ 3. EVALUATE if existing code works │
│ - Does it do what I need? → Use it │
│ - Close but not quite? → Extend it │
│ - Nothing suitable? → Create new, update index │
│ │
│ 4. If extending, check for breaking changes │
│ - Add optional params, don't change existing behavior │
│ - Update tests for new functionality │
└─────────────────────────────────────────────────────────────────┘
判断フロー
Need new functionality
│
▼
Check CODE_INDEX.md for similar
│
├─► Found exact match ──────► USE IT
│
├─► Found similar ──────────► Can it be extended?
│ │
│ ┌──────────────┴──────────────┐
│ ▼ ▼
│ Yes: Extend No: Create new
│ (add params) (update index)
│
└─► Nothing found ──────────► Create new (update index)
よくある重複パターン
パターン 1:ユーティリティ関数の再実装
❌ 悪い例: isEmail() があるのに validateEmail() を作成する
// DON'T: This already exists as isEmail()
function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
✅ 良い例: まずインデックスを確認し、既存のものを使う
import { isEmail } from '@/utils/validate';
if (isEmail(userInput)) { ... }
パターン 2:わずかに異なるバージョン
❌ 悪い例: わずかな違いしかない日付フォーマッターを複数作る
// In file A
function formatDate(d: Date) { return d.toLocaleDateString(); }
// In file B
function displayDate(d: Date) { return d.toLocaleDateString('en-US'); }
// In file C
function showDate(d: Date) { return d.toLocaleDateString('en-US', { month: 'short' }); }
✅ 良い例: オプションを指定できる関数を1つ作る
// utils/dates.ts
function formatDate(d: Date, options?: { locale?: string; format?: 'short' | 'long' }) {
const locale = options?.locale ?? 'en-US';
const formatOpts = options?.format === 'short'
? { month: 'short', day: 'numeric' }
: { month: 'long', day: 'numeric', year: 'numeric' };
return d.toLocaleDateString(locale, formatOpts);
}
パターン 3:抽出すべきインラインロジック
❌ 悪い例: 同じバリデーションロジックが複数のファイルに散在する
// In signup.ts
if (!email || !email.includes('@') || email.length < 5) { ... }
// In profile.ts
if (!email || !email.includes('@') || email.length < 5) { ... }
// In invite.ts
if (!email || !email.includes('@') || email.length < 5) { ... }
✅ 良い例: 1か所に抽出し、すべての場所でインポートする
// utils/validate.ts
export const isEmail = (email: string) =>
email && email.includes('@') && email.length >= 5;
// Everywhere else
import { isEmail } from '@/utils/validate';
if (!isEmail(email)) { ... }
定期監査
意味的な重複を見つけるため、定期的に /audit-duplicates を実行します。
監査チェックリスト
- ユーティリティ関数:似た処理を行う関数はありませんか?
- API 呼び出し:同じデータを取得する方法が複数ありませんか?
- バリデーション:インラインのバリデーションロジックが散在していませんか?
- エラー処理:エラー処理のパターンに一貫性がありませんか?
- コンポーネント:統合できる類似の UI コンポーネントはありませんか?
- フック:ロジックが重複するカスタムフックはありませんか?
監査結果の形式
## Duplicate Audit - [DATE]
### 🔴 High Priority (Merge These)
1. **Date formatting** - 3 similar functions found
- `formatDate()` in utils/dates.ts
- `displayDate()` in components/Header.tsx
- `showDate()` in pages/Profile.tsx
- **Action:** Consolidate into utils/dates.ts
2. **Email validation** - Inline logic in 5 files
- signup.ts:42
- profile.ts:28
- invite.ts:15
- settings.ts:67
- admin.ts:33
- **Action:** Extract to utils/validate.ts
### 🟡 Medium Priority (Consider Merging)
1. **User fetching** - 2 different patterns
- `fetchUser()` in api/users.ts
- `getUser()` in services/user.ts
- **Action:** Decide on one pattern
### 🟢 Low Priority (Monitor)
1. **Button components** - 3 variants exist
- May be intentional for different use cases
- **Action:** Document the differences
ベクトル DB との統合(任意)
大規模なコードベース(ファイル数 100 以上)では、ベクトル検索を追加します。
ChromaDB を使ったセットアップ
# scripts/index_codebase.py
import chromadb
from chromadb.utils import embedding_functions
# Initialize
client = chromadb.PersistentClient(path="./.chroma")
ef = embedding_functions.DefaultEmbeddingFunction()
collection = client.get_or_create_collection("code_index", embedding_function=ef)
# Index a function
collection.add(
documents=["Formats a date into human-readable relative string like '2 days ago'"],
metadatas=[{"function": "formatRelative", "file": "utils/dates.ts", "line": 32}],
ids=["formatRelative"]
)
# Search before writing
results = collection.query(
query_texts=["format date as relative time"],
n_results=5
)
# Returns: formatRelative in utils/dates.ts - 0.92 similarity
LanceDB を使ったセットアップ(より軽量)
# scripts/index_codebase.py
import lancedb
db = lancedb.connect("./.lancedb")
# Create table
data = [
{"function": "formatRelative", "file": "utils/dates.ts", "description": "Formats date as relative time"},
{"function": "isEmail", "file": "utils/validate.ts", "description": "Validates email format"},
]
table = db.create_table("code_index", data)
# Search
results = table.search("validate email address").limit(5).to_list()
ベクトル DB を使用するタイミング
| コードベースの規模 | 推奨事項 |
|---|---|
| 50 ファイル未満 | Markdown インデックスのみ |
| 50~200 ファイル | Markdown と定期監査 |
| 200 ファイル以上 | ベクトル DB を追加 |
| 500 ファイル以上 | ベクトル DB が必須 |
Claude への指示
セッション開始時
CODE_INDEX.mdがあれば読みます- 利用できるカテゴリと主要な関数を確認します
- セッション中はこの情報を保持します
新しいコードを書く前
- いったん立ち止まって確認します:「これに似たものは既にありますか?」
- CODE_INDEX.md で似た機能を検索します
- 判断がつかない場合は、コードベースを検索します:
grep -r "functionName\|similar_term" src/ - 適切なものが存在しないと確認できた場合にのみ、新しく作成します
新しいコードを書いた後
- すぐに CODE_INDEX.md を更新します
- 新しいファイルの場合はファイルヘッダーを追加します
- 関数の docstring を追加します
- インデックスの更新をコードと一緒にコミットします
ユーザーが「X の機能を追加して」と言った場合
Before implementing, let me check if we already have something similar...
[Checks CODE_INDEX.md]
Found: `existingFunction()` in utils/file.ts does something similar.
Options:
1. Use existing function as-is
2. Extend it with new capability
3. Create new (if truly different use case)
Which approach would you prefer?
クイックリファレンス
インデックスの更新コマンド
/update-code-index
監査コマンド
/audit-duplicates
ファイルヘッダーのテンプレート
/**
* @file [Short description]
* @description [What this file does]
*
* Key exports:
* - function1() - [what it does]
* - function2() - [what it does]
*/
関数のテンプレート
/**
* [One line description of what it does]
*/
export function name(params): ReturnType {
インデックスエントリーのテンプレート
| `functionName()` | path/file.ts:line | Does what in plain English | `(params)` |
コメント
Markdown 対応