.claude/skills/writing-typescript/SKILL.md
TypeScript / JavaScript コードの作成・レビュー時に適用する規約とベストプラクティス。 .ts, .tsx, .js, .jsx ファイルの編集、Node.js/Deno プロジェクトのセットアップ、 vitest/biome/tsc の実行時に自動で有効化される。
npx skillsauth add blackawa/dotfiles writing-typescriptInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| 用途 | 推奨 | 非推奨 | |------|------|--------| | パッケージ管理 | pnpm | npm, yarn | | テスト | vitest | jest | | Lint + Format | biome (v2+) | eslint + prettier(レガシー既存PJのみ), tslint | | 型チェック | tsc --noEmit | - | | ランタイム | Node.js LTS / Deno | - |
なぜ Biome か: ESLint+Prettier比で10〜25倍高速、設定ファイル1つ、 バイナリ1つ(127+ npmパッケージ不要)。v2でtype-aware linting・プラグイン対応済み。 ESLintが必要なのは、Biome未対応の特殊プラグイン(a11y等)に依存する既存PJのみ。
# 依存関係
pnpm add <package>
pnpm add -D <package>
pnpm install
# 品質チェック(変更後は必ずこの順で実行)
pnpm exec tsc --noEmit # 型チェック
pnpm exec biome check --fix . # lint + format(一括)
pnpm exec vitest run # テスト
pnpm add -D --save-exact @biomejs/biome
pnpm exec biome init # biome.json 生成
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noExcessiveCognitiveComplexity": "warn"
},
"suspicious": {
"noExplicitAny": "error"
},
"style": {
"noDefaultExport": "error",
"useImportType": "error",
"noNonNullAssertion": "error"
}
}
},
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always"
}
}
}
以下のオプションをすべて有効にする。妥協しない。
{
"compilerOptions": {
"target": "ES2024",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}
any 禁止。unknown を使って型ガードで絞り込むas によるキャストは最終手段。satisfies を優先する// @ts-ignore, // @ts-expect-error はテスト内の希少なケースのみ<T> ではなく <T extends Base>)// ✅ Good: satisfies で型安全に
const config = {
port: 3000,
host: "localhost",
} satisfies ServerConfig;
// ✅ Good: Discriminated Union
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: Error };
function handle(result: Result<User>) {
if (result.ok) {
console.log(result.value.name); // 型が絞られる
}
}
// ❌ Bad: any でごまかす
function parse(data: any) { return data.name; }
// ❌ Bad: as で無理やりキャスト
const user = response as User;
interface(拡張可能)typez.infer<typeof schema> で型を導出// ✅ interface: オブジェクトの形状
interface User {
readonly id: string;
name: string;
email: string;
}
// ✅ type: ユニオン・ユーティリティ
type UserRole = "admin" | "member" | "guest";
type UserWithRole = User & { role: UserRole };
const をデフォルト、変更が必要な場合のみ let。var は禁止?.) と Nullish Coalescing (??) を活用for...of ループを優先。forEach よりも中断可能で読みやすいimport type で型のみのインポートを明示する// ✅ Good: 早期リターン + 型ガード
function getDisplayName(user: User | undefined): string {
if (!user) return "Anonymous";
if (!user.name.trim()) return `User#${user.id}`;
return user.name;
}
// ✅ Good: import type
import type { User } from "./types.js";
import { createUser } from "./service.js";
enum — 代わりに as const 付きオブジェクトか Union Literal を使うnamespace — ESM のモジュールシステムを使うdefault export — named export を使う(リファクタリング安全性)! (non-null assertion) — 適切な型ガードで代替するeval(), Function() — セキュリティリスク// ✅ Good: as const でenum代替
const Status = {
Active: "active",
Inactive: "inactive",
Pending: "pending",
} as const;
type Status = (typeof Status)[keyof typeof Status];
// ❌ Bad: enum
enum Status { Active, Inactive, Pending }
cause チェインを活用)catch(e: unknown) で捕捉し、instanceof で型チェックclass ApiError extends Error {
constructor(
message: string,
readonly statusCode: number,
options?: ErrorOptions,
) {
super(message, options);
this.name = "ApiError";
}
}
// ✅ Good: cause チェインで原因を保持
try {
const data = await fetchUser(id);
} catch (e: unknown) {
throw new ApiError(`Failed to fetch user ${id}`, 500, { cause: e });
}
foo.ts → foo.test.tsvi.mock() でモジュールモック、vi.fn() でスパイimport { describe, it, expect, vi } from "vitest";
import { createUser } from "./user-service.js";
describe("createUser", () => {
it("returns user with generated id", async () => {
const user = await createUser({ name: "Alice", email: "[email protected]" });
expect(user.id).toBeDefined();
expect(user.name).toBe("Alice");
});
it("throws on duplicate email", async () => {
await createUser({ name: "Alice", email: "[email protected]" });
await expect(
createUser({ name: "Bob", email: "[email protected]" }),
).rejects.toThrow("Email already exists");
});
});
myproject/
├── src/
│ ├── index.ts # エントリポイント(named export)
│ ├── types.ts # 共有型定義
│ ├── lib/ # コアロジック
│ ├── utils/ # ユーティリティ
│ └── __tests__/ # 統合テスト(ユニットはコロケーション)
├── package.json
├── biome.json # Biome設定(lint + format 統合)
├── tsconfig.json
├── vitest.config.ts
└── README.md
components/UserProfile.tsx # PascalCase: React コンポーネント
hooks/useAuth.ts # camelCase + use prefix: カスタムフック
lib/format-date.ts # kebab-case: ユーティリティモジュール
types.ts # 型定義ファイル
foo.test.ts # テストはコロケーション
pnpm audit --audit-level=moderate を依存追加前に実行^ や ~ を付けない)で pin するpnpm-lock.yaml) を必ずコミットするdevelopment
X(Twitter)の特定投稿URLから原文を直接取得するスキル。 fxtwitter API(APIキー不要・無料)を使用し、ロングポスト(記事形式)の全文取得にも対応。 以下のようなリクエストで発動する: 「この投稿を取得」「ツイートの内容」「このURLの投稿を見せて」 「このXの投稿を読んで」「このツイートを取得して」。 X/TwitterのURLが含まれるメッセージで、検索ではなく特定投稿の内容取得が目的の場合に使う。 x-ai-search との棲み分け: - 検索(キーワードで複数投稿を探す)→ x-ai-search - 特定投稿の取得(URLやIDで1件取得)→ x-tweet-fetch
business
Slackメッセージの作成・送信時に適用する規約とベストプラクティス。 slack_send_message / slack_send_message_draft の実行時に自動で有効化される。 「Slackで連絡して」「Slackに投稿して」「スレッドに返信して」 といった発言があれば、このスキルを使うこと。
development
Python コードの作成・レビュー時に適用する規約とベストプラクティス。 Pythonファイルの編集、Pythonプロジェクトのセットアップ、 pytest/ruff/mypyの実行時に自動で有効化される。
business
社内向けの提案・決裁依頼テキストの作成時に適用する構成テンプレートとベストプラクティス。 「提案書を書いて」「決裁依頼を作って」「稟議を書いて」「承認を取りたい」 「〜を導入したい」「〜の予算を取りたい」「〜の増額を依頼したい」 といった発言があれば、このスキルを使うこと。 Slack / メール / ドキュメントいずれの媒体にも対応する。