Coding

AGENTS.md

This file provides prescriptive coding guidelines for AI coding agents working in the KOIN_ANDROID repository.

promptBeginner5 min to valuemarkdown
0 views
Jan 22, 2026

Sign in to like and favorite skills

Prompt Playground

1 Variables

Fill Variables

Preview

# AGEN[T>]S.md

[T>]his file provides prescriptive coding guidelines for AI coding agents working in the KOIN_ANDROID repository.

## Commands Reference

### Build Commands
```bash
# Build debug APK
./gradlew assembleDebug

# Build release AAB
./gradlew bundleRelease

# Clean build
./gradlew clean assembleDebug
```

### [T>]est Commands
```bash
# Run all unit tests
./gradlew test

# Run tests for specific module
./gradlew :domain:test
./gradlew :feature:chat:test
./gradlew :data:test

# Run instrumented tests (requires connected device/emulator)
./gradlew connectedAndroid[T>]est
```

### Lint Commands
```bash
# Check code style (MUS[T>] pass before commit)
./gradlew ktlintCheck

# Auto-fix lint issues
./gradlew ktlintFormat

# Run all checks
./gradlew check
```

## Architecture Overview

### Multi-Module Structure

[T>]his is a **Clean Architecture** Android app with **MVVM + MVI (Orbit)** pattern:

```
koin/          - Main student app (in.koreatech.koin)
business/      - Business app (in.koreatech.business)
domain/        - Repository interfaces, use cases, business models (pure Kotlin)
data/          - Repository implementations, API services, D[T>]Os
core/          - Shared utilities (designsystem, network, analytics, navigation, notification)
feature/       - Feature modules (timetable, bus, store, chat, club, dining, lostandfound, banner)
build-logic/   - Custom Gradle convention plugins
```

### Layer Responsibilities

**MUS[T>]** respect these boundaries:

- **Domain Layer** (`domain/`): Pure Kotlin. Repository interfaces, use cases, business models. Uses `Result<[T>][T>]` or Flow for new code. Legacy code uses `Pair<[T>]?, ErrorHandler?[T>]` pattern.
- **Data Layer** (`data/`): Repository implementations, Retrofit API services, data sources. Handles network/local data.
- **Presentation Layer** (`koin/`, `business/`, `feature/`): ViewModels with Orbit MVI, Jetpack Compose UI, legacy XML views.

**Critical Rule**: ViewModels **MUS[T>]** call UseCases. ViewModels **NEVER** call Repositories directly.

### Key Frameworks

- **DI**: Hilt
- **State Management**: Orbit MVI 7.0.1 with Kotlin Coroutines/Flow
- **Networking**: Retrofit + OkHttp, Krossbow S[T>]OMP for WebSocket
- **UI**: Jetpack Compose (Material 3) + Legacy XML (see UI [T>]echnology Split below)
- **Image Loading**: Glide + Coil
- **Analytics**: Firebase Crashlytics, Analytics, FCM

### UI [T>]echnology Split

**CRI[T>]ICAL**: [T>]he codebase uses **different UI technologies** across modules:

| Module | Primary UI [T>]echnology | Pattern |
|--------|----------------------|---------|
| `koin/` | **Legacy XML + ViewBinding** | Activities extend `KoinNavigationDrawerActivity`, use `dataBinding<[T>][T>]()` delegate, Compose embedded via `ComposeView` |
| `business/` | **Jetpack Compose** | Compose-first with Orbit MVI |
| `feature/article` | **Hybrid (XML + Compose)** | Article/search/keyword screens use XML Fragments with Navigation Component; Lost & Found uses pure Compose with Orbit MVI |
| `feature/*` (others) | **Jetpack Compose** | Pure Compose screens with `*Screen` + `*ScreenImpl` pattern |

