Skip to content

Commit 558b113

Browse files
committed
docs: add AGENTS.md
1 parent 14d41b0 commit 558b113

File tree

1 file changed

+323
-0
lines changed

1 file changed

+323
-0
lines changed

AGENTS.md

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
# OpenBMCLAPI Kotlin Agent - Development Guide
2+
3+
This document provides essential information for coding agents working on the OpenBMCLAPI Kotlin project.
4+
5+
## Project Overview
6+
7+
OpenBMCLAPI Kotlin Agent is a distributed file distribution network node for accelerating Minecraft resource downloads in China. It's a Kotlin/JVM project using Ktor framework, migrated from the original TypeScript/Node.js version.
8+
9+
**Technology Stack:**
10+
- Language: Kotlin 2.2.20 (JVM target, Java 11+ required)
11+
- Web Framework: Ktor 3.3.1 with Netty server
12+
- Dependency Injection: Koin 4.1.1 with KSP annotations
13+
- Build System: Gradle with Kotlin DSL
14+
- Testing: Kotlin Test with JUnit, MockK for mocking
15+
16+
## Build Commands
17+
18+
### Core Commands
19+
```bash
20+
# Build the project
21+
./gradlew build
22+
23+
# Run the application
24+
./gradlew run
25+
26+
# Run all tests
27+
./gradlew test
28+
29+
# Run a single test class
30+
./gradlew test --tests "ApplicationTest"
31+
32+
# Run a specific test method
33+
./gradlew test --tests "ApplicationTest.主页返回OK"
34+
35+
# Format code with ktlint
36+
./gradlew ktlintFormat
37+
38+
# Check code style
39+
./gradlew ktlintCheck
40+
41+
# Create executable JAR
42+
./gradlew buildFatJar
43+
44+
# Clean build artifacts
45+
./gradlew clean
46+
```
47+
48+
### Development Commands
49+
```bash
50+
# Continuous build (watches for changes)
51+
./gradlew build --continuous
52+
53+
# Run with specific profile
54+
./gradlew run --args="--config=application-dev.yaml"
55+
56+
# Generate dependency report
57+
./gradlew dependencies
58+
59+
# Check for dependency updates
60+
./gradlew dependencyUpdates
61+
```
62+
63+
## Code Style Guidelines
64+
65+
### Formatting Rules (.editorconfig)
66+
- **Indentation:** 4 spaces (no tabs)
67+
- **Line Length:** 140 characters for Kotlin files, 120 for others
68+
- **Line Endings:** LF (Unix-style)
69+
- **Final Newline:** Required
70+
- **Trailing Whitespace:** Trimmed
71+
72+
### Import Organization
73+
```kotlin
74+
// 1. Standard library imports
75+
import kotlinx.coroutines.runBlocking
76+
import kotlinx.serialization.json.Json
77+
78+
// 2. Third-party library imports
79+
import io.ktor.server.application.Application
80+
import org.koin.core.annotation.Single
81+
82+
// 3. Project imports (grouped by package)
83+
import com.bangbang93.openbmclapi.agent.config.ClusterConfig
84+
import com.bangbang93.openbmclapi.agent.service.BootstrapService
85+
```
86+
87+
### Naming Conventions
88+
- **Classes:** PascalCase (`BootstrapService`, `ClusterConfig`)
89+
- **Functions:** camelCase (`getToken()`, `setupCertificates()`)
90+
- **Properties:** camelCase (`clusterId`, `clusterSecret`)
91+
- **Constants:** SCREAMING_SNAKE_CASE (`AGENT_PROTOCOL_VERSION`)
92+
- **Packages:** lowercase with dots (`com.bangbang93.openbmclapi.agent`)
93+
94+
### Type Annotations
95+
```kotlin
96+
// Explicit types for public APIs
97+
fun getConfig(env: ApplicationEnvironment): ClusterConfig
98+
99+
// Type inference for local variables is acceptable
100+
val server = embeddedServer(Netty, env)
101+
102+
// Nullable types explicitly declared
103+
var keystorePath: String? = null
104+
105+
// Collection types with generics
106+
val storageOpts: Map<String, String> = emptyMap()
107+
```
108+
109+
### Function Structure
110+
```kotlin
111+
// Suspend functions for async operations
112+
suspend fun bootstrap() {
113+
logger.info { "Starting bootstrap process" }
114+
// Implementation
115+
}
116+
117+
// Private functions use camelCase
118+
private fun scheduleFileCheck(lastModified: Long) {
119+
// Implementation
120+
}
121+
122+
// Extension functions when appropriate
123+
suspend fun Application.module() {
124+
// Configuration
125+
}
126+
```
127+
128+
### Error Handling
129+
```kotlin
130+
// Use try-catch for expected exceptions
131+
try {
132+
val result = riskyOperation()
133+
logger.info { "Operation succeeded: $result" }
134+
} catch (e: Exception) {
135+
logger.error(e) { "Operation failed" }
136+
throw e // Re-throw if needed
137+
}
138+
139+
// Use Result type for recoverable errors when appropriate
140+
fun parseConfig(input: String): Result<Config> {
141+
return try {
142+
Result.success(Json.decodeFromString(input))
143+
} catch (e: Exception) {
144+
Result.failure(e)
145+
}
146+
}
147+
```
148+
149+
### Logging
150+
```kotlin
151+
// Use KotlinLogging with structured logging
152+
private val logger = KotlinLogging.logger {}
153+
154+
// Log with lambda for lazy evaluation
155+
logger.info { "Processing ${files.size} files" }
156+
logger.error(exception) { "Failed to process request" }
157+
158+
// Use appropriate log levels
159+
logger.debug { "Debug information" }
160+
logger.info { "General information" }
161+
logger.warn { "Warning message" }
162+
logger.error(exception) { "Error with exception" }
163+
```
164+
165+
### Dependency Injection (Koin)
166+
```kotlin
167+
// Service classes use @Single annotation
168+
@Single
169+
class BootstrapService(
170+
private val storage: IStorage,
171+
private val tokenManager: TokenManager,
172+
) {
173+
// Implementation
174+
}
175+
176+
// Factory functions for configuration
177+
@Single
178+
fun getConfig(env: ApplicationEnvironment): ClusterConfig {
179+
// Configuration logic
180+
}
181+
182+
// Inject dependencies in Ktor applications
183+
suspend fun Application.module() {
184+
val bootstrapService by inject<BootstrapService>()
185+
}
186+
```
187+
188+
## Project Structure
189+
190+
```
191+
src/main/kotlin/com/bangbang93/openbmclapi/agent/
192+
├── Application.kt # Main entry point
193+
├── AppModule.kt # Koin DI configuration
194+
├── config/ # Configuration classes
195+
│ ├── Config.kt # Main configuration
196+
│ ├── Constants.kt # Application constants
197+
│ └── Version.kt # Version management
198+
├── model/ # Data models and DTOs
199+
├── nat/ # NAT/UPnP support
200+
├── routes/ # HTTP route handlers
201+
├── service/ # Business logic services
202+
├── storage/ # Storage backend implementations
203+
└── util/ # Utility functions
204+
```
205+
206+
## Testing Guidelines
207+
208+
### Test Structure
209+
```kotlin
210+
class ServiceTest {
211+
@BeforeTest
212+
fun setup() {
213+
// Test setup
214+
}
215+
216+
@AfterTest
217+
fun cleanup() {
218+
// Test cleanup
219+
}
220+
221+
@Test
222+
fun `should handle normal case`() {
223+
// Arrange
224+
val input = createTestInput()
225+
226+
// Act
227+
val result = serviceUnderTest.process(input)
228+
229+
// Assert
230+
assertEquals(expected, result)
231+
}
232+
}
233+
```
234+
235+
### Mocking with MockK
236+
```kotlin
237+
val mockStorage = mockk<IStorage> {
238+
coEvery { check() } returns true
239+
coEvery { init() } returns Unit
240+
}
241+
```
242+
243+
### Integration Tests
244+
```kotlin
245+
@Test
246+
fun `integration test with test application`() = testApplication {
247+
application {
248+
runBlocking { this@application.appModule() }
249+
}
250+
251+
client.get("/").apply {
252+
assertEquals(HttpStatusCode.OK, status)
253+
}
254+
}
255+
```
256+
257+
## Common Patterns
258+
259+
### Coroutines Usage
260+
```kotlin
261+
// Use appropriate dispatchers
262+
CoroutineScope(Dispatchers.IO).launch {
263+
// I/O operations
264+
}
265+
266+
// Structured concurrency
267+
suspend fun processFiles() = coroutineScope {
268+
val jobs = files.map { file ->
269+
async { processFile(file) }
270+
}
271+
jobs.awaitAll()
272+
}
273+
```
274+
275+
### Configuration Management
276+
- Use `application.yaml` for Ktor configuration
277+
- Use `.env` files for environment-specific settings (gitignored)
278+
- Support multiple configuration sources (Ktor config, dotenv, env vars, system props)
279+
- Provide sensible defaults for development
280+
281+
### Storage Abstraction
282+
- Implement `IStorage` interface for new storage backends
283+
- Use factory pattern for storage creation
284+
- Support multiple storage types: file, WebDAV, MinIO, Aliyun OSS
285+
286+
## Development Workflow
287+
288+
1. **Before Making Changes:**
289+
- Run `./gradlew ktlintCheck` to verify code style
290+
- Run `./gradlew test` to ensure all tests pass
291+
292+
2. **During Development:**
293+
- Use `./gradlew build --continuous` for automatic rebuilds
294+
- Write tests for new functionality
295+
- Follow existing patterns and conventions
296+
297+
3. **Before Committing:**
298+
- Run `./gradlew ktlintFormat` to format code
299+
- Run `./gradlew build` to ensure everything compiles
300+
- Run `./gradlew test` to verify all tests pass
301+
302+
## Key Dependencies
303+
304+
- **Ktor:** Web framework and HTTP client
305+
- **Koin:** Dependency injection with KSP code generation
306+
- **Kotlinx Coroutines:** Async programming
307+
- **Socket.IO:** Cluster communication
308+
- **Logback:** Logging implementation
309+
- **BouncyCastle:** SSL/TLS certificate handling
310+
- **Various Storage:** MinIO, Aliyun OSS, WebDAV clients
311+
312+
## Environment Setup
313+
314+
Required environment variables (see `.env.example`):
315+
- `CLUSTER_ID`: Cluster identifier
316+
- `CLUSTER_SECRET`: Cluster authentication secret
317+
- `CLUSTER_PORT`: Server port (default: 4000)
318+
- `CLUSTER_STORAGE`: Storage backend type (default: "file")
319+
320+
Optional variables:
321+
- `CLUSTER_IP`: Public IP address
322+
- `ENABLE_UPNP`: Enable UPnP NAT traversal
323+
- `SSL_KEY`/`SSL_CERT`: Custom SSL certificates (BYOC mode)

0 commit comments

Comments
 (0)