소스 정보
- 저장소
- navikt/helved-utbetaling
- 최근 소스 활동
- 2026년 3월 18일 09:33
- 감지된 SKILL.md 언어
- 영어
- 스타
- 1
- 포크
- 1
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/navikt/helved-utbetaling --skill readable-code명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
This skill should be used when the user needs guidance on any of the 200+ http4k modules - it contains patterns and API usage examples for all http4k technologies.
Custom JDBC patterns for helved-utbetaling - Migrator, Dao<T>, transaction { }, CoroutineDatasource. Triggers - "write a migration", "create a DAO", "database query", "transaction", "/database".
Build Ktor routes, StatusPages handlers, and authentication blocks following helved-utbetaling conventions. Triggers on "Ktor route", "REST endpoint", "API handler", "StatusPages", "/ktor-routing".
SOC 직업 분류 기준
SKILL.md 표시 중
| name | readable-code |
| description | Write readable and maintainable code following helved-utbetaling patterns |
| license | MIT |
| compatibility | opencode |
| metadata | {"audience":"ai-assistant","language":"kotlin","framework":"ktor","domain":"nav-payment-system"} |
I guide you to write readable and maintainable Kotlin code that follows the established patterns in the helved-utbetaling codebase.
Load this skill when you are writing, refactoring, or reviewing code in this project.
Prerequisites: Root
AGENTS.mdcovers naming conventions, error handling patterns, testing patterns, and architecture. This skill provides concrete code examples that complement those rules.
Target 10-30 lines per function. Use early returns for clarity. Extract complex logic into helper functions.
// Good: Clear flow, early returns, focused logic
fun branntaarn(
config: Config = Config(),
now: LocalDateTime = LocalDateTime.now(),
) {
if (now.toLocalDate().erHelligdag() || now.hour < 6 || now.hour > 21) return
val peisschtappern = PeisschtappernClient(config)
val slack = SlackClient(config)
val branner = peisschtappern.branner()
.filter { brann -> brann.timeout.isBefore(now) }
if (branner.isEmpty()) return
val grouped = branner.groupBy { it.fagsystem }
slack.postAggregated(grouped)
branner.forEach(peisschtappern::slukk)
}
// Bad: Nested conditions, unclear flow
fun branntaarn(
config: Config = Config(),
now: LocalDateTime = LocalDateTime.now(),
) {
if (!now.toLocalDate().erHelligdag() && now.hour >= 6 && now.hour <= 21) {
val peisschtappern = PeisschtappernClient(config)
val slack = SlackClient(config)
val branner = peisschtappern.branner().filter { it.timeout.isBefore(now) }
if (branner.isNotEmpty()) {
val grouped = branner.groupBy { it.fagsystem }
slack.postAggregated(grouped)
for (brann in branner) {
peisschtappern.slukk(brann)
}
}
}
}
Public API at the top, private helpers below. Group related functions together.
// Public API first, private helpers below
package branntaarn
class SlackClient(
private val config: Config,
private val client: HttpClient = HttpClientFactory.new(LogLevel.ALL),
) {
fun postAggregated(grouped: Map<String, List<Brann>>) {
runBlocking {
client.post(config.slack.host.toString()) {
contentType(ContentType.Application.Json)
setBody(jsonAggregated(grouped, config))
}
}
}
}
private fun jsonAggregated(
grouped: Map<String, List<Brann>>,
config: Config
): String { /* ... */ }
private fun emoji(config: Config): String = when (config.nais.cluster) {
"prod-gcp" -> ":fire:"
else -> ""
}
Keep transformation pipelines readable with chained operations.
// Good: Clear transformation pipeline
val sakIdText = if (sakIds.size <= displayLimit) {
sakIds.joinToString(", ")
} else {
val shown = sakIds.take(displayLimit).joinToString(", ")
val remaining = sakIds.size - displayLimit
"$shown _(+$remaining more not shown)_"
}
Use comments to explain WHY, not WHAT. Document business rules and non-obvious Norwegian terms.
// Good: Explains business rule
// Skip alerts outside operational hours (06-21, weekdays only, excluding holidays)
if (now.toLocalDate().erHelligdag() || now.hour < 6 || now.hour > 21) return
// Good: Clarifies Norwegian term
// Slukk (extinguish) - Delete the timer from peisschtappern
branner.forEach(peisschtappern::slukk)
Prefer self-documenting code over comments:
// Good: Self-documenting with named boolean
val isOutsideOperationalHours = now.toLocalDate().erHelligdag()
|| now.hour < 6
|| now.hour > 21
if (isOutsideOperationalHours) return
if (sakId.isEmpty()) {
badRequest("sakId cannot be empty", DocumentedErrors.INVALID_SAK_ID)
}
val utbetaling = dao.findById(utbetalingId)
?: notFound("Utbetaling $utbetalingId not found")
val result = Result.catch {
oppdragMapper.readValue(value)
}.onFailure { error ->
appLog.warn("Failed to parse oppdrag: ${error.message}")
}
// Good: Explicit null handling with logging
val oppdrag = oppdragMapper.readValue(value) ?: run {
appLog.warn("Failed to parse oppdrag for key $key")
return stopTimer(key)
}
// Bad: Silent failure
val oppdrag = oppdragMapper.readValue(value) ?: return
Constructor injection with default parameters. No DI framework. Wire in app entry point.
// Good: Clear dependencies, testable with defaults
class SlackClient(
private val config: Config,
private val client: HttpClient = HttpClientFactory.new(LogLevel.ALL),
) { /* ... */ }
// Good: App wiring in entry point
fun Application.utsjekk() {
val config = Config()
val datasource = Jdbc.initialize(config.jdbc)
val oppdragProducer = OppdragProducer(config.kafka)
val iverksettingService = IverksettingService(datasource, oppdragProducer)
routing {
iverksettingRoutes(iverksettingService)
}
}
// Bad: Hidden dependencies, hard to test
class SlackClient {
private val config = Config() // Hard to test
private val client = HttpClient(CIO) // Can't mock
}
data class Brann(
val key: String,
val timeout: LocalDateTime,
val sakId: String,
val fagsystem: String,
)
data class Config(
val azure: AzureConfig = AzureConfig(),
val slack: SlackConfig = SlackConfig(),
val nais: NaisConfig = NaisConfig(),
)
private fun emoji(config: Config): String = when (config.nais.cluster) {
"prod-gcp" -> ":fire:"
else -> ""
}
fun shouldSkipProcessing(now: LocalDateTime): Boolean =
now.toLocalDate().erHelligdag()
|| now.hour < 6
|| now.hour > 21
// Good: Functional chaining
val grouped = branner
.filter { brann -> brann.timeout.isBefore(now) }
.groupBy { it.fagsystem }
// Good: buildList for construction
val blocks = buildList {
add("""{"type": "header"}""")
grouped.entries.sortedBy { it.key }.forEach { (fagsystem, branner) ->
add("""{"type": "section", "text": "$fagsystem - ${branner.size}"}""")
}
}
// Good: String templates
val message = "*$totalCount missing kvitteringer across $fagsystemCount fagsystems*"
// Good: Triple-quoted string with trimIndent
val json = """
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Branntaarn Alert (${config.nais.cluster})"
}
}
""".trimIndent()