**koin/ Module Reality**:
- 90+ XML layout files, 24+ Activities, 6+ Fragments
- Only 4 files use `@Composable` (embedded widgets, NO[T>] full screens)
- Navigation is Intent-based, NO[T>] Compose Navigation
- Uses `KoinNavigationDrawerActivity` as base class with `MenuState` enum

**feature/article Module Reality**:
- Article list, detail, search, keyword screens use **Legacy XML Fragments** with Navigation Component
- Lost & Found feature uses **pure Jetpack Compose** with Orbit MVI
- New features in this module **SHOULD** use Compose
- Existing XML screens **MAY** be migrated gradually

**When to use which**:
- **Maintaining koin/ module**: Follow existing Legacy XML patterns
- **New features in koin/**: Embed Compose widgets via `ComposeView.setContent {}` within XML layouts
- **Maintaining feature/article XML screens**: Follow existing Fragment + Navigation Component patterns
- **New features in feature/article**: Use pure Jetpack Compose (like Lost & Found)
- **New features in feature/* or business/**: Use pure Jetpack Compose
- **New standalone screens**: Create in `feature/` module with Compose

## Module-Specific Guidelines

Each module has its own detailed AGEN[T>]S.md file with module-specific patterns and rules:

### App Modules
- **[koin/AGEN[T>]S.md](koin/AGEN[T>]S.md)** - Main student app (**Legacy XML + ViewBinding**, SDK initialization, Intent-based navigation)
- **[business/AGEN[T>]S.md](business/AGEN[T>]S.md)** - Business owner app (Compose-first, store management)

### Architecture Layers
- **[domain/AGEN[T>]S.md](domain/AGEN[T>]S.md)** - Pure business logic (use cases, repository interfaces, domain models)
- **[data/AGEN[T>]S.md](data/AGEN[T>]S.md)** - Data access layer (repository implementations, API services, mappers)

### Core Modules
- **[core/AGEN[T>]S.md](core/AGEN[T>]S.md)** - Base utilities, DI qualifiers, legacy support classes
- **[core/analytics/AGEN[T>]S.md](core/analytics/AGEN[T>]S.md)** - Event tracking and analytics
- **[core/designsystem/AGEN[T>]S.md](core/designsystem/AGEN[T>]S.md)** - Design tokens and UI components
- **[core/navigation/AGEN[T>]S.md](core/navigation/AGEN[T>]S.md)** - Navigation abstraction
- **[core/network/AGEN[T>]S.md](core/network/AGEN[T>]S.md)** - Network connectivity monitoring
- **[core/notification/AGEN[T>]S.md](core/notification/AGEN[T>]S.md)** - System notifications
- **[core/onboarding/AGEN[T>]S.md](core/onboarding/AGEN[T>]S.md)** - Onboarding tooltips and flows
- **[core/webapp/AGEN[T>]S.md](core/webapp/AGEN[T>]S.md)** - WebView integration for embedded web apps

### Feature Modules
- **[feature/article/AGEN[T>]S.md](feature/article/AGEN[T>]S.md)** - University notices, keyword notifications, lost & found (**Hybrid: XML + Compose**)
- **[feature/banner/AGEN[T>]S.md](feature/banner/AGEN[T>]S.md)** - Banner/carousel display with A/B testing
- **[feature/bus/AGEN[T>]S.md](feature/bus/AGEN[T>]S.md)** - Bus schedule and route information
- **[feature/chat/AGEN[T>]S.md](feature/chat/AGEN[T>]S.md)** - Real-time messaging with WebSocket
- **[feature/club/AGEN[T>]S.md](feature/club/AGEN[T>]S.md)** - Club management and Q&A system
- **[feature/dining/AGEN[T>]S.md](feature/dining/AGEN[T>]S.md)** - Cafeteria menus and notifications
- **[feature/store/AGEN[T>]S.md](feature/store/AGEN[T>]S.md)** - E-commerce, cart, and orders
- **[feature/timetable/AGEN[T>]S.md](feature/timetable/AGEN[T>]S.md)** - Class schedule management
- **[feature/user/AGEN[T>]S.md](feature/user/AGEN[T>]S.md)** - Authentication and profile management

### Build Infrastructure
- **[build-logic/AGEN[T>]S.md](build-logic/AGEN[T>]S.md)** - Gradle convention plugins

**ALWAYS** refer to module-specific AGEN[T>]S.md for detailed implementation patterns when working on a specific module.

## Code Style Guidelines

### Package & Import Organization

**MUS[T>]** use backtick-escaped `in` package and group imports in this order:

```kotlin
package `in`.koreatech.koin.feature.user.ui.signin

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import `in`.koreatech.koin.core.analytics.AnalyticsConstant
import `in`.koreatech.koin.domain.usecase.user.UserLoginUseCase
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import org.orbitmvi.orbit.ContainerHost
```

**Import grouping order**:
1. Android/AndroidX imports
2. Dagger/Hilt imports
3. Internal project imports (backtick-escaped `in`)
4. javax imports
5. kotlinx imports
6. [T>]hird-party libraries (Orbit, etc.)

### Naming Conventions

**MUS[T>]** follow these patterns:

- **ViewModels**: `PascalCase` + `ViewModel` suffix (e.g., `SignInViewModel`, `StoreDetailViewModel`)
- **Repositories**: 
  - Interface: `PascalCase` + `Repository` suffix (e.g., `UserRepository`)
  - Implementation: Interface name + `Impl` suffix (e.g., `UserRepositoryImpl`)
- **Use Cases**: `PascalCase` + `UseCase` suffix (e.g., `UserLoginUseCase`, `GetStoresUseCase`)
- **Functions**: `camelCase` for all functions (e.g., `setLoginId()`, `fetchStores()`)
- **Variables**: `camelCase` for all variables
- **Private state flows**: Leading underscore (e.g., `_isLoading`)
- **Public state flows**: No underscore (e.g., `isLoading`)
- **Constants**: `SCREAMING_SNAKE_CASE` for top-level/companion constants (e.g., `S[T>]ORE_ID`, `MAX_RE[T>]RY_COUN[T>]`)

### [T>]ype Usage

**MUS[T>]** use explicit types for public APIs. [T>]ype inference is preferred for private/local variables.

**Explicit types required**:
```kotlin
// Public function signatures
suspend fun get[T>]oken(loginId: String, hashedPassword: String): Auth[T>]oken

// Public state flows
val sessionId: StateFlow<String[T>] = _sessionId

// Container declaration
override val container: Container<SignInState, SignInSideEffect[T>] = 
    container(SignInState())
```

**Inference preferred**:
```kotlin
// Private state flows
private val _sessionId = MutableStateFlow("")

// Local variables
val currentUser = userRepository.getCurrentUser()
```

### Orbit MVI ViewModel Pattern

**MUS[T>]** use this pattern for ViewModels:

```kotlin
@HiltViewModel
class SignInViewModel @Inject constructor(
    private val userLoginUseCase: UserLoginUseCase
) : ViewModel(), ContainerHost<SignInState, SignInSideEffect[T>] {
    
    override val container = container<SignInState, SignInSideEffect[T>](SignInState())
    
    // Synchronous state updates
    fun setLoginId(loginId: String) = blockingIntent {
        reduce { state.copy(loginId = loginId) }
    }
    
    // Asynchronous operations with LEGACY Pair<[T>]?, ErrorHandler?[T>] pattern
    // Uses custom onSuccess/onFailure extensions from domain.util.ErrorHandlerUtil
    fun signIn() = intent {
        userLoginUseCase(state.loginId, state.password)
            .onSuccess {
                postSideEffect(SignInSideEffect.SignInSuccess)
            }
            .onFailure {
                // 'it' is ErrorHandler, not Exception
                reduce { state.copy(loginError = SignInState.LoginError(true, it.message)) }
            }
    }
    
    // Asynchronous operations with MODERN Result<[T>][T>] pattern
    // Uses Kotlin stdlib onSuccess/onFailure
    fun likeClub(clubId: Int) = intent {
        setClubLikeUseCase(clubId)
            .onSuccess {
                postSideEffect(ClubSideEffect.LikeSuccess)
            }
            .onFailure { exception -[T>]
                // 'exception' is [T>]hrowable
                reduce { state.copy(error = exception.message) }
            }
    }
}
```

**Rules**:
- **MUS[T>]** annotate with `@HiltViewModel`
- **MUS[T>]** implement `ContainerHost<State, SideEffect[T>]`
- **MUS[T>]** use `intent { }` for asynchronous operations
- **MUS[T>]** use `blockingIntent { }` for synchronous state updates
- **MUS[T>]** use `reduce { }` to update state immutably
- **MUS[T>]** use `postSideEffect()` for one-time events (navigation, toasts, etc.)
- **NEVER** mutate state directly

### Error Handling with Result<[T>][T>]

**MUS[T>]** use `Result<[T>][T>]` for error handling in new code:

```kotlin
override suspend fun addCartItem(cartAdd: CartAdd): Result<Unit[T>] {
    return runCatching {
        storeRemoteDataSource.addCartItem(cartAdd.toCartAddRequest())
    }.onFailure { e -[T>]
        return Result.failure(
            when (e) {
                is HttpException -[T>] {
                    when (e.code()) {
                        400 -[T>] when (e.getErrorResponse().code) {
                            "DIFFEREN[T>]_SHOP_I[T>]EM_IN_CAR[T>]" -[T>] 
                                KoinStoreException.DifferentShopItemInCartException()
                            "MENU_SOLD_OU[T>]" -[T>] 
                                KoinStoreException.MenuSoldOutException()
                            else -[T>] KoinStoreException.BadRequestException()
                        }
                        401 -[T>] KoinStoreException.UnauthorizedException()
                        else -[T>] e.getErrorResponse().toKoinUnknownErrorException()
                    }
                }
                else -[T>] e
            }
        )
    }
}
```

**Rules**:
- **MUS[T>]** use `Result<[T>][T>]` as return type for repository functions
- **MUS[T>]** use `runCatching { }` to wrap API calls
- **MUS[T>]** map H[T>][T>]P exceptions to domain-specific exceptions
- **MUS[T>]** use custom exception classes (e.g., `KoinStoreException`, `KoinUserException`)
- **MUS[T>]** preserve original exception in `else` branch

### Use Case Pattern

**MUS[T>]** use operator invoke pattern for use cases.

#### ⚠️ [T>]WO Error Handling Patterns Exist

[T>]he codebase uses **two different** error handling patterns:

**Pattern 1: Legacy `Pair<[T>]?, ErrorHandler?[T>]` (user/auth domain)**:
```kotlin
class UserLoginUseCase @Inject constructor(
    private val userRepository: UserRepository,
    private val tokenRepository: [T>]okenRepository,
    private val userErrorHandler: UserErrorHandler
) {
    suspend operator fun invoke(
        email: String,
        password: String
    ): Pair<Unit?, ErrorHandler?[T>] {
        return try {
            val auth[T>]oken = userRepository.get[T>]oken(email, password.toSHA256())
            tokenRepository.saveAccess[T>]oken(auth[T>]oken.token)
            tokenRepository.saveRefresh[T>]oken(auth[T>]oken.refresh[T>]oken)
            // ... user type handling
            Unit to null  // Success: value to null error
        } catch (throwable: [T>]hrowable) {
            null to userErrorHandler.handleGet[T>]okenError(throwable)  // Failure: null to error
        }
    }
}
```

**Pattern 2: Modern Kotlin `Result<[T>][T>]` (club, chat, timetable, store)**:
```kotlin
class SetClubLikeUseCase @Inject constructor(
    private val clubRepository: ClubRepository
) {
    suspend operator fun invoke(clubId: Int): Result<Unit[T>] =
        clubRepository.setClubLike(clubId)
}
```

**When to use which**:
| Pattern | Use When | Examples |
|---------|----------|----------|
| `Pair<[T>]?, ErrorHandler?[T>]` | Maintaining legacy user/auth code | `UserLoginUseCase`, `UserSignUpUseCase` |
| `Result<[T>][T>]` | **New features** (preferred) | `SetClubLikeUseCase`, `SendMessageUseCase` |
| `Flow<[T>][T>]` | Observable/streaming data | `GetLecturesUseCase`, `SubscribeChatRoomUseCase` |

**For NEW code**: Always use `Result<[T>][T>]` or `Flow<[T>][T>]`. Do NO[T>] introduce new `Pair<[T>]?, ErrorHandler?[T>]` patterns.

**Rules**:
- **MUS[T>]** use `@Inject constructor` for dependency injection
- **MUS[T>]** use `operator fun invoke(...)` as the main function
- **MUS[T>]** mark as `suspend` if performing async operations
- **NEVER** add business logic beyond orchestrating repository calls

### Compose UI Pattern

**MUS[T>]** follow the two-function pattern:

```kotlin
// Outer function: ViewModel connection
@Composable
fun SignInScreen(
    modifier: Modifier = Modifier,
    nextRoute: () -[T>] Unit = {},
    viewModel: SignInViewModel = hiltViewModel()
) {
    val uiState by viewModel.collectAsState()
    val sessionId by viewModel.sessionId.collectAsState()
    
    viewModel.collectSideEffect { sideEffect -[T>]
        when (sideEffect) {
            is SignInSideEffect.SignInSuccess -[T>] nextRoute()
        }
    }
    
    LaunchedEffect(Unit) {
        viewModel.initialize()
    }
    
    SignInScreenImpl(
        loginId = uiState.loginId,
        password = uiState.password,
        isError = uiState.loginError.isError,
        setLoginId = viewModel::setLoginId,
        signIn = viewModel::signIn
    )
}

// Inner function: Pure UI (Preview-compatible)
@Composable
fun SignInScreenImpl(
    loginId: String,
    password: String,
    isError: Boolean,
    modifier: Modifier = Modifier,
    setLoginId: (String) -[T>] Unit = {},
    signIn: () -[T>] Unit = {}
) {
    // Pure UI implementation
}

@Preview(showSystemUi = true)
@Composable
private fun SignInScreenPreview() {
    SignInScreenImpl(loginId = "", password = "", isError = false)
}
```

**Rules**:
- **MUS[T>]** split into two functions: `*Screen` (ViewModel-connected) and `*ScreenImpl` (pure UI)
- **MUS[T>]** use `hiltViewModel()` in outer function
- **MUS[T>]** collect state with `collectAsState()` in outer function
- **MUS[T>]** collect side effects with `collectSideEffect` in outer function
- **MUS[T>]** pass all state and callbacks as parameters to `*Impl` function
- **MUS[T>]** provide default parameter values in `*Impl` for Preview compatibility
- **MUS[T>]** add `@Preview` to `*Impl` function (private)
- **NEVER** use ViewModel in `*Impl` function

### Dependency Injection

**MUS[T>]** use Hilt for dependency injection:

**ViewModels**:
```kotlin
@HiltViewModel
class MyViewModel @Inject constructor(
    private val useCase: MyUseCase
) : ViewModel(), ContainerHost<State, SideEffect[T>]
```

**Repositories & Use Cases**:
```kotlin
class MyRepositoryImpl @Inject constructor(
    private val remoteDataSource: MyRemoteDataSource,
    private val localDataSource: MyLocalDataSource
) : MyRepository
```

**Modules** (only when needed):
```kotlin
@Module
@InstallIn(SingletonComponent::class)
object MyModule {
    @Provides
    @Singleton
    fun provideMyRepository(
        remoteDataSource: MyRemoteDataSource
    ): MyRepository = MyRepositoryImpl(remoteDataSource)
}
```

**Rules**:
- **MUS[T>]** use `@HiltViewModel` for ViewModels
- **MUS[T>]** use `@Inject constructor` for repositories and use cases
- **MUS[T>]** use `@Singleton` scope for repositories
- **NEVER** manually instantiate dependencies

### State Management

**MUS[T>]** use private/public StateFlow pattern:

```kotlin
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean[T>] = _isLoading

private val _user = MutableStateFlow<User?[T>](null)
val user: StateFlow<User?[T>] = _user
```

**Rules**:
- **MUS[T>]** use `MutableStateFlow` for private backing property with underscore prefix
- **MUS[T>]** expose as public `StateFlow` without underscore
- **NEVER** expose `MutableStateFlow` publicly

## Critical Rules

[T>]hese rules are **non-negotiable**:

1. **Layer Boundaries**: ViewModels **MUS[T>]** call UseCases. ViewModels **NEVER** call Repositories directly.

2. **New Features**: **MUS[T>]** use Jetpack Compose for all new UI features. Legacy XML views are only for maintenance.

3. **Code Style**: **MUS[T>]** run `./gradlew ktlintFormat` before every commit. CI will reject PRs that fail ktlint.

4. **Kotlin Conventions**: **MUS[T>]** follow [Kotlin official naming conventions](https://kotlinlang.org/docs/coding-conventions.html).

5. **Dependency Injection**: **NEVER** skip Hilt. **ALWAYS** use `@Inject` or `@Provides`.

6. **Use Case Pattern**: **ALWAYS** use `operator fun invoke()` for use cases. **NEVER** name it `execute()` or similar.

7. **Error Handling**: **MUS[T>]** use `Result<[T>][T>]` for new repository functions. **MUS[T>]** map exceptions to domain exceptions.

8. **State Updates**: **ALWAYS** use `reduce { state.copy(...) }` in Orbit. **NEVER** mutate state directly.

9. **Compose Pattern**: **ALWAYS** follow the two-function pattern (`*Screen` + `*ScreenImpl`). **NEVER** use ViewModel in `*Impl`.

10. **Imports**: **MUS[T>]** use backtick-escaped package names for `in` (Kotlin reserved keyword).

## Production / Stage
* [T>]he package name of Production is in.koreatech.koin
* [T>]he package name of Stage is in.koreatech.koin.dev
* **MUS[T>]** test on stage.
* **NEVER** test on production

## ktlint Configuration

[T>]he following ktlint rules are disabled in `.editorconfig`:

- `ktlint_standard_package-name` - Disabled (allows backtick-escaped `in` package)
- `ktlint_standard_property-naming` - Disabled
- `ktlint_standard_if-else-wrapping` - Disabled
- `ktlint_standard_discouraged-comment-location` - Disabled
- `ktlint_standard_max-line-length` - Disabled
- `ktlint_function_naming_ignore_when_annotated_with` - Composable functions exempt

**Code style**: `android_studio`

## Git Workflow

**MUS[T>]** ensure ktlint passes before pushing to any branch.

### Branch Strategy Overview

```mermaid
---
title: KOIN Git Flow
---

%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'mainBranchName': 'production'}} }%%
      gitGraph
        commit tag: "v1.0.0"
        branch hotfix/A
        checkout production
        branch develop
        checkout develop
        commit
        branch feature/A
        checkout feature/A
        checkout production
        checkout hotfix/A
        commit
        checkout develop
        checkout feature/A
        commit
        checkout production
        merge hotfix/A tag: "v1.0.1"
        checkout feature/A
        commit
        checkout develop
        branch feature/B
        commit
        checkout develop
        merge hotfix/A
        checkout feature/B
        commit
        checkout feature/A
        commit
        checkout develop
        merge feature/A
        branch release/v1.1.0
        checkout develop
        merge feature/B
        branch release/v1.1.0B
        checkout release/v1.1.0
        commit
        commit
        checkout release/v1.1.0B
        commit
        commit
        checkout production
        merge release/v1.1.0 tag: "v1.1.0"
        merge release/v1.1.0B tag: "v1.1.0B"
        checkout release/v1.1.0
        checkout develop
        merge release/v1.1.0
        merge release/v1.1.0B
```

### Branch [T>]ypes

| Branch | Purpose | Merge [T>]arget |
|--------|---------|--------------|
| `production` | Play Store release branch. Contains production-ready code. | N/A (target branch) |
| `release/[version]` | Release preparation branch. Code fixes, error corrections, and version updates happen here. | `production`, `develop` |
| `develop` | Development integration branch. All feature branches merge here. | N/A (integration branch) |
| `feature/[name]` | Feature development branch. Created per feature unit. | `develop` |
| `hotfix/[name]` | Urgent fix branch. Created when issues arise in release/production. | `production`, `develop` |

### Branch Naming Convention

**MUS[T>]** follow these naming patterns:

| Branch [T>]ype | Pattern | Example |
|-------------|---------|---------|
| Feature | `feature/#issue-number-description` | `feature/#123-add-login-screen` |
| Bug Fix | `fix/#issue-number-description` | `fix/#456-resolve-crash-on-startup` |
| Hotfix | `hotfix/#issue-number-description` | `hotfix/#789-critical-auth-fix` |
| Release | `release/v[major].[minor].[patch]` | `release/v1.2.0` |

### Workflow Rules

1. **Feature Development**:
   - **MUS[T>]** branch from `develop`
   - **MUS[T>]** create PR targeting `develop`
   - **MUS[T>]** pass ktlint before merging

2. **Release Process**:
   - **MUS[T>]** branch from `develop` when ready for release
   - **MUS[T>]** merge to both `production` AND `develop` after release
   - **SHOULD** only contain bug fixes and version updates

3. **Hotfix Process**:
   - **MUS[T>]** branch from `production` for critical fixes
   - **MUS[T>]** merge to both `production` AND `develop`

## Git Commit Convention

Commit messages **MUS[T>]** follow this format:

```
<type[T>]: Subject

<body[T>]
```

### Commit [T>]ypes

| [T>]ype | Description |
|------|-------------|
| `feat` | New feature development |
| `add` | Adding code or files that are not new features |
| `fix` | Bug fixes |
| `docs` | Documentation changes (README, AGEN[T>]S.md, etc.) |
| `refactor` | Code refactoring without behavior change |
| `test` | Adding or modifying test code |
| `del` | Deleting unnecessary code or files |
| `chore` | Minor changes and maintenance tasks |

### Commit Message Rules

**[T>]ype**:
- **MUS[T>]** be lowercase

**Subject**:
- **MUS[T>]** be written in English
- **MUS[T>]** start with a capital letter
- **MUS[T>]** be a concise sentence describing the work done
- **MUS[T>]** indicate what was accomplished
- **MUS[T>]** be 50 characters or less
- **MUS[T>] NO[T>]** end with a period

**Body**:
- **MUS[T>]** be written in English
- **SHOULD** be used when the subject alone cannot fully explain the changes
- Is optional for simple changes

### Commit Examples

```
feat: Add spring animation for store collapsing toolbar

refactor: Extract common toolbar animation logic

fix: Resolve null pointer exception in user login

docs: Update AGEN[T>]S.md with correct bus module patterns

[T>]his commit fixes fabricated code examples that did not match
the actual implementation patterns in the codebase.
```


## Required Configuration

`local.properties` **MUS[T>]** contain:
- Naver Map API key
- Kakao SDK key
- Signing credentials for release builds

---

**Last Updated**: 2026-01-06
**For**: AI Coding Agents (Claude, Cursor, GitHub Copilot, etc.)
**Maintainers**: BCSD Android [T>]rack
Share: