Plan phase 1
This commit is contained in:
@@ -0,0 +1,576 @@
|
||||
---
|
||||
phase: 01-project-infrastructure-module-wiring
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- build-logic/settings.gradle.kts
|
||||
- build-logic/build.gradle.kts
|
||||
- build-logic/src/main/kotlin/recipe.quality.gradle.kts
|
||||
- build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts
|
||||
- build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts
|
||||
- build-logic/src/main/kotlin/recipe.android.application.gradle.kts
|
||||
- build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts
|
||||
- settings.gradle.kts
|
||||
- build.gradle.kts
|
||||
autonomous: true
|
||||
requirements: [INFRA-02]
|
||||
requirements_addressed: [INFRA-02]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "build-logic/ is an included build resolved via pluginManagement.includeBuild (PITFALL #9)"
|
||||
- "5 precompiled script plugins exist under build-logic/src/main/kotlin/: recipe.quality, recipe.kotlin.multiplatform, recipe.compose.multiplatform, recipe.android.application, recipe.jvm.server (D-06)"
|
||||
- "Each precompiled plugin reads versions via extensions.getByType<VersionCatalogsExtension>().named(\"libs\") (PITFALL #1)"
|
||||
- "recipe.kotlin.multiplatform locks the D-05 target matrix (androidTarget, iosArm64, iosSimulatorArm64, jvm, wasmJs) + JVM toolchain 21 + framework basename 'ComposeApp' + Koin/Kermit/kotlin-test deps + allWarningsAsErrors"
|
||||
- "recipe.compose.multiplatform layers on recipe.kotlin.multiplatform (does NOT re-declare KMP plugin — PITFALL #2)"
|
||||
- "recipe.jvm.server uses quoted dependency configurations (\"implementation\"(...) — quoted-config footgun)"
|
||||
- "settings.gradle.kts places includeBuild(\"build-logic\") INSIDE pluginManagement { } block (PITFALL #9)"
|
||||
artifacts:
|
||||
- path: "build-logic/settings.gradle.kts"
|
||||
provides: "Included-build settings with shared catalog access (from files(\"../gradle/libs.versions.toml\"))"
|
||||
- path: "build-logic/build.gradle.kts"
|
||||
provides: "kotlin-dsl plugin + compileOnly(asDependency()) entries for every alias-based plugin referenced by precompiled plugins"
|
||||
- path: "build-logic/src/main/kotlin/recipe.quality.gradle.kts"
|
||||
provides: "Spotless + ktlint + allWarningsAsErrors safety net (D-10 / D-11)"
|
||||
- path: "build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts"
|
||||
provides: "D-05 target matrix + JVM toolchain + common deps + allWarningsAsErrors (D-07, D-08, D-11)"
|
||||
- path: "build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts"
|
||||
provides: "Compose MP plugin + hot-reload + Compose deps for commonMain (layered on KMP)"
|
||||
- path: "build-logic/src/main/kotlin/recipe.android.application.gradle.kts"
|
||||
provides: "com.android.application + namespace + SDK versions (composeApp only)"
|
||||
- path: "build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts"
|
||||
provides: "kotlin(jvm) + Ktor + Flyway + server deps (server only)"
|
||||
- path: "settings.gradle.kts"
|
||||
provides: "Root settings with pluginManagement { includeBuild(\"build-logic\") }"
|
||||
- path: "build.gradle.kts"
|
||||
provides: "Root build with apply-false entries for spotless + flywayPlugin (classloader hint)"
|
||||
key_links:
|
||||
- from: "build-logic/src/main/kotlin/recipe.*.gradle.kts"
|
||||
to: "gradle/libs.versions.toml"
|
||||
via: "VersionCatalogsExtension.named(\"libs\")"
|
||||
pattern: "extensions\\.getByType<VersionCatalogsExtension>\\(\\)\\.named\\(\"libs\"\\)"
|
||||
- from: "Plan 03 module build files"
|
||||
to: "build-logic/src/main/kotlin/recipe.*.gradle.kts"
|
||||
via: "plugins { id(\"recipe.kotlin.multiplatform\") }"
|
||||
pattern: "id\\(\"recipe\\."
|
||||
---
|
||||
|
||||
<objective>
|
||||
Scaffold the `build-logic/` included build with 5 precompiled script plugins (`recipe.quality`, `recipe.kotlin.multiplatform`, `recipe.compose.multiplatform`, `recipe.android.application`, `recipe.jvm.server`) that every module in Plan 03 will apply. Wire the included build into `settings.gradle.kts` via `pluginManagement.includeBuild("build-logic")` and extend the root `build.gradle.kts` with `apply false` declarations for the two new plugins (Spotless + Flyway) so Gradle's classloader resolves them consistently.
|
||||
|
||||
Purpose: This is the **dependency root** for every subsequent Phase 1 plan. Plan 03 cannot refactor module builds until these plugins exist. Plan 05 cannot wire Flyway into the server without `recipe.jvm.server`. The design (per D-06) enforces role declarations — `shared/` applies only `recipe.kotlin.multiplatform` + `recipe.quality` and therefore CANNOT pull Compose transitively (INFRA-06).
|
||||
|
||||
Output: A fully populated `build-logic/` directory whose included-build settings resolve the parent catalog, a root settings file that finds `recipe.*` plugins by ID, and 5 precompiled plugins whose internals are verbatim (or near-verbatim) copies of 01-RESEARCH.md § Code Examples / § Architecture Patterns.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/01-project-infrastructure-module-wiring/01-CONTEXT.md
|
||||
@.planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md
|
||||
@.planning/phases/01-project-infrastructure-module-wiring/01-PATTERNS.md
|
||||
@.planning/phases/01-project-infrastructure-module-wiring/01-VALIDATION.md
|
||||
@settings.gradle.kts
|
||||
@build.gradle.kts
|
||||
@gradle/libs.versions.toml
|
||||
@CLAUDE.md
|
||||
|
||||
<interfaces>
|
||||
<!-- These are the canonical excerpts the executor MUST copy verbatim. Line ranges refer to 01-RESEARCH.md. -->
|
||||
|
||||
Plugin applications reference (01-PATTERNS.md and 01-RESEARCH.md):
|
||||
- `id("recipe.quality")` → from .gradle.kts file named `recipe.quality.gradle.kts` (Gradle convention)
|
||||
- `id("recipe.kotlin.multiplatform")` → `recipe.kotlin.multiplatform.gradle.kts`
|
||||
- etc.
|
||||
|
||||
Version-catalog access pattern inside precompiled plugins (PITFALL #1, RESEARCH.md lines 362-380):
|
||||
```kotlin
|
||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
|
||||
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
|
||||
// Usage:
|
||||
val v = libs.findVersion("kotlin").get().toString()
|
||||
val lib = libs.findLibrary("koin-core").get()
|
||||
```
|
||||
|
||||
Quoted configuration names in precompiled plugin dependencies (RESEARCH.md line 603, Pattern 7):
|
||||
```kotlin
|
||||
dependencies {
|
||||
"implementation"(libs.findLibrary("ktor-serverCore").get()) // quoted!
|
||||
// NOT: implementation(...) — unresolved reference in precompiled plugin context
|
||||
}
|
||||
```
|
||||
|
||||
The root `settings.gradle.kts` layout required by PITFALL #9 (RESEARCH.md lines 749-767):
|
||||
```kotlin
|
||||
pluginManagement {
|
||||
includeBuild("build-logic") // MUST be inside pluginManagement { }
|
||||
repositories { ... }
|
||||
}
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Scaffold build-logic/ included build + 5 precompiled plugins</name>
|
||||
<files>build-logic/settings.gradle.kts, build-logic/build.gradle.kts, build-logic/src/main/kotlin/recipe.quality.gradle.kts, build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts, build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts, build-logic/src/main/kotlin/recipe.android.application.gradle.kts, build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts</files>
|
||||
<read_first>
|
||||
- .planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md lines 308-605 (§ Pattern 1 through § Pattern 7 — canonical excerpts for every file in this task)
|
||||
- .planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md lines 652-774 (§ Common Pitfalls 1-10 — especially #1 catalog access, #2 double-apply KMP, #3 warnings-as-errors scope, #7 kotlinOptions, #9 includeBuild location, #10 framework basename)
|
||||
- .planning/phases/01-project-infrastructure-module-wiring/01-PATTERNS.md lines 105-443 (pattern assignments for each build-logic/ file with deltas)
|
||||
- gradle/libs.versions.toml (Plan 01 added these aliases — verify they exist before writing `findLibrary(...)` references)
|
||||
- .planning/phases/01-project-infrastructure-module-wiring/01-CONTEXT.md D-06 through D-17 (plugin split, JVM split, warnings-as-errors, Koin deps, Flyway, server scope)
|
||||
</read_first>
|
||||
<action>
|
||||
Create the `build-logic/` directory and all 7 files listed in `<files>`. Each file's content comes directly from 01-RESEARCH.md. Use the Write tool for every file (no heredoc).
|
||||
|
||||
---
|
||||
|
||||
**File 1: `build-logic/settings.gradle.kts`** (01-RESEARCH.md lines 316-331, verbatim):
|
||||
|
||||
```kotlin
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "build-logic"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**File 2: `build-logic/build.gradle.kts`** (01-RESEARCH.md lines 333-358, verbatim):
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.plugins.kotlinMultiplatform.asDependency())
|
||||
compileOnly(libs.plugins.androidApplication.asDependency())
|
||||
compileOnly(libs.plugins.androidLibrary.asDependency())
|
||||
compileOnly(libs.plugins.composeMultiplatform.asDependency())
|
||||
compileOnly(libs.plugins.composeCompiler.asDependency())
|
||||
compileOnly(libs.plugins.composeHotReload.asDependency())
|
||||
compileOnly(libs.plugins.kotlinJvm.asDependency())
|
||||
compileOnly(libs.plugins.ktor.asDependency())
|
||||
compileOnly(libs.plugins.spotless.asDependency())
|
||||
compileOnly(libs.plugins.flywayPlugin.asDependency())
|
||||
}
|
||||
|
||||
fun Provider<PluginDependency>.asDependency(): Provider<String> =
|
||||
map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version.requiredVersion}" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**File 3: `build-logic/src/main/kotlin/recipe.quality.gradle.kts`** (01-RESEARCH.md lines 483-512 + D-11 safety net):
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
id("com.diffplug.spotless")
|
||||
}
|
||||
|
||||
spotless {
|
||||
kotlin {
|
||||
target("src/**/*.kt")
|
||||
targetExclude("**/build/**", "**/generated/**")
|
||||
ktlint()
|
||||
}
|
||||
kotlinGradle {
|
||||
target("*.gradle.kts")
|
||||
ktlint()
|
||||
}
|
||||
format("markdown") {
|
||||
target("*.md", "docs/**/*.md")
|
||||
endWithNewline()
|
||||
trimTrailingWhitespace()
|
||||
}
|
||||
}
|
||||
|
||||
// D-11 redundancy guard: if a module applies recipe.quality WITHOUT recipe.kotlin.multiplatform
|
||||
// (e.g. a future pure-JVM utility), ensure allWarningsAsErrors still applies.
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
|
||||
compilerOptions {
|
||||
allWarningsAsErrors.set(true)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**File 4: `build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts`** (01-RESEARCH.md lines 777-835, verbatim — the canonical KMP plugin):
|
||||
|
||||
```kotlin
|
||||
// build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts
|
||||
// Establishes the D-05 target matrix + JVM toolchain + common deps.
|
||||
// Android bytecode is JVM 11 (D-08); server + desktop + shared/jvm are JVM 21.
|
||||
|
||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
id("org.jetbrains.kotlin.multiplatform")
|
||||
}
|
||||
|
||||
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
|
||||
androidTarget {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
listOf(iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
|
||||
iosTarget.binaries.framework {
|
||||
baseName = "ComposeApp"
|
||||
isStatic = true
|
||||
}
|
||||
}
|
||||
|
||||
jvm {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_21)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalWasmDsl::class)
|
||||
wasmJs { browser() }
|
||||
|
||||
compilerOptions {
|
||||
allWarningsAsErrors.set(true)
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(project.dependencies.platform(libs.findLibrary("koin-bom").get()))
|
||||
implementation(libs.findLibrary("koin-core").get())
|
||||
implementation(libs.findLibrary("kermit").get())
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(libs.findLibrary("kotlin-test").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**File 5: `build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts`** (01-RESEARCH.md lines 447-477 + 01-PATTERNS.md lines 247-287 — layers on KMP, PITFALL #2):
|
||||
|
||||
```kotlin
|
||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
|
||||
plugins {
|
||||
id("recipe.kotlin.multiplatform")
|
||||
id("org.jetbrains.compose")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
id("org.jetbrains.compose.hot-reload")
|
||||
}
|
||||
|
||||
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.findLibrary("compose-runtime").get())
|
||||
implementation(libs.findLibrary("compose-foundation").get())
|
||||
implementation(libs.findLibrary("compose-material3").get())
|
||||
implementation(libs.findLibrary("compose-ui").get())
|
||||
implementation(libs.findLibrary("compose-components-resources").get())
|
||||
implementation(libs.findLibrary("androidx-lifecycle-viewmodelCompose").get())
|
||||
implementation(libs.findLibrary("androidx-lifecycle-runtimeCompose").get())
|
||||
implementation(libs.findLibrary("koin-compose").get())
|
||||
implementation(libs.findLibrary("koin-composeViewmodel").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
CRITICAL: this plugin applies `id("recipe.kotlin.multiplatform")` — NOT `id("org.jetbrains.kotlin.multiplatform")`. The KMP plugin is applied transitively by the recipe plugin. Double-applying throws "Plugin already applied" (PITFALL #2).
|
||||
|
||||
---
|
||||
|
||||
**File 6: `build-logic/src/main/kotlin/recipe.android.application.gradle.kts`** (01-RESEARCH.md lines 516-552, catalog-accessor-adjusted for precompiled-plugin context):
|
||||
|
||||
```kotlin
|
||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
}
|
||||
|
||||
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
|
||||
android {
|
||||
namespace = "dev.ulfrx.recipe"
|
||||
compileSdk = libs.findVersion("android-compileSdk").get().toString().toInt()
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "dev.ulfrx.recipe"
|
||||
minSdk = libs.findVersion("android-minSdk").get().toString().toInt()
|
||||
targetSdk = libs.findVersion("android-targetSdk").get().toString().toInt()
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
CRITICAL: the version lookup is `libs.findVersion("android-compileSdk").get().toString().toInt()` — NOT `libs.versions.android.compileSdk.get().toInt()` (that accessor does not exist in precompiled plugins — PITFALL #1).
|
||||
|
||||
---
|
||||
|
||||
**File 7: `build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts`** (01-RESEARCH.md lines 558-601, quoted-config variant per PATTERNS.md line 395):
|
||||
|
||||
```kotlin
|
||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
|
||||
plugins {
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
id("io.ktor.plugin")
|
||||
id("org.flywaydb.flyway")
|
||||
application
|
||||
}
|
||||
|
||||
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
compilerOptions {
|
||||
allWarningsAsErrors.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"implementation"(libs.findLibrary("ktor-serverCore").get())
|
||||
"implementation"(libs.findLibrary("ktor-serverNetty").get())
|
||||
"implementation"(libs.findLibrary("ktor-serverContentNegotiation").get())
|
||||
"implementation"(libs.findLibrary("ktor-serializationKotlinxJson").get())
|
||||
"implementation"(libs.findLibrary("logback").get())
|
||||
"implementation"(libs.findLibrary("flyway-core").get())
|
||||
"implementation"(libs.findLibrary("flyway-database-postgresql").get())
|
||||
"implementation"(libs.findLibrary("postgresql").get())
|
||||
"testImplementation"(libs.findLibrary("ktor-serverTestHost").get())
|
||||
"testImplementation"(libs.findLibrary("kotlin-testJunit").get())
|
||||
}
|
||||
|
||||
flyway {
|
||||
url = System.getenv("DATABASE_URL") ?: "jdbc:postgresql://localhost:5432/recipe"
|
||||
user = System.getenv("DATABASE_USER") ?: "recipe"
|
||||
password = System.getenv("DATABASE_PASSWORD") ?: "recipe"
|
||||
locations = arrayOf("classpath:db/migration")
|
||||
cleanDisabled = true
|
||||
baselineOnMigrate = true
|
||||
validateOnMigrate = true
|
||||
}
|
||||
```
|
||||
|
||||
CRITICAL:
|
||||
- `"implementation"(...)` with quoted-string configuration is MANDATORY inside precompiled plugins — the unquoted form is a typed method that only exists in module build scripts.
|
||||
- The `flyway { }` block is for CLI ergonomics (`./gradlew flywayInfo`). Runtime migration uses the Java API (Plan 05 wires this).
|
||||
|
||||
---
|
||||
|
||||
After writing all 7 files, verify that `build-logic/build.gradle.kts` can see the catalog by running a syntax-only check. No `./gradlew build` yet — Plan 03 wires the modules.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>test -f build-logic/settings.gradle.kts && test -f build-logic/build.gradle.kts && test -f build-logic/src/main/kotlin/recipe.quality.gradle.kts && test -f build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts && test -f build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts && test -f build-logic/src/main/kotlin/recipe.android.application.gradle.kts && test -f build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts && grep -q 'from(files("../gradle/libs.versions.toml"))' build-logic/settings.gradle.kts && grep -q '`kotlin-dsl`' build-logic/build.gradle.kts && grep -q 'asDependency' build-logic/build.gradle.kts && grep -q 'id("org.jetbrains.kotlin.multiplatform")' build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts && grep -q 'id("recipe.kotlin.multiplatform")' build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts && ! grep -q 'id("org.jetbrains.kotlin.multiplatform")' build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts && grep -q '"implementation"' build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts && grep -q 'extensions.getByType<VersionCatalogsExtension>' build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- All 7 files exist at their declared paths
|
||||
- `build-logic/settings.gradle.kts` contains literal `from(files("../gradle/libs.versions.toml"))`
|
||||
- `build-logic/settings.gradle.kts` ends with `rootProject.name = "build-logic"`
|
||||
- `build-logic/build.gradle.kts` contains `` `kotlin-dsl` `` (triple-backtick plugin alias)
|
||||
- `build-logic/build.gradle.kts` defines the `Provider<PluginDependency>.asDependency()` extension function
|
||||
- `build-logic/build.gradle.kts` has exactly 10 `compileOnly(libs.plugins.*.asDependency())` calls (kotlinMultiplatform, androidApplication, androidLibrary, composeMultiplatform, composeCompiler, composeHotReload, kotlinJvm, ktor, spotless, flywayPlugin)
|
||||
- `recipe.kotlin.multiplatform.gradle.kts` contains `id("org.jetbrains.kotlin.multiplatform")` (exactly ONCE, in the plugins block)
|
||||
- `recipe.kotlin.multiplatform.gradle.kts` contains `baseName = "ComposeApp"` (D-20 / PITFALL #10)
|
||||
- `recipe.kotlin.multiplatform.gradle.kts` contains `jvmToolchain(21)` AND `JvmTarget.JVM_11` AND `JvmTarget.JVM_21` (D-08 split)
|
||||
- `recipe.kotlin.multiplatform.gradle.kts` contains `allWarningsAsErrors.set(true)` at the `kotlin { compilerOptions { } }` extension level (D-11)
|
||||
- `recipe.kotlin.multiplatform.gradle.kts` does NOT contain `js {` or `iosX64` (D-01 / D-02)
|
||||
- `recipe.compose.multiplatform.gradle.kts` contains `id("recipe.kotlin.multiplatform")` AND does NOT contain `id("org.jetbrains.kotlin.multiplatform")` (PITFALL #2 guard)
|
||||
- `recipe.compose.multiplatform.gradle.kts` contains `id("org.jetbrains.compose.hot-reload")` (preserves commit c50d747)
|
||||
- `recipe.android.application.gradle.kts` contains `namespace = "dev.ulfrx.recipe"` (D-20)
|
||||
- `recipe.android.application.gradle.kts` uses `libs.findVersion("android-compileSdk").get().toString().toInt()` (PITFALL #1)
|
||||
- `recipe.jvm.server.gradle.kts` uses quoted `"implementation"` (not unquoted `implementation(...)` — quoted-config footgun)
|
||||
- `recipe.jvm.server.gradle.kts` contains `cleanDisabled = true` (PITFALL #6 safety)
|
||||
- `recipe.quality.gradle.kts` contains `targetExclude("**/build/**", "**/generated/**")` (avoids scanning generated Compose resources)
|
||||
- Every precompiled plugin that reads the catalog contains `extensions.getByType<VersionCatalogsExtension>().named("libs")`
|
||||
</acceptance_criteria>
|
||||
<done>build-logic/ scaffold complete; all 7 files follow canonical patterns; no PITFALL #1/#2/#7/#9/#10 violations detectable via grep.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Wire build-logic into root settings.gradle.kts and update root build.gradle.kts</name>
|
||||
<files>settings.gradle.kts, build.gradle.kts</files>
|
||||
<read_first>
|
||||
- settings.gradle.kts (current 37-line content — target of edit)
|
||||
- build.gradle.kts (current 12-line content — target of edit)
|
||||
- .planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md lines 749-767 (PITFALL #9 — includeBuild MUST be inside pluginManagement)
|
||||
- .planning/phases/01-project-infrastructure-module-wiring/01-PATTERNS.md lines 510-572 (settings.gradle.kts + root build.gradle.kts deltas)
|
||||
- .planning/phases/01-project-infrastructure-module-wiring/01-CONTEXT.md lines 107-109 (build-logic/ as included build — standard Gradle pattern)
|
||||
</read_first>
|
||||
<action>
|
||||
Edit two files.
|
||||
|
||||
---
|
||||
|
||||
**Edit 1: `settings.gradle.kts`** — add `includeBuild("build-logic")` as the FIRST statement inside the existing `pluginManagement { }` block. Do NOT move or remove any other line.
|
||||
|
||||
The current `pluginManagement { }` block (lines 4-16 of the existing file) should become:
|
||||
|
||||
```kotlin
|
||||
pluginManagement {
|
||||
includeBuild("build-logic")
|
||||
repositories {
|
||||
google {
|
||||
mavenContent {
|
||||
includeGroupAndSubgroups("androidx")
|
||||
includeGroupAndSubgroups("com.android")
|
||||
includeGroupAndSubgroups("com.google")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
PITFALL #9 is load-bearing: `includeBuild` MUST be inside `pluginManagement { }`, NOT at top level, and NOT inside `dependencyResolutionManagement { }`. Placing it elsewhere means child modules cannot resolve `id("recipe.*")` plugin IDs.
|
||||
|
||||
Do NOT modify:
|
||||
- Line 1: `rootProject.name = "recipe"`
|
||||
- Line 2: `enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")`
|
||||
- `dependencyResolutionManagement { }` block
|
||||
- `plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" }`
|
||||
- `include(":composeApp")`, `include(":server")`, `include(":shared")`
|
||||
|
||||
---
|
||||
|
||||
**Edit 2: `build.gradle.kts`** — append two new `alias(...) apply false` entries to the existing plugins block. Keep the existing 8 entries in their current order.
|
||||
|
||||
Result:
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// this is necessary to avoid the plugins to be loaded multiple times
|
||||
// in each subproject's classloader
|
||||
alias(libs.plugins.androidApplication) apply false
|
||||
alias(libs.plugins.androidLibrary) apply false
|
||||
alias(libs.plugins.composeHotReload) apply false
|
||||
alias(libs.plugins.composeMultiplatform) apply false
|
||||
alias(libs.plugins.composeCompiler) apply false
|
||||
alias(libs.plugins.kotlinJvm) apply false
|
||||
alias(libs.plugins.kotlinMultiplatform) apply false
|
||||
alias(libs.plugins.ktor) apply false
|
||||
alias(libs.plugins.spotless) apply false
|
||||
alias(libs.plugins.flywayPlugin) apply false
|
||||
}
|
||||
```
|
||||
|
||||
Why the `apply false` entries: Gradle's plugin classloader uses these declarations as hints when the plugin is applied through an included-build's precompiled plugin. `recipe.quality` applies `com.diffplug.spotless` and `recipe.jvm.server` applies `org.flywaydb.flyway` — the root `apply false` entries ensure a single resolved classpath per plugin ID (per the existing template's comment).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>grep -q 'includeBuild("build-logic")' settings.gradle.kts && awk '/pluginManagement \{/,/^\}/' settings.gradle.kts | grep -q 'includeBuild("build-logic")' && ! awk '/dependencyResolutionManagement \{/,/^\}/' settings.gradle.kts | grep -q 'includeBuild' && grep -q 'alias(libs.plugins.spotless) apply false' build.gradle.kts && grep -q 'alias(libs.plugins.flywayPlugin) apply false' build.gradle.kts && grep -c 'apply false' build.gradle.kts | grep -q '^10$'</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `settings.gradle.kts` contains `includeBuild("build-logic")` exactly 1 time
|
||||
- That `includeBuild("build-logic")` line appears INSIDE the `pluginManagement { ... }` block (verifiable: `awk '/pluginManagement \{/,/^\}/' settings.gradle.kts | grep -q 'includeBuild("build-logic")'`)
|
||||
- `settings.gradle.kts` does NOT contain `includeBuild` anywhere else (NOT at top level, NOT in `dependencyResolutionManagement`)
|
||||
- `settings.gradle.kts` still contains `rootProject.name = "recipe"` (unmodified line 1)
|
||||
- `settings.gradle.kts` still contains `include(":composeApp")`, `include(":server")`, `include(":shared")` (unmodified)
|
||||
- `build.gradle.kts` contains `alias(libs.plugins.spotless) apply false`
|
||||
- `build.gradle.kts` contains `alias(libs.plugins.flywayPlugin) apply false`
|
||||
- `grep -c 'apply false' build.gradle.kts` returns `10` (8 existing + 2 new)
|
||||
- All 8 existing `alias(...)` lines are preserved
|
||||
</acceptance_criteria>
|
||||
<done>build-logic/ is discoverable as an included build for plugin resolution; root `build.gradle.kts` declares classloader hints for Spotless + Flyway.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Gradle build → build-logic/ (included build) | Same-repo; no external trust boundary. Precompiled plugins run in the Gradle daemon's JVM with full project access by design. |
|
||||
| build-logic precompiled plugins → Maven Central + plugin portal | Inherits repository set from `build-logic/settings.gradle.kts.dependencyResolutionManagement` (google, mavenCentral, gradlePluginPortal). Pinned plugin versions via catalog aliases. |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-01-02-01 | Tampering (supply chain) | Precompiled plugin classpath | mitigate | Plugin versions resolved exclusively from catalog aliases via `asDependency()` — no literal versions leak into build-logic/build.gradle.kts. D-09 catalog-only rule enforced by Plan 07's `tools/verify-no-version-literals.sh`. |
|
||||
| T-01-02-02 | Elevation of Privilege | `recipe.jvm.server` applying Flyway to non-server modules | mitigate | `recipe.jvm.server` is applied ONLY to `server/build.gradle.kts` (Plan 03). The plugin bundles `io.ktor.plugin` + `org.flywaydb.flyway` + Postgres JDBC — if accidentally applied to `composeApp`, AGP would fail at configuration time. Role-declaration design (D-06) makes misuse obvious. |
|
||||
| T-01-02-03 | Tampering | `recipe.quality` Spotless scanning untrusted paths | accept | Spotless config restricted via `target("src/**/*.kt")` + `targetExclude("**/build/**", "**/generated/**")`. No execution of scanned code; ktlint is pure static analysis. |
|
||||
| T-01-02-04 | Denial of Service | Misspelled plugin ID breaks entire root build | mitigate | Task 1 `<acceptance_criteria>` greps for exact plugin IDs and the `id("recipe.kotlin.multiplatform")` layering in `recipe.compose.multiplatform.gradle.kts`. Plan 03's `./gradlew help` invocations will surface any remaining typos immediately. |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
Phase-level verification for this plan:
|
||||
|
||||
- `tools/verify-no-version-literals.sh` still exits 0 (build-logic/build.gradle.kts is explicitly excluded by the script — the `asDependency()` coordinates contain a version string as part of the synthesized artifact coord, but the script excludes that single file).
|
||||
- No Gradle command is run yet — Plan 03 refactors modules to apply these plugins; until then, the root `./gradlew build` will still work against the EXISTING module build files (which have not yet been refactored).
|
||||
|
||||
Optional fast sanity check (if needed):
|
||||
- `./gradlew --help` exits 0 (proves `settings.gradle.kts` still parses).
|
||||
- `./gradlew help` (without args) exits 0 (proves `includeBuild` is legal).
|
||||
|
||||
These sanity checks are NOT in the `<automated>` verify blocks to keep them fast; run them once manually if a later plan fails unexpectedly.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 7 files under `build-logic/` created with canonical content (exact path listing in `files_modified`)
|
||||
- `settings.gradle.kts` has `includeBuild("build-logic")` inside `pluginManagement { }`
|
||||
- `build.gradle.kts` has 10 `apply false` entries (8 existing + 2 new for Spotless + Flyway)
|
||||
- No existing version aliases or source files modified in Plan 01 or prior
|
||||
- `tools/verify-no-version-literals.sh` continues to exit 0
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-project-infrastructure-module-wiring/01-02-SUMMARY.md` recording: file tree under `build-logic/`, any deviations from canonical excerpts (expected: none), and the final plugin ID list (10 applies from recipe-family + spotless/flyway).
|
||||
</output>
|
||||
Reference in New Issue
Block a user