一键导入
add-core-module
Scaffold a new core module with api/impl split, optional test module, source files, and AGENTS.md. Use when adding shared infrastructure to the app.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Scaffold a new core module with api/impl split, optional test module, source files, and AGENTS.md. Use when adding shared infrastructure to the app.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Convert acceptance criteria, Jira tickets, Gherkin scenarios, PRDs, or any product requirements into a structured spec template. Use when translating PM artifacts into actionable specs for add-*, update, migrate, or remove skills.
Scaffold a complete new feature module with all 6 submodules, source files, tests, fakes, and AGENTS.md. Use when adding an entirely new feature to the app.
Execute cross-cutting migrations — library swaps, pattern changes, API version upgrades, or architecture refactors. Handles phased rollout, coexistence, and rollback planning.
Reverse-engineer a spec from existing code — reconstructs presumed acceptance criteria, data flow, API contracts, UI states, and business rules by reading source, tests, and git history. Use when inheriting undocumented features, onboarding onto unfamiliar code, or preparing for a refactor.
Modify existing feature code across all affected layers — domain models, data, presentation, tests, and fakes. Use when changing behavior, adding fields, or enhancing existing features.
Verification pipeline with three scopes — diff (default, changed modules only), full (whole project lint + unit + arch + builds), and all (every test level + every variant + full assemble). Use after any code change.
| name | add-core-module |
| description | Scaffold a new core module with api/impl split, optional test module, source files, and AGENTS.md. Use when adding shared infrastructure to the app. |
Scaffold a core module with api/impl split for shared infrastructure.
Parameters:
name (lowercase kebab-case, e.g., feature-flag) — requiredhasTest (boolean, default true) — whether to create a test/ submodule with fakesplugin (mockdonalds.kmp.domain for modules needing Metro DI, mockdonalds.kmp.library for pure contracts) — default mockdonalds.kmp.domain.agents/standards/architecture.md.agents/standards/naming-conventions.md.agents/standards/dependency-injection.md.agents/standards/centerpost.md.agents/standards/convention-plugins.mdUse core/auth/ as the reference for api/impl split pattern, DI wiring, and AGENTS.md format.
Create submodules under core/{name}/:
api/build.gradle.kts — public contract (interfaces, abstract interactors, types):
plugins {
id("mockdonalds.kmp.domain")
}
kotlin {
android {
namespace = "com.mockdonalds.app.core.{name}.api"
}
sourceSets {
commonMain.dependencies {
// Add api dependencies as needed (e.g., core:centerpost for interactors)
}
}
}
For pure contract modules with no DI needs, use mockdonalds.kmp.library instead.
impl/build.gradle.kts — concrete implementations:
plugins {
id("mockdonalds.kmp.domain")
}
kotlin {
android {
namespace = "com.mockdonalds.app.core.{name}.impl"
}
sourceSets {
commonMain.dependencies {
api(project(":core:{name}:api"))
}
}
}
test/build.gradle.kts (if hasTest is true) — fakes for consumer tests:
plugins {
id("mockdonalds.kmp.domain")
}
kotlin {
android {
namespace = "com.mockdonalds.app.core.{name}.test"
}
sourceSets {
commonMain.dependencies {
api(project(":core:{name}:api"))
api(project(":core:test-fixtures"))
}
}
}
Core modules are manually listed (not auto-discovered). Add to the "Core modules" section:
include(":core:{name}:api")
include(":core:{name}:impl")
include(":core:{name}:test") // if hasTest
api/ — src/commonMain/kotlin/com/mockdonalds/app/core/{name}/
Place public interfaces, abstract interactors, and data types here. Package: com.mockdonalds.app.core.{name}.
CenterPostInteractor or CenterPostSubjectInteractor@Serializable if they cross module boundariesCore modules with api/impl expose CenterPost interactors for presenter consumption by default. Domain/data layers always inject the provider interface directly. One documented exception: core:remote-config uses Composable rememberFlag(flag) / rememberConfig(config) extensions instead of an interactor (see .agents/standards/centerpost.md for the rationale). New core modules follow the interactor rule unless the carve-out is re-justified and documented.
| Core Module Characteristic | Presenter Consumption | Domain/Data Consumption | Example |
|---|---|---|---|
| Produces observable state (Flow/StateFlow) | CenterPostSubjectInteractor | Provider interface | GetHomeContent (feature-level subject interactor) |
| Produces one-shot result or fire-and-forget | CenterPostInteractor | Provider interface | core:analytics: TrackAnalyticsEvent / AnalyticsDispatcher |
| Ambient config read (carve-out) | Composable extension on provider | Provider interface | core:remote-config: RemoteConfigProvider.rememberFlag(flag) / rememberConfig(config) over RemoteConfigProvider |
For fire-and-forget interactors, the inProgress loading state simply goes uncollected — it's opt-in with zero overhead. The value of wrapping even void operations in CenterPost: structured execution, error handling, timeout protection, dispatcher correctness.
impl/ — src/commonMain/kotlin/com/mockdonalds/app/core/{name}/impl/
Place concrete implementations here. Package: com.mockdonalds.app.core.{name}.impl.
Key rules:
@ContributesBinding(AppScope::class) (Metro implicitly provides @Inject — do NOT add explicit @Inject)@SingleIn(AppScope::class)internal@ContributesBinding(AppScope::class)
class {Name}Impl(
private val dependency: SomeDependency,
) : {Name}() {
override fun createObservable(params: P): Flow<T> {
return dependency.observe()
}
}
test/ — src/commonMain/kotlin/com/mockdonalds/app/core/{name}/test/
Place fakes here (in commonMain, NOT commonTest). Package: com.mockdonalds.app.core.{name}.test.
MutableStateFlow-backed with control methods (setXxx(), reset())@ContributesBinding(AppScope::class) for test graph auto-wiringapi only — never on impl (Konsist-enforced via TestModuleDependencyTest). This keeps vendor SDKs and platform-specific code out of test graphs.Parallel multibind contracts — if your impl module declares a multibind slot via a @ContributesTo(AppScope::class) interface with @Multibinds, declare the same slot a second time in test (different package, same scope, same signature). Test graphs that pull api + test (not impl) need to see the slot or Metro cannot resolve the Set<T>. Metro consolidates slots by type + scope, so both copies resolve to the same binding. Do this whenever a test module exists for a core that contributes multibind aggregators. See .agents/standards/dependency-injection.md → "Parallel multibind contracts".
Place unit tests in impl/src/commonTest/kotlin/com/mockdonalds/app/core/{name}/impl/.
BehaviorSpec with Given/When/Then structureCreate core/{name}/AGENTS.md following this template:
# core:{name}
## Purpose
One-line description of what this core module provides.
## Architecture
\```
core/{name}/api -> Public interfaces and types (feature-visible)
core/{name}/impl -> Concrete implementations (DI-only, never imported directly)
core/{name}/test -> Fakes for consumer tests
\```
## Public API
| Type | Module | Description |
|------|--------|-------------|
| `{Interface}` | api | Description |
| `{Interactor}` | api | Abstract CenterPost interactor for ... |
## Consumption Pattern
| Layer | What to inject | Why |
|-------|---------------|-----|
| Presenters | CenterPost interactor from api | Reactive, lifecycle-aware, structured execution |
| Domain/Data | Provider interface from api | Direct, synchronous access |
## Usage
**Presenters** use the CenterPost interactor:
\```kotlin
@CircuitInject(MyScreen::class, AppScope::class)
@Composable
fun MyPresenter(
myInteractor: {Interactor},
): MyUiState {
// Use interactor for reactive/one-shot access
}
\```
**Domain/data** inject the provider interface:
\```kotlin
@ContributesBinding(AppScope::class)
class MyRepositoryImpl(
private val myProvider: {Provider},
) : MyRepository {
// Use provider for direct access
}
\```
## Rules
- Core modules never import from features
- Features MUST depend on `core:{name}:api` only, never `core:{name}:impl`
- **Presenters** must use CenterPost interactors, never provider interfaces directly (Konsist-enforced)
- **Domain/data** must use provider interfaces, never CenterPost interactors (Konsist-enforced)
- `impl` is wired exclusively through Metro `@ContributesBinding` in `AppScope`
- Test code should use fakes from `core:{name}:test`
Add the impl dependency to composeApp/build.gradle.kts in the core dependencies section:
implementation(project(":core:{name}:impl"))
This ensures the @ContributesBinding classes are on the classpath when ProdAppGraph is compiled. The api module is pulled transitively.
Confirm the modules are recognized:
./gradlew projects | grep {name}
Should show all submodules (api, impl, test if applicable).
com.mockdonalds.app.core.{name} (api), .impl (impl), .test (test)@Inject on impl classes — @ContributesBinding handles it implicitlytest/src/commonMain/ — they are published dependencies, not test-onlyAgentDocumentationTest)AppGraph in core:metro does NOT need updating — consumers get dependencies via constructor injectionWork is NEVER complete until verification passes. Run verify full since new modules touch build structure and require full validation.
The verify skill will run: lint, unit tests, architecture tests (Konsist + Harmonize), and builds for both platforms.
If ANY check fails, fix the issue and re-run. Do not declare the task complete until verification passes.