## 概要
`resources/` ディレクトリを `builtins/` にリネームし、用途を明確化。同時に export-cc コマンドを拡張して全リソースをコピーするように修正する。
---
## タスク一覧
### 1. ディレクトリリネーム(優先度: 高)
| 変更前 | 変更後 |
|--------|--------|
| `resources/` | `builtins/` |
| `resources/global/{lang}/` | `builtins/{lang}/`(global/ 階層を除去) |
| `resources/project/` | `builtins/project/` |
| `resources/skill/` | `builtins/skill/` |
### 2. 不要ファイル削除(優先度: 高)
- `builtins/{lang}/prompts/` を削除
- 対象: `interactive-system.md`, `interactive-summary.md`
- 理由: コードから未参照、実体は `src/shared/prompts/`
### 3. コード修正 — パス参照(優先度: 高)
`resources` → `builtins`、`global/{lang}` → `{lang}` に更新:
| ファイル | 修正内容 |
|----------|----------|
| `src/infra/resources/index.ts` | `getResourcesDir()`, `getGlobalResourcesDir()`, `getLanguageResourcesDir()` 等のパス |
| `src/infra/config/paths.ts` | `getBuiltinPiecesDir()`, `getBuiltinPersonasDir()` |
| `src/infra/config/global/initialization.ts` | `copyLanguageConfigYaml()` |
| `src/infra/config/loaders/pieceCategories.ts` | `getLanguageResourcesDir()` 参照 |
| `src/features/config/ejectBuiltin.ts` | `getLanguageResourcesDir()` 参照 |
| `src/features/config/deploySkill.ts` | `getResourcesDir()` 参照 |
### 4. export-cc 修正(優先度: 高)
ファイル: `src/features/config/deploySkill.ts`
**現状**: pieces/ と personas/ のみコピー
**修正後**:
- `builtins/{lang}/` 全体を `~/.claude/skills/takt/` にコピー
- `skill/` のファイル(SKILL.md, references/, takt-command.md)は従来通り
- サマリー表示を新リソースタイプ(stances, instructions, knowledge 等)に対応
- confirm メッセージ修正:
- 現状: `'上書きしますか?'`
- 修正後: `'既存のスキルファイルをすべて削除し、最新版に置き換えます。続行しますか?'`
### 5. テスト修正(優先度: 中)
| ファイル | 修正内容 |
|----------|----------|
| `src/__tests__/initialization.test.ts` | `getLanguageResourcesDir` のパス期待値 |
| `src/__tests__/piece-category-config.test.ts` | mock パス |
| その他 `resources` パスを参照しているテスト | パス更新 |
### 6. ビルド・パッケージ設定(優先度: 中)
| ファイル | 修正内容 |
|----------|----------|
| `package.json` | `files` フィールドで `resources/` → `builtins/` |
| `tsconfig.json` | `resources/` への参照があれば更新 |
| `.gitignore` | 必要に応じて更新 |
### 7. ドキュメント(優先度: 低)
- `CLAUDE.md` の Directory Structure セクションを更新
- JSDoc コメントから `prompts/` 記述を削除
---
## 制約
- `builtins/{lang}/` のフラット構造は変更不可(ピースYAML内の相対パス依存)
- eject のセーフティ(skip-if-exists)は変更不要
- export-cc のセーフティ(SKILL.md 存在チェック + confirm)は維持
---
## 確認方法
- `npm run build` が成功すること
- `npm test` が全てパスすること
- `takt init` / `takt eject` / `takt export-cc` が正常動作すること
9.9 KiB
9.9 KiB
コーディングスタンス
速さより丁寧さ、実装の楽さよりコードの正確さを優先する。
原則
| 原則 | 基準 |
|---|---|
| Simple > Easy | 書きやすさより読みやすさを優先 |
| DRY | 3回重複したら抽出 |
| コメント | Why のみ。What/How は書かない |
| 関数サイズ | 1関数1責務。30行目安 |
| ファイルサイズ | 目安として300行。タスクに応じて柔軟に |
| ボーイスカウト | 触った箇所は少し改善して去る |
| Fail Fast | エラーは早期に検出。握りつぶさない |
フォールバック・デフォルト引数の禁止
値の流れを不明瞭にするコードは書かない。ロジックを追わないと値が分からないのは悪いコード。
禁止パターン
| パターン | 例 | 問題 |
|---|---|---|
| 必須データへのフォールバック | user?.id ?? 'unknown' |
エラーになるべき状態で処理が進む |
| デフォルト引数の濫用 | function f(x = 'default') で全呼び出し元が省略 |
値がどこから来るか分からない |
| null合体で渡す口がない | options?.cwd ?? process.cwd() で上位から渡す経路なし |
常にフォールバックになる(意味がない) |
| try-catch で空値返却 | catch { return ''; } |
エラーを握りつぶす |
| 不整合な値のサイレントスキップ | if (a !== expected) return undefined |
設定ミスが実行時に黙って無視される |
正しい実装
// ❌ 禁止 - 必須データへのフォールバック
const userId = user?.id ?? 'unknown'
processUser(userId) // 'unknown' で処理が進んでしまう
// ✅ 正しい - Fail Fast
if (!user?.id) {
throw new Error('User ID is required')
}
processUser(user.id)
// ❌ 禁止 - デフォルト引数で全呼び出し元が省略
function loadConfig(path = './config.json') { ... }
// 全呼び出し元: loadConfig() ← path を渡していない
// ✅ 正しい - 必須引数にして明示的に渡す
function loadConfig(path: string) { ... }
// 呼び出し元: loadConfig('./config.json') ← 明示的
// ❌ 禁止 - null合体で渡す口がない
class Engine {
constructor(config, options?) {
this.cwd = options?.cwd ?? process.cwd()
// 問題: options に cwd を渡す経路がない場合、常に process.cwd() になる
}
}
// ✅ 正しい - 上位から渡せるようにする
function createEngine(config, cwd: string) {
return new Engine(config, { cwd })
}
許容されるケース
- 外部入力(ユーザー入力、API応答)のバリデーション時のデフォルト値
- 設定ファイルのオプショナル値(明示的に省略可能と設計されている)
- 一部の呼び出し元のみがデフォルト引数を使用(全員が省略している場合は禁止)
判断基準
- 必須データか? → フォールバックせず、エラーにする
- 全呼び出し元が省略しているか? → デフォルト引数を削除し、必須にする
- 上位から値を渡す経路があるか? → なければ引数・フィールドを追加
- 関連する値に不変条件があるか? → ロード・セットアップ時にクロスバリデーションする
抽象化
条件分岐を追加する前に考える
- 同じ条件が他にもあるか → あればパターンで抽象化
- 今後も分岐が増えそうか → Strategy/Mapパターンを使う
- 型で分岐しているか → ポリモーフィズムで置換
// ❌ 条件分岐を増やす
if (type === 'A') { ... }
else if (type === 'B') { ... }
else if (type === 'C') { ... } // また増えた
// ✅ Mapで抽象化
const handlers = { A: handleA, B: handleB, C: handleC };
handlers[type]?.();
抽象度を揃える
1つの関数内では同じ粒度の処理を並べる。詳細な処理は別関数に切り出す。「何をするか」と「どうやるか」を混ぜない。
// ❌ 抽象度が混在
function processOrder(order) {
validateOrder(order); // 高レベル
const conn = pool.getConnection(); // 低レベル詳細
conn.query('INSERT...'); // 低レベル詳細
}
// ✅ 抽象度を揃える
function processOrder(order) {
validateOrder(order);
saveOrder(order); // 詳細は隠蔽
}
言語・フレームワークの作法に従う
- Pythonなら Pythonic に、KotlinならKotlinらしく
- フレームワークの推奨パターンを使う
- 独自の書き方より標準的な書き方を選ぶ
- 不明なときはリサーチする。推測で実装しない
インターフェース設計
インターフェースは利用側の都合で設計する。実装側の内部構造を露出しない。
| 原則 | 基準 |
|---|---|
| 利用者視点 | 呼び出し側が必要としないものを押し付けない |
| 構成と実行の分離 | 「何を使うか」はセットアップ時に決定し、実行APIはシンプルに保つ |
| メソッド増殖の禁止 | 同じことをする複数メソッドは構成の違いで吸収する |
// ❌ メソッド増殖 — 構成の違いを呼び出し側に押し付けている
interface NotificationService {
sendEmail(to, subject, body)
sendSMS(to, message)
sendPush(to, title, body)
sendSlack(channel, message)
}
// ✅ 構成と実行の分離
interface NotificationService {
setup(config: ChannelConfig): Channel
}
interface Channel {
send(message: Message): Promise<Result>
}
抽象化の漏れ
特定実装が汎用層に現れたら抽象化が漏れている。汎用層はインターフェースだけを知り、分岐は実装側で吸収する。
// ❌ 汎用層に特定実装のインポートと分岐
import { uploadToS3 } from '../aws/s3.js'
if (config.storage === 's3') {
return uploadToS3(config.bucket, file, options)
}
// ✅ 汎用層はインターフェースのみ。非対応は生成時にエラー
const storage = createStorage(config)
return storage.upload(file, options)
構造
分割の基準
- 独自のstateを持つ → 分離
- 50行超のUI/ロジック → 分離
- 複数の責務がある → 分離
依存の方向
- 上位層 → 下位層(逆方向禁止)
- データ取得はルート(View/Controller)で行い、子に渡す
- 子は親のことを知らない
状態管理
- 状態は使う場所に閉じ込める
- 子は状態を直接変更しない(イベントを親に通知)
- 状態の流れは単方向
エラーハンドリング
エラーは一元管理する。各所でtry-catchしない。
// ❌ 各所でtry-catch
async function createUser(data) {
try {
const user = await userService.create(data)
return user
} catch (e) {
console.error(e)
throw new Error('ユーザー作成に失敗しました')
}
}
// ✅ 上位層で一元処理
// Controller/Handler層でまとめてキャッチ
// または @ControllerAdvice / ErrorBoundary で処理
async function createUser(data) {
return await userService.create(data) // 例外はそのまま上に投げる
}
エラー処理の配置
| 層 | 責務 |
|---|---|
| ドメイン/サービス層 | ビジネスルール違反時に例外をスロー |
| Controller/Handler層 | 例外をキャッチしてレスポンスに変換 |
| グローバルハンドラ | 共通例外(NotFound, 認証エラー等)を処理 |
変換処理の配置
変換メソッドはDTO側に持たせる。
// ✅ Request/Response DTOに変換メソッド
interface CreateUserRequest {
name: string
email: string
}
function toUseCaseInput(req: CreateUserRequest): CreateUserInput {
return { name: req.name, email: req.email }
}
// Controller
const input = toUseCaseInput(request)
const output = await useCase.execute(input)
return UserResponse.from(output)
変換の方向:
Request → toInput() → UseCase/Service → Output → Response.from()
共通化の判断
3回ルール
- 1回目: そのまま書く
- 2回目: まだ共通化しない(様子見)
- 3回目: 共通化を検討
共通化すべきもの
- 同じ処理が3箇所以上
- 同じスタイル/UIパターン
- 同じバリデーションロジック
- 同じフォーマット処理
共通化すべきでないもの
- 似ているが微妙に違うもの(無理に汎用化すると複雑化)
- 1-2箇所しか使わないもの
- 「将来使うかも」という予測に基づくもの
// ❌ 過度な汎用化
function formatValue(value, type, options) {
if (type === 'currency') { ... }
else if (type === 'date') { ... }
else if (type === 'percentage') { ... }
}
// ✅ 用途別に関数を分ける
function formatCurrency(amount: number): string { ... }
function formatDate(date: Date): string { ... }
function formatPercentage(value: number): string { ... }
禁止事項
- フォールバックは原則禁止 -
?? 'unknown'、|| 'default'、try-catchで握りつぶすフォールバックを書かない。エラーは上位に伝播させる。どうしても必要な場合はコメントで理由を明記する - 説明コメント - コードで意図を表現する。What/How のコメントは書かない
- 未使用コード - 「念のため」のコードは書かない
- any型 - 型安全を破壊しない
- オブジェクト/配列の直接変更 - スプレッド演算子で新規作成
- console.log - 本番コードに残さない
- 機密情報のハードコーディング
- 各所でのtry-catch - エラーは上位層で一元処理
- 後方互換・Legacy対応の自発的追加 - 明示的な指示がない限り不要
- 安全機構を迂回するワークアラウンド - 根本修正が正しいなら追加の迂回は不要