| name | coding-standards |
| description | Flequit プロジェクトのコーディング標準への準拠をチェックします。命名規則、ファイル構造、型定義、エラーハンドリング、コメント、インポート順序などのコーディング標準チェックに使用します。 |
| allowed-tools | Read, Edit, Bash(bun check:*), Bash(bun run lint:*) |
| model | sonnet |
Coding Standards Skill
Flequit プロジェクトのコーディング標準への準拠をチェックするスキルです。
命名規則
TypeScript/JavaScript
// ✅ OK: camelCase
const userName = 'john';
const taskList = [];
function getUserById(id: string) {}
// ✅ OK: PascalCase (クラス、型、コンポーネント)
class TaskManager {}
interface Task {}
type TaskStatus = 'todo' | 'in_progress' | 'completed';
// ✅ OK: SCREAM_SNAKE_CASE (定数)
const USER_ROLE_ADMIN = 'admin';
const MAX_RETRY_COUNT = 3;
// ❌ NG: snake_case(Rust以外では使用しない)
const user_name = 'john'; // camelCase にすべき
function get_user_by_id(id: string) {} // camelCase にすべき
Rust
// ✅ OK: snake_case
let user_name = "john";
fn get_user_by_id(id: &str) {}
// ✅ OK: PascalCase (構造体、Enum)
struct TaskManager {}
enum TaskStatus {
Todo,
InProgress,
Completed,
}
// ✅ OK: SCREAM_SNAKE_CASE (定数)
const USER_ROLE_ADMIN: &str = "admin";
const MAX_RETRY_COUNT: usize = 3;
// ❌ NG: camelCase(Rustでは使用しない)
let userName = "john"; // snake_case にすべき
fn getUserById(id: &str) {} // snake_case にすべき
ファイル・ディレクトリ名
✅ OK: kebab-case
components/task-item.svelte
services/task-service.ts
repositories/task-repository.rs
❌ NG: camelCase や snake_case
components/TaskItem.svelte
services/task_service.ts
ファイル構造
ファイルサイズ制限
- 200行以上: 必須分割対象(テストコード除外)
- 100行以上: 分割を検討
- 例外: 設定ファイル、データ定義
// ❌ NG: 250行の巨大ファイル
// task-service.ts (250行)
// ✅ OK: 機能ごとに分割
// task-service.ts (120行)
// task-filter-service.ts (70行)
// task-sort-service.ts (60行)
単一責任原則
// ✅ OK: 1ファイル1責任
// task-service.ts - タスク操作のみ
export class TaskService {
async getTasks(projectId: string) { /* ... */ }
async createTask(request: CreateTaskRequest) { /* ... */ }
}
// ❌ NG: 複数の責任
// app-service.ts - タスク、プロジェクト、ユーザー全部
export class AppService {
async getTasks() { /* ... */ }
async getProjects() { /* ... */ }
async getUsers() { /* ... */ }
}
型定義
厳密な型指定
// ✅ OK: 具体的な型
interface Task {
id: string;
title: string;
status: 'todo' | 'in_progress' | 'completed';
assignee?: User;
}
// ❌ NG: any を使用
interface Task {
id: any; // string にすべき
title: string;
status: string; // Union type にすべき
assignee: any; // User | undefined にすべき
}
Optional vs Required
// ✅ OK: 明確な区別
interface CreateTaskRequest {
title: string; // 必須
description?: string; // オプション
dueDate?: Date; // オプション
}
interface Task {
id: string; // 作成後は必須
title: string; // 必須
description: string; // 作成後は必須(空文字列でも)
dueDate?: Date; // 常にオプション
}
Rust の型定義
// ✅ OK: 適切な derive
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: TaskId,
pub title: String,
pub status: TaskStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
Todo,
InProgress,
Completed,
}
エラーハンドリング
TypeScript
// ✅ OK: 明示的なエラーハンドリング
async function fetchTasks(): Promise<Task[] | null> {
try {
const response = await api.getTasks();
return response.data;
} catch (error) {
console.error('Failed to fetch tasks:', error);
return null;
}
}
// ✅ OK: Result型パターン
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
async function fetchTasksWithResult(): Promise<Result<Task[]>> {
try {
const data = await api.getTasks();
return { success: true, data };
} catch (error) {
return { success: false, error: error as Error };
}
}
// ❌ NG: エラーを無視
async function fetchTasks(): Promise<Task[]> {
const response = await api.getTasks(); // エラーが throw される可能性
return response.data;
}
Rust
// ✅ OK: カスタムエラー型
#[derive(Debug, thiserror::Error)]
pub enum ServiceError {
#[error("Resource not found: {0}")]
NotFound(String),
#[error("Validation failed: {0}")]
Validation(String),
#[error("Repository error: {0}")]
Repository(#[from] RepositoryError),
}
pub type ServiceResult<T> = Result<T, ServiceError>;
// ✅ OK: エラーの伝播
pub async fn get_task(id: &TaskId) -> ServiceResult<Task> {
let task = repository.find_by_id(id).await?
.ok_or_else(|| ServiceError::NotFound("Task not found".to_string()))?;
Ok(task)
}
// ❌ NG: unwrap でパニック
pub async fn get_task(id: &TaskId) -> Task {
repository.find_by_id(id).await.unwrap().unwrap() // パニックの可能性
}
インポート順序
TypeScript
// 1. Node modules
import { invoke } from '@tauri-apps/api/tauri';
import { writable } from 'svelte/store';
// 2. Internal libraries ($lib で始まる)
import type { Task } from '$lib/types';
import { taskService } from '$lib/services/task-service';
import TaskItem from '$lib/components/task-item.svelte';
// 3. Relative paths(エイリアスがない場合のみ)
import './component.css';
Svelte コンポーネントのインポート
// ✅ OK: エイリアスを使用
import TaskItem from '$lib/components/task-item.svelte';
import Textbox from '$lib/components/ui/textbox.svelte';
// ❌ NG: 相対パス(エイリアスがある場合)
import TaskItem from '../../components/task-item.svelte';
// ✅ OK: エイリアスがない場合のみ相対パス許可
// src/routes/tasks/+page.svelte
import LocalHelper from './local-helper.svelte';
Rust
// 1. 標準ライブラリ
use std::collections::HashMap;
use std::sync::Arc;
// 2. 外部 crate
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
// 3. 自 crate
use crate::models::task::Task;
use crate::services::task_service;
// 4. 親モジュール
use super::repository::TaskRepository;
コメント
TypeScript
/**
* ユーザーのタスク一覧を取得
*
* @param userId - 対象ユーザーのID
* @param options - 取得オプション
* @returns Promise<Task[]> - タスク一覧
*
* @example
* ```typescript
* const tasks = await getUserTasks('user-123', { includeCompleted: false });
* ```
*/
export async function getUserTasks(
userId: string,
options: GetTasksOptions = {}
): Promise<Task[]> {
// 実装
}
Rust
/// タスクの進捗を計算
///
/// # Arguments
///
/// * `completed_tasks` - 完了タスク数
/// * `total_tasks` - 総タスク数
///
/// # Returns
///
/// 進捗パーセンテージ (0-100)
///
/// # Examples
///
/// ```
/// let progress = calculate_progress(3, 10);
/// assert_eq!(progress, 30);
/// ```
pub fn calculate_progress(completed_tasks: usize, total_tasks: usize) -> u8 {
if total_tasks == 0 {
return 0;
}
((completed_tasks * 100) / total_tasks) as u8
}
関数・メソッド
純粋関数の推奨
// ✅ OK: 純粋関数
function calculateProgress(completedTasks: number, totalTasks: number): number {
if (totalTasks === 0) return 0;
return Math.round((completedTasks / totalTasks) * 100);
}
// ✅ OK: 副作用が必要な場合は明確に分離
function updateTaskInStore(task: Task): void {
taskStore.updateTask(task);
}
// ❌ NG: 副作用を持つ関数(名前から推測できない)
function calculateProgressAndUpdate(completedTasks: number, totalTasks: number): number {
const progress = Math.round((completedTasks / totalTasks) * 100);
updateProgressUI(progress); // 副作用 - 名前から推測できない
return progress;
}
Svelte 5 パターン
Props 定義
// ✅ OK: 明確な Props interface
<script lang="ts">
interface Props {
task: Task;
readonly?: boolean;
onUpdate?: (task: Task) => void;
onDelete?: (taskId: string) => void;
}
let {
task,
readonly = false,
onUpdate = () => {},
onDelete = () => {}
}: Props = $props();
</script>
State 管理
// ✅ OK: $state と $derived の適切な使用
let isEditing = $state<boolean>(false);
let formData = $state<CreateTaskRequest>({
title: '',
description: ''
});
const isFormValid = $derived(
formData.title.trim().length > 0
);
// ❌ NG: 手動で状態を同期
let isFormValid = $state<boolean>(false);
$effect(() => {
isFormValid = formData.title.trim().length > 0; // $derived を使うべき
});
チェックリスト
TypeScript/Svelte
- 変数・関数名は camelCase
- クラス・型名は PascalCase
- 定数は SCREAM_SNAKE_CASE
- ファイル名は kebab-case
- ファイルサイズが200行以下
- any 型を使用していない
- エラーハンドリングが適切
- インポート順序が正しい
- Svelte コンポーネントのインポートにエイリアス使用
- $state, $derived を適切に使用
Rust
- 変数・関数名は snake_case
- 構造体・Enum は PascalCase
- 定数は SCREAM_SNAKE_CASE
- ファイル名は snake_case
- 適切な derive を使用
- unwrap を避けて ? 演算子使用
- カスタムエラー型を定義
- インポート順序が正しい
共通
- 単一責任原則に従っている
- 適切なコメントが記述されている
- テストが書かれている
- ドキュメントが更新されている
Lint/Format コマンド
フロントエンド
# 型チェック
bun check
# Lint チェック
bun run lint
# Lint 自動修正
bun run lint:fix
# Format
bun run format
バックエンド
# 型チェック
cargo check
# Lint (clippy)
cargo clippy
# Format
cargo fmt
# Format チェック
cargo fmt --check
関連ドキュメント
詳細は以下のドキュメントを参照:
docs/en/develop/rules/coding-standards.md- コーディング標準全体docs/en/develop/rules/frontend.md- フロントエンドルールdocs/en/develop/rules/backend.md- バックエンドルールdocs/en/develop/design/frontend/svelte5-patterns.md- Svelte 5 パターン