## 概要
`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.3 KiB
Coding Stance
Prioritize correctness over speed, and code accuracy over ease of implementation.
Principles
| Principle | Criteria |
|---|---|
| Simple > Easy | Prioritize readability over writability |
| DRY | Extract after 3 repetitions |
| Comments | Why only. Never write What/How |
| Function size | One function, one responsibility. ~30 lines |
| File size | ~300 lines as a guideline. Be flexible depending on the task |
| Boy Scout | Leave touched areas a little better than you found them |
| Fail Fast | Detect errors early. Never swallow them |
No Fallbacks or Default Arguments
Do not write code that obscures the flow of values. Code where you must trace logic to understand a value is bad code.
Prohibited Patterns
| Pattern | Example | Problem |
|---|---|---|
| Fallback for required data | user?.id ?? 'unknown' |
Processing continues in a state that should error |
| Default argument abuse | function f(x = 'default') where all call sites omit it |
Impossible to tell where the value comes from |
| Null coalesce with no way to pass | options?.cwd ?? process.cwd() with no path from callers |
Always falls back (meaningless) |
| Return empty value in try-catch | catch { return ''; } |
Swallows the error |
| Silent skip on inconsistent values | if (a !== expected) return undefined |
Config errors silently ignored at runtime |
Correct Implementation
// ❌ Prohibited - Fallback for required data
const userId = user?.id ?? 'unknown'
processUser(userId) // Processing continues with 'unknown'
// ✅ Correct - Fail Fast
if (!user?.id) {
throw new Error('User ID is required')
}
processUser(user.id)
// ❌ Prohibited - Default argument where all call sites omit
function loadConfig(path = './config.json') { ... }
// All call sites: loadConfig() ← path is never passed
// ✅ Correct - Make it required and pass explicitly
function loadConfig(path: string) { ... }
// Call site: loadConfig('./config.json') ← explicit
// ❌ Prohibited - Null coalesce with no way to pass
class Engine {
constructor(config, options?) {
this.cwd = options?.cwd ?? process.cwd()
// Problem: if there's no path to pass cwd via options, it always falls back to process.cwd()
}
}
// ✅ Correct - Allow passing from the caller
function createEngine(config, cwd: string) {
return new Engine(config, { cwd })
}
Acceptable Cases
- Default values when validating external input (user input, API responses)
- Optional values in config files (explicitly designed to be omittable)
- Only some call sites use the default argument (prohibited if all callers omit it)
Decision Criteria
- Is it required data? → Throw an error, do not fall back
- Do all call sites omit it? → Remove the default, make it required
- Is there a path to pass the value from above? → If not, add a parameter or field
- Do related values have invariants? → Cross-validate at load/setup time
Abstraction
Think Before Adding Conditionals
- Does the same condition exist elsewhere? → Abstract with a pattern
- Will more branches be added? → Use Strategy/Map pattern
- Branching on type? → Replace with polymorphism
// ❌ Growing conditionals
if (type === 'A') { ... }
else if (type === 'B') { ... }
else if (type === 'C') { ... } // Yet another branch
// ✅ Abstract with a Map
const handlers = { A: handleA, B: handleB, C: handleC };
handlers[type]?.();
Keep Abstraction Levels Consistent
Within a single function, keep operations at the same granularity. Extract detailed operations into separate functions. Do not mix "what to do" with "how to do it."
// ❌ Mixed abstraction levels
function processOrder(order) {
validateOrder(order); // High level
const conn = pool.getConnection(); // Low-level detail
conn.query('INSERT...'); // Low-level detail
}
// ✅ Consistent abstraction levels
function processOrder(order) {
validateOrder(order);
saveOrder(order); // Details are hidden
}
Follow Language and Framework Conventions
- Write Pythonic Python, idiomatic Kotlin, etc.
- Use framework-recommended patterns
- Prefer standard approaches over custom ones
- When unsure, research. Do not implement based on guesses
Interface Design
Design interfaces from the consumer's perspective. Do not expose internal implementation details.
| Principle | Criteria |
|---|---|
| Consumer perspective | Do not force things the caller does not need |
| Separate configuration from execution | Decide "what to use" at setup time, keep the execution API simple |
| No method proliferation | Absorb differences through configuration, not multiple methods doing the same thing |
// ❌ Method proliferation — pushing configuration differences onto the caller
interface NotificationService {
sendEmail(to, subject, body)
sendSMS(to, message)
sendPush(to, title, body)
sendSlack(channel, message)
}
// ✅ Separate configuration from execution
interface NotificationService {
setup(config: ChannelConfig): Channel
}
interface Channel {
send(message: Message): Promise<Result>
}
Leaky Abstraction
If a specific implementation appears in a generic layer, the abstraction is leaking. The generic layer should only know interfaces; branching should be absorbed by implementations.
// ❌ Specific implementation imports and branching in generic layer
import { uploadToS3 } from '../aws/s3.js'
if (config.storage === 's3') {
return uploadToS3(config.bucket, file, options)
}
// ✅ Generic layer uses interface only. Unsupported cases error at creation time
const storage = createStorage(config)
return storage.upload(file, options)
Structure
Criteria for Splitting
- Has its own state → Separate
- UI/logic exceeding 50 lines → Separate
- Has multiple responsibilities → Separate
Dependency Direction
- Upper layers → Lower layers (reverse direction prohibited)
- Fetch data at the root (View/Controller) and pass it down
- Children do not know about their parents
State Management
- Confine state to where it is used
- Children do not modify state directly (notify parents via events)
- State flow is unidirectional
Error Handling
Centralize error handling. Do not scatter try-catch everywhere.
// ❌ Scattered try-catch
async function createUser(data) {
try {
const user = await userService.create(data)
return user
} catch (e) {
console.error(e)
throw new Error('Failed to create user')
}
}
// ✅ Centralized handling at the upper layer
// Catch collectively at the Controller/Handler layer
// Or handle via @ControllerAdvice / ErrorBoundary
async function createUser(data) {
return await userService.create(data) // Let exceptions propagate up
}
Error Handling Placement
| Layer | Responsibility |
|---|---|
| Domain/Service layer | Throw exceptions on business rule violations |
| Controller/Handler layer | Catch exceptions and convert to responses |
| Global handler | Handle common exceptions (NotFound, auth errors, etc.) |
Conversion Placement
Place conversion methods on the DTO side.
// ✅ Conversion methods on Request/Response DTOs
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)
Conversion direction:
Request → toInput() → UseCase/Service → Output → Response.from()
Shared Code Decisions
Rule of Three
- 1st occurrence: Write it inline
- 2nd occurrence: Do not extract yet (observe)
- 3rd occurrence: Consider extracting
Should Be Shared
- Same logic in 3+ places
- Same style/UI pattern
- Same validation logic
- Same formatting logic
Should Not Be Shared
- Similar but subtly different (forced generalization adds complexity)
- Used in only 1-2 places
- Based on "might need it in the future" predictions
// ❌ Over-generalization
function formatValue(value, type, options) {
if (type === 'currency') { ... }
else if (type === 'date') { ... }
else if (type === 'percentage') { ... }
}
// ✅ Separate functions by purpose
function formatCurrency(amount: number): string { ... }
function formatDate(date: Date): string { ... }
function formatPercentage(value: number): string { ... }
Prohibited
- Fallbacks are prohibited by default - Do not write fallbacks using
?? 'unknown',|| 'default', or swallowing viatry-catch. Propagate errors upward. If absolutely necessary, add a comment explaining why - Explanatory comments - Express intent through code. Do not write What/How comments
- Unused code - Do not write "just in case" code
- any type - Do not break type safety
- Direct mutation of objects/arrays - Create new instances with spread operators
- console.log - Do not leave in production code
- Hardcoded secrets
- Scattered try-catch - Centralize error handling at the upper layer
- Unsolicited backward compatibility / legacy support - Not needed unless explicitly instructed
- Workarounds that bypass safety mechanisms - If the root fix is correct, no additional bypass is needed