| name | phase-2-convention |
| context | fork |
| classification | workflow |
| classification-reason | Process automation persists regardless of model advancement |
| deprecation-risk | none |
| effort | medium |
| description | Define coding rules, conventions, and standards for AI collaboration.
Triggers: convention, coding style, lint, rules, ์ฝ๋ฉ ๊ท์น, ์ปจ๋ฒค์
.
|
| agent | bkit:pipeline-guide |
| allowed-tools | ["Read","Write","Glob","Grep"] |
| user-invocable | false |
| imports | ["${PLUGIN_ROOT}/templates/pipeline/phase-2-convention.template.md","${PLUGIN_ROOT}/templates/shared/naming-conventions.md"] |
| next-skill | phase-3-mockup |
| pdca-phase | plan |
| task-template | [Phase-2] {feature} |
Phase 2: Coding Convention
Define code writing rules
Purpose
Maintain consistent code style. Especially important when collaborating with AI - clarify what style AI should use when writing code.
What to Do in This Phase
- Naming Rules: Variables, functions, files, folder names
- Code Style: Indentation, quotes, semicolons, etc.
- Structure Rules: Folder structure, file separation criteria
- Pattern Definition: Frequently used code patterns
Deliverables
Project Root/
โโโ CONVENTIONS.md # Full conventions
โโโ docs/01-plan/
โโโ naming.md # Naming rules
โโโ structure.md # Structure rules
PDCA Application
- Plan: Identify necessary convention items
- Design: Design detailed rules
- Do: Write convention documents
- Check: Review consistency/practicality
- Act: Finalize and proceed to Phase 3
Level-wise Application
| Level | Application Level |
|---|
| Starter | Basic (essential rules only) |
| Dynamic | Extended (including API, state management) |
| Enterprise | Extended (per-service rules) |
Core Convention Items
Naming
- Components: PascalCase
- Functions: camelCase
- Constants: UPPER_SNAKE_CASE
- Files: kebab-case or PascalCase
Folder Structure
src/
โโโ components/ # Reusable components
โโโ features/ # Feature modules
โโโ hooks/ # Custom hooks
โโโ utils/ # Utilities
โโโ types/ # Type definitions
Environment Variable Convention
Why Define at Design Stage?
โ Organizing env vars just before deployment
โ Missing variables, naming inconsistency, deployment delays
โ
Establish convention at design stage
โ Consistent naming, clear categorization, fast deployment
Environment Variable Naming Rules
| Prefix | Purpose | Exposure Scope | Example |
|---|
NEXT_PUBLIC_ | Client-exposed | Browser | NEXT_PUBLIC_API_URL |
DB_ | Database | Server only | DB_HOST, DB_PASSWORD |
API_ | External API keys | Server only | API_STRIPE_SECRET |
AUTH_ | Authentication | Server only | AUTH_SECRET, AUTH_GOOGLE_ID |
SMTP_ | Email service | Server only | SMTP_HOST, SMTP_PASSWORD |
STORAGE_ | File storage | Server only | STORAGE_S3_BUCKET |
โ ๏ธ Security Principles
- Never expose anything except NEXT_PUBLIC_* to client
- API keys and passwords must be server-only variables
- Never commit sensitive info in .env files
.env File Structure
Project Root/
โโโ .env.example # Template (in Git, values empty)
โโโ .env.local # Local development (Git ignored)
โโโ .env.development # Development env defaults
โโโ .env.staging # Staging env defaults
โโโ .env.production # Production defaults (no sensitive info)
โโโ .env.test # Test environment
.env.example Template
NODE_ENV=development
NEXT_PUBLIC_APP_URL=http://localhost:3000
DB_HOST=
DB_PORT=5432
DB_NAME=
DB_USER=
DB_PASSWORD=
AUTH_SECRET=
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=
NEXT_PUBLIC_API_URL=
API_STRIPE_SECRET=
SMTP_HOST=
SMTP_USER=
SMTP_PASSWORD=
Environment-wise Value Classification
| Variable Type | .env.example | .env.local | CI/CD Secrets |
|---|
| App URL | Template | Local value | Per-env value |
| API endpoints | Template | Local/dev | Per-env value |
| DB password | Empty | Local value | โ
Secrets |
| API keys | Empty | Test key | โ
Secrets |
| JWT Secret | Empty | Local value | โ
Secrets |
Environment Variable Validation
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
NODE_ENV: z.enum(['development', 'staging', 'production']).default('development'),
NEXT_PUBLIC_APP_URL: z.string().url(),
});
export const env = envSchema.parse(process.env);
Environment Variable Checklist
Clean Architecture Principles
Why Define at Design Stage?
Clean Architecture = Code resilient to change
โ Developing without architecture
โ Spaghetti code, multiple file changes for each modification
โ
Define layers at design stage
โ Separation of concerns, easy testing, easy maintenance
4-Layer Architecture (Recommended)
src/
โโโ presentation/ # or app/, pages/
โ โโโ components/ # UI components
โ โโโ hooks/ # State management hooks
โ โโโ pages/ # Page components
โ
โโโ application/ # or services/, features/
โ โโโ use-cases/ # Business use cases
โ โโโ services/ # API service wrappers
โ
โโโ domain/ # or types/, entities/
โ โโโ entities/ # Domain entities
โ โโโ types/ # Type definitions
โ โโโ constants/ # Domain constants
โ
โโโ infrastructure/ # or lib/, api/
โโโ api/ # API clients
โโโ db/ # Database connections
โโโ external/ # External services
Layer Responsibilities and Rules
| Layer | Responsibility | Can Depend On | Cannot Depend On |
|---|
| Presentation | UI rendering, user events | Application, Domain | Infrastructure directly |
| Application | Business logic orchestration | Domain, Infrastructure | Presentation |
| Domain | Core business rules, types | Nothing (independent) | All external layers |
| Infrastructure | External system connections | Domain | Application, Presentation |
Dependency Rule
import { apiClient } from '@/lib/api/client';
export function UserList() {
const users = apiClient.get('/users');
}
import { userService } from '@/services/user.service';
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: userService.getList,
});
}
import { useUsers } from '@/hooks/useUsers';
export function UserList() {
const { data: users } = useUsers();
}
File Import Rules
import { User } from '@/domain/types';
import { useUsers } from '@/hooks/useUsers';
import { userService } from '@/services/user';
import { User } from '@/domain/types';
import { apiClient } from '@/lib/api/client';
import { User } from '@/domain/types';
import { apiClient } from '@/lib/api/client';
import { Button } from '@/components/ui/button';
import { useUsers } from '@/hooks/useUsers';
Level-wise Application
| Level | Architecture Application |
|---|
| Starter | Simple structure (components, lib) |
| Dynamic | 3-4 layer separation (recommended structure) |
| Enterprise | Strict layer separation + DI container |
Starter Level Folder Structure
src/
โโโ components/ # UI components
โโโ lib/ # Utilities, API
โโโ types/ # Type definitions
Dynamic Level Folder Structure
src/
โโโ components/ # Presentation
โ โโโ ui/
โโโ features/ # Feature modules (Application + Presentation)
โ โโโ auth/
โ โโโ product/
โโโ hooks/ # Presentation (state management)
โโโ services/ # Application
โโโ types/ # Domain
โโโ lib/ # Infrastructure
โโโ api/
Enterprise Level Folder Structure
src/
โโโ presentation/
โ โโโ components/
โ โโโ hooks/
โ โโโ pages/
โโโ application/
โ โโโ use-cases/
โ โโโ services/
โโโ domain/
โ โโโ entities/
โ โโโ types/
โโโ infrastructure/
โโโ api/
โโโ db/
Phase Connection
Conventions defined in this Phase are verified in later Phases:
| Definition (Phase 2) | Verification (Phase 8) |
|---|
| Naming rules | Naming consistency check |
| Folder structure | Structure consistency check |
| Environment variable convention | Env var naming check |
| Clean architecture principles | Dependency direction check |
Template
See templates/pipeline/phase-2-convention.template.md
Next Phase
Phase 3: Mockup Development โ Rules are set, now rapid prototyping
6. Reusability Principles
6.1 Function Design
Creating Generic Functions
function formatUserName(user: User) {
return `${user.firstName} ${user.lastName}`
}
function formatFullName(firstName: string, lastName: string) {
return `${firstName} ${lastName}`
}
formatFullName(user.firstName, user.lastName)
formatFullName(author.first, author.last)
Parameter Generalization
function calculateOrderTotal(order: Order) {
return order.items.reduce((sum, item) => sum + item.price, 0)
}
interface HasPrice { price: number }
function calculateTotal<T extends HasPrice>(items: T[]) {
return items.reduce((sum, item) => sum + item.price, 0)
}
calculateTotal(order.items)
calculateTotal(cart.products)
calculateTotal(invoice.lineItems)
6.2 Component Design
Composable Components
function UserCard({ user }: { user: User }) {
return (
<div className="card">
<img src={user.avatar} />
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)
}
function Card({ children, className }: CardProps) {
return <div className={cn("card", className)}>{children}</div>
}
function Avatar({ src, alt }: AvatarProps) {
return <img src={src} alt={alt} className="avatar" />
}
<Card>
<Avatar src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.email}</p>
</Card>
Props Extensibility
interface ButtonProps {
label: string
onClick: () => void
}
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'outline' | 'ghost'
size?: 'sm' | 'md' | 'lg'
}
<Button type="submit" disabled={isLoading}>
Save
</Button>
6.3 Extraction Criteria
When to Extract as Function
1. Same logic used 2+ times
2. Logic is complex enough to need a name
3. Logic that needs testing
4. Can be used in other files
When to Extract as Component
1. Same UI pattern repeats
2. Has independent state
3. Is a reusable unit
4. JSX over 50 lines
7. Extensibility Principles
7.1 Configuration-Based Design
function getStatusColor(status: string) {
if (status === 'active') return 'green'
if (status === 'pending') return 'yellow'
if (status === 'error') return 'red'
return 'gray'
}
const STATUS_CONFIG = {
active: { color: 'green', label: 'Active' },
pending: { color: 'yellow', label: 'Pending' },
error: { color: 'red', label: 'Error' },
} as const
function getStatusConfig(status: keyof typeof STATUS_CONFIG) {
return STATUS_CONFIG[status] ?? { color: 'gray', label: status }
}
7.2 Strategy Pattern
function processPayment(method: string, amount: number) {
switch (method) {
case 'card':
break
case 'bank':
break
}
}
interface PaymentStrategy {
process(amount: number): Promise<Result>
}
const paymentStrategies: Record<string, PaymentStrategy> = {
card: new CardPayment(),
bank: new BankTransfer(),
}
function processPayment(method: string, amount: number) {
const strategy = paymentStrategies[method]
if (!strategy) throw new Error(`Unknown method: ${method}`)
return strategy.process(amount)
}
7.3 Plugin Structure
interface Plugin {
name: string
init(): void
execute(data: unknown): unknown
}
class PluginManager {
private plugins: Plugin[] = []
register(plugin: Plugin) {
this.plugins.push(plugin)
}
executeAll(data: unknown) {
return this.plugins.reduce(
(result, plugin) => plugin.execute(result),
data
)
}
}
8. Duplication Prevention Checklist
Before Writing Code
After Writing Code