書いたコードを“スキル化”して再利用してる話
こんにちは、@_mkazutaka です。
今回は、Claude Codeのスキル機能を使って、過去に書いたコードをスキル化し、再利用する方法について紹介したいと思います。
背景
コードを書く際に、プロジェクトの規則、もしくはある一定のパターンに従って、コードを書くことがあると思います。例えば、弊社の場合、データストアへのアクセスのためにRepositoryパターンが使われているのですが、エンティティごとにだいたい似たような実装になることが多いです。
具体的には以下の6個の関数を定義し、テストを加えた形が多いです。
- create: 作成
- update: 更新
- save: 更新 もしくは 作成
- find: 単一のレコードを検索
- list: 複数のレコードを検索
- delete: 削除
これらを手動で書くには量が多く時間もかかってしまいます。なので、テンプレート化して楽に書きたくなってきます。
LLM登場以前はgo generate等を使うケースも多かったのですが、パターンごとにgo generateのテンプレートを作るのも大変です。一方で、LLMに既存コードを参考にさせ生成させると、古い書き方や不要なコードまで生成してしまい、結局修正の手間がかかります。
どうにかできないかなと思っていたのですが、ちょうど良きタイミングでAgent Skillsが登場し、これを使えば精度も高く、簡単にテンプレート化できたので紹介したいと思います。
Claude Code の Agent Skills とは
Claude Codeの機能を拡張する機能の一つです。SKILL.mdを起点に、スクリプトや複数のマークダウンを一つのディレクトリに格納でき、それを一まとまりのモジュールとして配布やClaude上から参照することができます。
詳しい説明は以下の記事に載せていたりしますので、よかったらご覧ください。
Agent Skills の skill creator
Anthropicが用意している公式のClaude CodeのSkillの中にSkillを作成するためのSkill、skill creator(コード)。これを使うことで、簡単にClaude Code用のAgent Skillを作ることができます。
つまり発想としては、skill creatorに特定のPRやgitのdiffなどのコードの変更を与えることで、コードの変更箇所からテンプレートを自動作成し、それをスキルとして再利用しようという感じです。
実際にやってみる
Repositoryパターンのコードを用意します。参考までに以下のコードを使います。
(弊社は、関数型DDDを採用しており、カリー化等をしているコードになります。ここではあくまで雰囲気をわかってもらえれば問題ないので、詳しいコードの解説はしません。)
UserRepositoryの例
import type { Prisma } from '@repo/prisma/generated'
import { fromAsyncThrowable, type ResultAsync } from 'neverthrow'
import {
buildUserFromModel,
type User,
toCreateUserInput,
toUpdateUserInput,
} from '$lib/features/user/entities/user'
import type { AppContext } from '$lib/types/context'
import type { UserID } from '$lib/types/ids'
export function toCreateUser(db: Prisma.TransactionClient) {
return (user: User): ResultAsync<User, Error> => {
const userCreate = fromAsyncThrowable(
db.user.create,
(e) => new Error((e as Error).message),
)
return userCreate({
data: toCreateUserInput(user),
}) //.map(() => user)
}
}
export function toUpdateUser(db: Prisma.TransactionClient) {
return (user: User): ResultAsync<User, Error> => {
const userUpdate = fromAsyncThrowable(
db.user.update,
(e) => new Error((e as Error).message),
)
return userUpdate({
where: { id: user.id },
data: toUpdateUserInput(user),
}) //.map(() => user)
}
}
export function toSaveUser(db: Prisma.TransactionClient) {
return (user: User): ResultAsync<User, Error> => {
const userUpsert = fromAsyncThrowable(
db.user.upsert,
(e) => new Error((e as Error).message),
)
return userUpsert({
where: { id: user.id },
create: toCreateUserInput(user),
update: toUpdateUserInput(user),
}) //.map(() => user)
}
}
export function toFindUserByID(ctx: AppContext, db: Prisma.TransactionClient) {
return (id: UserID): ResultAsync<User, Error> => {
const userFindUniqueOrThrow = fromAsyncThrowable(
db.user.findUniqueOrThrow,
(e) => new Error((e as Error).message),
)
return userFindUniqueOrThrow({
where: {
id,
organizationId: ctx.organizationId,
teamId: ctx.teamId,
},
}) //.map((m) => buildUserFromModel(m))
}
}
export function toListUsers(ctx: AppContext, db: Prisma.TransactionClient) {
return (): ResultAsync<User[], Error> => {
const userFindMany = fromAsyncThrowable(
db.user.findMany,
(e) => new Error((e as Error).message),
)
return userFindMany({
where: {
organizationId: ctx.organizationId,
teamId: ctx.teamId,
},
}) //.map((rows) => rows.map((r) => buildUserFromModel(r)))
}
}
export function toDeleteUserByID(ctx: AppContext, db: Prisma.TransactionClient) {
return (id: UserID): ResultAsync<void, Error> => {
const userDelete = fromAsyncThrowable(
db.user.delete,
(e) => new Error((e as Error).message),
)
return userDelete({
where: {
id,
organizationId: ctx.organizationId,
teamId: ctx.teamId,
},
}) //.map(() => undefined)
}
}
Agent Skillの導入
Claude Codeからスキルを使うためには、事前にスキルを特定の箇所に配置する必要があります。
今回はプロジェクト単位なので、プロジェクト直下の.claude/skills にskill-creatorディレクトリをコピーし配置します。
skill-creator スキルを使う
ここからの処理は簡単です。skill-creator を呼び出すだけで完了です
今回の場合、以下のようなプロンプトを使用しました
skill creatorを使って、git diffから得られるRepositoryコードをテンプレート化し、スキル化してください。
作成されたスキル
上記のプロンプトを実行後、以下のようなファイルが出来上がりました。
コードの変更箇所からうまくテンプレート用のベースとなるコードを生成しています。これを使うことで最終的な成果物の精度をシンプルなプロンプト以上に上げることができるようです。
SKILL.md
---
name: repository-generator
description: Generate or refactor repository layer code following the project's standard pattern. Use when creating new entity repositories (Product, Order, etc.) or refactoring existing repository code to match the standard CRUD pattern with Prisma, neverthrow, and AppContext.
---
# Repository Generator
## Overview
Generate type-safe repository layer code following this project's standard pattern using Prisma, neverthrow for error handling, and AppContext for multi-tenancy. This skill supports both creating new repositories from scratch and refactoring existing repository code.
## When to Use This Skill
Use this skill when:
1. **Creating a new entity repository** - Generate complete repository code for a new domain entity (e.g., Product, Order, Customer)
2. **Refactoring existing repositories** - Update existing repository code to match the standard pattern
3. **Adding missing CRUD operations** - Extend existing repositories with standard operations they're lacking
4. **Standardizing repository implementations** - Ensure consistency across all repositories in the codebase
## Workflow Decision Tree
### 1. Determine the Task Type
**For new repositories:**
- Proceed to "Creating a New Repository"
**For existing repositories:**
- Proceed to "Refactoring an Existing Repository"
### 2. Creating a New Repository
When generating a new repository:
1. **Identify the entity details:**
- Entity name (e.g., `Product`, `Order`, `Customer`)
- Entity ID type (e.g., `ProductID`, `OrderID`, `CustomerID`)
- Whether multi-tenancy is required (organizationId/teamId filtering)
2. **Read the template:**
- Load `assets/repository-template.ts`
3. **Apply variable substitution:**
- `{{EntityName}}` → PascalCase entity name (e.g., `Product`)
- `{{entityName}}` → camelCase entity name (e.g., `product`)
- `{{EntityID}}` → Entity ID type (e.g., `ProductID`)
- `{{#if useMultiTenancy}}...{{/if}}` → Include/exclude multi-tenancy code based on requirements
4. **Verify entity dependencies exist:**
- Check that `$lib/features/{entity}/entities/{entity}.ts` exists with:
- `type {Entity}` - Domain entity type
- `build{Entity}FromModel` - Conversion from Prisma model
- `toCreate{Entity}Input` - Conversion to create input
- `toUpdate{Entity}Input` - Conversion to update input
5. **Create the repository file:**
- Path: `$lib/features/{entity}/repository/{entity}Repository.ts`
- Use the substituted template
6. **Customize if needed:**
- Add additional query methods if required
- Modify where clauses for specific business logic
- Add specialized find methods (e.g., `findByEmail`, `findByStatus`)
### 3. Refactoring an Existing Repository
When updating existing repository code:
1. **Read the existing repository:**
- Understand current implementation
- Identify which operations exist
- Note any custom logic that should be preserved
2. **Compare with standard pattern:**
- Load `assets/repository-template.ts` for reference
- Identify gaps or inconsistencies
- Note any operations that don't follow the pattern
3. **Apply refactoring:**
- Replace non-standard implementations with template-based code
- Ensure all functions use `fromAsyncThrowable` for error handling
- Add missing CRUD operations if requested
- Preserve any custom business logic
4. **Update function signatures:**
- Ensure proper use of `AppContext` for tenant-scoped operations
- Verify return types are `ResultAsync<T, Error>`
- Follow naming convention: `to{Operation}{Entity}`
5. **Test compatibility:**
- Check that entity conversion functions exist
- Verify imports are correct
- Ensure Prisma model names match
## Standard Operations
The template includes six standard CRUD operations:
1. **toCreate{Entity}** - Create new entity
2. **toUpdate{Entity}** - Update existing entity by ID
3. **toSave{Entity}** - Upsert (create or update)
4. **toFind{Entity}ByID** - Find single entity by ID (with multi-tenancy)
5. **toList{Entity}s** - List all entities (with multi-tenancy)
6. **toDelete{Entity}ByID** - Delete entity by ID (with multi-tenancy)
Operations 4-6 require `AppContext` for tenant isolation.
## Multi-Tenancy Considerations
### When to Include Multi-Tenancy
Include `organizationId` and `teamId` filtering when:
- The entity belongs to a specific organization/team
- Data isolation between tenants is required
- The Prisma model includes `organizationId` and `teamId` fields
### When to Omit Multi-Tenancy
Omit tenant filtering when:
- The entity is globally shared (e.g., system configurations, reference data)
- The entity doesn't have tenant-scoping fields
- Custom scoping logic is needed
### How to Apply
In the template, multi-tenancy code is marked with:
`\`\`typescript
{{#if useMultiTenancy}}
organizationId: ctx.organizationId,
teamId: ctx.teamId,
{{/if}}
`\`\`
When generating code:
- If multi-tenancy is needed: Include these lines
- If not needed: Remove these lines and possibly the `ctx` parameter from read operations
## Customization Points
### Adding Custom Query Methods
Beyond standard CRUD, add specialized finders as needed:
`\`\`typescript
export function toFindUserByEmail(ctx: AppContext, db: Prisma.TransactionClient) {
return (email: string): ResultAsync<User, Error> => {
const userFindUniqueOrThrow = fromAsyncThrowable(
db.user.findUniqueOrThrow,
(e) => new Error((e as Error).message),
)
return userFindUniqueOrThrow({
where: {
email,
organizationId: ctx.organizationId,
teamId: ctx.teamId,
},
})
}
}
`\`\`
### Modifying Where Clauses
For filtering beyond tenant isolation:
`\`\`typescript
export function toListActiveUsers(ctx: AppContext, db: Prisma.TransactionClient) {
return (): ResultAsync<User[], Error> => {
const userFindMany = fromAsyncThrowable(
db.user.findMany,
(e) => new Error((e as Error).message),
)
return userFindMany({
where: {
organizationId: ctx.organizationId,
teamId: ctx.teamId,
status: 'ACTIVE', // Additional filtering
},
})
}
}
`\`\`
### Including Relations
For queries that need related data:
`\`\`typescript
export function toFindUserWithProfile(ctx: AppContext, db: Prisma.TransactionClient) {
return (id: UserID): ResultAsync<User, Error> => {
const userFindUniqueOrThrow = fromAsyncThrowable(
db.user.findUniqueOrThrow,
(e) => new Error((e as Error).message),
)
return userFindUniqueOrThrow({
where: {
id,
organizationId: ctx.organizationId,
teamId: ctx.teamId,
},
include: {
profile: true, // Include related profile
},
})
}
}
`\`\`
## Resources
### assets/repository-template.ts
The base template for all repository implementations. Contains all six standard CRUD operations with template variables for entity customization.
### references/repository-pattern.md
Comprehensive documentation covering:
- Repository architecture and technology stack
- Error handling patterns with neverthrow
- Multi-tenancy support with AppContext
- Function signature patterns
- Type definitions and naming conventions
- Best practices and common patterns
Load this reference when:
- Understanding the architectural decisions
- Learning about neverthrow usage patterns
- Implementing custom operations beyond the template
- Troubleshooting type errors or pattern violations
## Examples
### Example 1: Generate Product Repository
**User request:** "Create a repository for Product entity with multi-tenancy"
**Process:**
1. Load `assets/repository-template.ts`
2. Substitute variables:
- `{{EntityName}}` → `Product`
- `{{entityName}}` → `product`
- `{{EntityID}}` → `ProductID`
- Include multi-tenancy code
3. Verify entity file exists at `$lib/features/product/entities/product.ts`
4. Create `$lib/features/product/repository/productRepository.ts`
### Example 2: Refactor Existing User Repository
**User request:** "Refactor the user repository to match the standard pattern"
**Process:**
1. Read existing `$lib/features/user/repository/userRepository.ts`
2. Load `assets/repository-template.ts` for comparison
3. Identify operations that don't match pattern
4. Replace non-standard implementations while preserving custom logic
5. Ensure all operations use `fromAsyncThrowable`
6. Verify naming conventions are followed
### Example 3: Add Missing Operations
**User request:** "Add delete and list operations to the Order repository"
**Process:**
1. Read existing `$lib/features/order/repository/orderRepository.ts`
2. Load relevant sections from `assets/repository-template.ts`:
- `toListOrders` function
- `toDeleteOrderByID` function
3. Substitute `Order`/`order`/`OrderID` variables
4. Add the new functions to existing repository file
5. Ensure consistency with existing code style
references/repository-pattern.md
# Repository Pattern Reference
## Overview
This document describes the repository pattern used in this project. Repositories provide a clean abstraction layer between the domain layer and the data access layer, using Prisma for database operations and neverthrow for error handling.
## Architecture
### Technology Stack
- **Prisma**: Type-safe database client
- **neverthrow**: Functional error handling with `Result` types
- **AppContext**: Multi-tenancy support with organization and team scoping
### File Structure
For an entity named `User`, the typical structure is:
`\`\`
$lib/features/user/
├── entities/
│ └── user.ts # Domain entity, conversion functions
└── repository/
└── userRepository.ts # Data access layer
`\`\`
## Repository Pattern Components
### 1. Entity Layer (`entities/`)
The entity layer defines:
- **Domain entity type**: The core business object (e.g., `User`)
- **Conversion functions**:
- `buildUserFromModel(model)`: Converts Prisma model to domain entity
- `toCreateUserInput(user)`: Converts domain entity to Prisma create input
- `toUpdateUserInput(user)`: Converts domain entity to Prisma update input
### 2. Repository Layer (`repository/`)
The repository layer provides data access functions using a consistent pattern:
#### Standard CRUD Operations
1. **Create**: `toCreate{Entity}(db)`
- Creates a new entity
- Parameters: Takes domain entity
- Returns: `ResultAsync<Entity, Error>`
2. **Update**: `toUpdate{Entity}(db)`
- Updates an existing entity by ID
- Parameters: Takes domain entity with ID
- Returns: `ResultAsync<Entity, Error>`
3. **Upsert**: `toSave{Entity}(db)`
- Creates or updates (upsert operation)
- Parameters: Takes domain entity
- Returns: `ResultAsync<Entity, Error>`
4. **Find by ID**: `toFind{Entity}ByID(ctx, db)`
- Retrieves a single entity by ID
- Parameters: Takes entity ID
- Returns: `ResultAsync<Entity, Error>`
- Throws if not found (`findUniqueOrThrow`)
5. **List**: `toList{Entity}s(ctx, db)`
- Retrieves all entities (with context filtering)
- Parameters: None
- Returns: `ResultAsync<Entity[], Error>`
6. **Delete**: `toDelete{Entity}ByID(ctx, db)`
- Deletes an entity by ID
- Parameters: Takes entity ID
- Returns: `ResultAsync<void, Error>`
## Error Handling Pattern
All repository functions use `fromAsyncThrowable` from neverthrow to convert Prisma's promise-based API to `ResultAsync`:
`\`\`typescript
const userCreate = fromAsyncThrowable(
db.user.create,
(e) => new Error((e as Error).message),
)
`\`\`
This pattern:
- Converts thrown errors into `Result.err` values
- Provides type-safe error handling
- Allows for functional composition with `.map()`, `.andThen()`, etc.
## Multi-Tenancy Support
### AppContext
Many operations require `AppContext` for multi-tenancy:
`\`\`typescript
interface AppContext {
organizationId: string
teamId: string
// ... other context fields
}
`\`\`
### Context Usage
- **Operations requiring context**: Read, List, Delete operations
- These filter by `organizationId` and `teamId`
- Ensures data isolation between tenants
- **Operations not requiring context**: Create, Update, Upsert
- Entity already contains tenant information
- Context embedded in domain entity
### Example with Context
`\`\`typescript
export function toFindUserByID(ctx: AppContext, db: Prisma.TransactionClient) {
return (id: UserID): ResultAsync<User, Error> => {
const userFindUniqueOrThrow = fromAsyncThrowable(
db.user.findUniqueOrThrow,
(e) => new Error((e as Error).message),
)
return userFindUniqueOrThrow({
where: {
id,
organizationId: ctx.organizationId, // Tenant filtering
teamId: ctx.teamId, // Tenant filtering
},
})
}
}
`\`\`
## Function Signature Patterns
### Higher-Order Function Pattern
All repository functions follow a curried pattern:
`\`\`typescript
function toCreate{Entity}(db: Prisma.TransactionClient) {
return (entity: Entity): ResultAsync<Entity, Error> => {
// implementation
}
}
`\`\`
Benefits:
- Allows dependency injection of database client
- Enables partial application
- Facilitates testing with mock databases
### Naming Conventions
- Repository functions: `to{Operation}{Entity}`
- Examples: `toCreateUser`, `toFindUserByID`, `toListUsers`
- Variable names for Prisma operations: `{entity}{Operation}`
- Examples: `userCreate`, `userFindUniqueOrThrow`, `userDelete`
## Type Definitions
### Entity ID Types
Entity IDs are branded types from `$lib/types/ids`:
`\`\`typescript
import type { UserID, ProductID, OrderID } from '$lib/types/ids'
`\`\`
### Prisma Types
`\`\`typescript
import type { Prisma } from '@repo/prisma/generated'
`\`\`
- `Prisma.TransactionClient`: Type for database client (supports transactions)
- `Prisma.{Model}CreateInput`: Input type for create operations
- `Prisma.{Model}UpdateInput`: Input type for update operations
## Common Patterns
### Transaction Support
All functions accept `Prisma.TransactionClient`, enabling transactional operations:
`\`\`typescript
await db.$transaction(async (tx) => {
const createUser = toCreateUser(tx)
const user = await createUser(newUser)
// ... other operations in transaction
})
`\`\`
### Result Mapping
ResultAsync supports functional transformations:
`\`\`typescript
// Map successful result
return userCreate({ data }).map(buildUserFromModel)
// Chain operations
return userCreate({ data })
.andThen((user) => toAssignRole(tx)(user.id, roleId))
`\`\`
## Best Practices
1. **Always use context for read operations**: Ensures proper tenant isolation
2. **Use `findUniqueOrThrow` for single entity queries**: Clear error handling for missing entities
3. **Convert between domain and database models**: Keep domain logic separate from database structure
4. **Leverage ResultAsync**: Avoid try-catch blocks, use functional error handling
5. **Keep repository functions pure**: No side effects beyond database operations
6
assets/repository-template.ts
import type { Prisma } from '@repo/prisma/generated'
import { fromAsyncThrowable, type ResultAsync } from 'neverthrow'
import {
build{{EntityName}}FromModel,
type {{EntityName}},
toCreate{{EntityName}}Input,
toUpdate{{EntityName}}Input,
} from '$lib/features/{{entityName}}/entities/{{entityName}}'
import type { AppContext } from '$lib/types/context'
import type { {{EntityID}} } from '$lib/types/ids'
export function toCreate{{EntityName}}(db: Prisma.TransactionClient) {
return ({{entityName}}: {{EntityName}}): ResultAsync<{{EntityName}}, Error> => {
const {{entityName}}Create = fromAsyncThrowable(
db.{{entityName}}.create,
(e) => new Error((e as Error).message),
)
return {{entityName}}Create({
data: toCreate{{EntityName}}Input({{entityName}}),
})
}
}
export function toUpdate{{EntityName}}(db: Prisma.TransactionClient) {
return ({{entityName}}: {{EntityName}}): ResultAsync<{{EntityName}}, Error> => {
const {{entityName}}Update = fromAsyncThrowable(
db.{{entityName}}.update,
(e) => new Error((e as Error).message),
)
return {{entityName}}Update({
where: { id: {{entityName}}.id },
data: toUpdate{{EntityName}}Input({{entityName}}),
})
}
}
export function toSave{{EntityName}}(db: Prisma.TransactionClient) {
return ({{entityName}}: {{EntityName}}): ResultAsync<{{EntityName}}, Error> => {
const {{entityName}}Upsert = fromAsyncThrowable(
db.{{entityName}}.upsert,
(e) => new Error((e as Error).message),
)
return {{entityName}}Upsert({
where: { id: {{entityName}}.id },
create: toCreate{{EntityName}}Input({{entityName}}),
update: toUpdate{{EntityName}}Input({{entityName}}),
})
}
}
export function toFind{{EntityName}}ByID(ctx: AppContext, db: Prisma.TransactionClient) {
return (id: {{EntityID}}): ResultAsync<{{EntityName}}, Error> => {
const {{entityName}}FindUniqueOrThrow = fromAsyncThrowable(
db.{{entityName}}.findUniqueOrThrow,
(e) => new Error((e as Error).message),
)
return {{entityName}}FindUniqueOrThrow({
where: {
id,{{#if useMultiTenancy}}
organizationId: ctx.organizationId,
teamId: ctx.teamId,{{/if}}
},
})
}
}
export function toList{{EntityName}}s(ctx: AppContext, db: Prisma.TransactionClient) {
return (): ResultAsync<{{EntityName}}[], Error> => {
const {{entityName}}FindMany = fromAsyncThrowable(
db.{{entityName}}.findMany,
(e) => new Error((e as Error).message),
)
return {{entityName}}FindMany({
where: {{{#if useMultiTenancy}}
organizationId: ctx.organizationId,
teamId: ctx.teamId,{{/if}}
},
})
}
}
export function toDelete{{EntityName}}ByID(ctx: AppContext, db: Prisma.TransactionClient) {
return (id: {{EntityID}}): ResultAsync<void, Error> => {
const {{entityName}}Delete = fromAsyncThrowable(
db.{{entityName}}.delete,
(e) => new Error((e as Error).message),
)
return {{entityName}}Delete({
where: {
id,{{#if useMultiTenancy}}
organizationId: ctx.organizationId,
teamId: ctx.teamId,{{/if}}
},
})
}
}
実際にスキルを使ってみる
作成したスキルを使って別のレポジトリコードを作ってみましょう。プロンプトは以下のようにしました。
repository-generatorを使って、TaskRepositoryを作成してください
作成されたファイル。かねがね良さそうですね!
taskRepository.ts
import type { Prisma } from '@repo/prisma/generated'
import { fromAsyncThrowable, type ResultAsync } from 'neverthrow'
import {
buildTaskFromModel,
type Task,
toCreateTaskInput,
toUpdateTaskInput,
} from '$lib/features/task/entities/task'
import type { AppContext } from '$lib/types/context'
import type { TaskID } from '$lib/types/ids'
export function toCreateTask(db: Prisma.TransactionClient) {
return (task: Task): ResultAsync<Task, Error> => {
const taskCreate = fromAsyncThrowable(
db.task.create,
(e) => new Error((e as Error).message),
)
return taskCreate({
data: toCreateTaskInput(task),
}).map(() => task)
}
}
export function toUpdateTask(db: Prisma.TransactionClient) {
return (task: Task): ResultAsync<Task, Error> => {
const taskUpdate = fromAsyncThrowable(
db.task.update,
(e) => new Error((e as Error).message),
)
return taskUpdate({
where: { id: task.id },
data: toUpdateTaskInput(task),
}).map(() => task)
}
}
export function toSaveTask(db: Prisma.TransactionClient) {
return (task: Task): ResultAsync<Task, Error> => {
const taskUpsert = fromAsyncThrowable(
db.task.upsert,
(e) => new Error((e as Error).message),
)
return taskUpsert({
where: { id: task.id },
create: toCreateTaskInput(task),
update: toUpdateTaskInput(task),
}).map(() => task)
}
}
export function toFindTaskByID(ctx: AppContext, db: Prisma.TransactionClient) {
return (id: TaskID): ResultAsync<Task, Error> => {
const taskFindUniqueOrThrow = fromAsyncThrowable(
db.task.findUniqueOrThrow,
(e) => new Error((e as Error).message),
)
return taskFindUniqueOrThrow({
where: {
id,
organizationId: ctx.organizationId,
teamId: ctx.teamId,
},
}).map((m) => buildTaskFromModel(m))
}
}
export function toListTasks(ctx: AppContext, db: Prisma.TransactionClient) {
return (): ResultAsync<Task[], Error> => {
const taskFindMany = fromAsyncThrowable(
db.task.findMany,
(e) => new Error((e as Error).message),
)
return taskFindMany({
where: {
organizationId: ctx.organizationId,
teamId: ctx.teamId,
},
}).map((rows) => rows.map((r) => buildTaskFromModel(r)))
}
}
export function toDeleteTaskByID(ctx: AppContext, db: Prisma.TransactionClient) {
return (id: TaskID): ResultAsync<void, Error> => {
const taskDelete = fromAsyncThrowable(
db.task.delete,
(e) => new Error((e as Error).message),
)
return taskDelete({
where: {
id,
organizationId: ctx.organizationId,
teamId: ctx.teamId,
},
}).map(() => undefined)
}
}
まとめ / 感想
Claude Codeのskill creatorを使って、過去に書いたコードをスキル化し、再利用する方法について紹介しました!
Claude Codeのスキルを使うことでプロンプトにコードを仕込むというよりかはテンプレート用のファイルを作成でき、そこからコード生成することが可能になりました。
実は今回ぐらいのレベルだとUserRepositoryを参考にTaskRepositoryを作成してくださいというのでも同様のものができます。一方でUserRepositoryにUserRepository独自の関数などがあった場合に都度削除する必要もあったりします。
定期的にGitのDiffや特定のPRからスキルを作っておくことで今後の開発速度と品質の向上にうまく貢献できるようになると思います。
「プロダクトの力で、豊かな暮らしをつくる」をミッションに、法人向けに生成AIのPoC、コンサルティング〜開発を支援する事業を展開しております。 エンジニア募集しています。カジュアル面談応募はこちらから: herp.careers/careers/companies/explaza
Discussion