---
phase: 01-project-infrastructure-module-wiring
plan: 03
type: execute
wave: 2
depends_on: [01, 02]
files_modified:
  - composeApp/build.gradle.kts
  - shared/build.gradle.kts
  - server/build.gradle.kts
  - shared/src/jsMain
autonomous: true
requirements: [INFRA-02, INFRA-06]
requirements_addressed: [INFRA-02, INFRA-06]

must_haves:
  truths:
    - "composeApp/build.gradle.kts applies recipe.kotlin.multiplatform + recipe.compose.multiplatform + recipe.android.application + recipe.quality, and nothing else (D-06 role declaration)"
    - "shared/build.gradle.kts applies recipe.kotlin.multiplatform + recipe.quality + androidLibrary alias, with explicitApi() set directly in the module (D-12)"
    - "server/build.gradle.kts applies recipe.jvm.server + recipe.quality, and keeps only the module-specific application { } block"
    - "The js target is removed from composeApp and shared (D-01); shared/src/jsMain/ directory is deleted"
    - "iosX64 target is never referenced (D-02) — only iosArm64 + iosSimulatorArm64 via the convention plugin"
    - "No version literals exist in any *.gradle.kts outside gradle/libs.versions.toml (INFRA-01 / D-09)"
    - "shared/ framework basename is overridden to 'Shared' (D-07, PITFALL #10); composeApp keeps 'ComposeApp' from the convention plugin"
  artifacts:
    - path: "composeApp/build.gradle.kts"
      provides: "Module build applying 4 recipe.* convention plugins + module-only source-set deps (androidMain, commonMain projects.shared, jvmMain desktop)"
      min_lines: 15
    - path: "shared/build.gradle.kts"
      provides: "Module build applying recipe.kotlin.multiplatform + recipe.quality + androidLibrary; enabling explicitApi(); overriding framework baseName to 'Shared'; keeping android { namespace } block"
      min_lines: 15
    - path: "server/build.gradle.kts"
      provides: "Module build applying recipe.jvm.server + recipe.quality; keeping application { mainClass } block and implementation(projects.shared) dep"
      min_lines: 10
  key_links:
    - from: "composeApp/build.gradle.kts"
      to: "build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts"
      via: "plugins { id(\"recipe.kotlin.multiplatform\") }"
      pattern: "id\\(\"recipe\\.kotlin\\.multiplatform\"\\)"
    - from: "composeApp/build.gradle.kts"
      to: "build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts"
      via: "plugins { id(\"recipe.compose.multiplatform\") }"
      pattern: "id\\(\"recipe\\.compose\\.multiplatform\"\\)"
    - from: "server/build.gradle.kts"
      to: "build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts"
      via: "plugins { id(\"recipe.jvm.server\") }"
      pattern: "id\\(\"recipe\\.jvm\\.server\"\\)"
---

<objective>
Refactor the three module build scripts (`composeApp/`, `shared/`, `server/`) to apply the convention plugins from Plan 02 and remove the content those plugins now own. Drop the `js` target (D-01), confirm `iosX64` stays absent (D-02), add `explicitApi()` + framework-basename override to `shared/` (D-12 / PITFALL #10), and ensure every module's `plugins { }` block reads as a role declaration (D-06). Also delete the `shared/src/jsMain/` source directory (D-01).

Purpose: This plan delivers INFRA-02's structural payoff — adding a new KMP module in the future should require only `plugins { id("recipe.kotlin.multiplatform") }` + source-set declarations, not copy-pasting Compose configs. It also delivers INFRA-06's structural prerequisite: after this refactor, `shared/` no longer pulls Compose transitively (because `recipe.compose.multiplatform` is applied only to `composeApp/`).

Output: Three rewritten `build.gradle.kts` files (each ≤40 lines), `shared/src/jsMain/` directory deleted. No `./gradlew build` run in this plan — Plan 04/05 verify via their own targets, Plan 07 runs the full green-build gate.
</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
@composeApp/build.gradle.kts
@shared/build.gradle.kts
@server/build.gradle.kts
@CLAUDE.md

<interfaces>
<!-- Plans 01 + 02 must be complete before this plan runs. -->

From gradle/libs.versions.toml (Plan 01 extended):
- `libs.plugins.androidLibrary` — still referenced as alias inside shared/build.gradle.kts
- `libs.compose.uiToolingPreview` — referenced from composeApp/build.gradle.kts module-specific deps
- `libs.androidx.activity.compose` — referenced from composeApp androidMain deps
- `libs.kotlinx.coroutinesSwing` — referenced from composeApp jvmMain deps
- `libs.compose.uiTooling` — referenced from composeApp debugImplementation
- `libs.koin.android` — NEW alias (Plan 01) for MainApplication's `androidContext(...)` in Plan 04

From build-logic/src/main/kotlin/ (Plan 02 created):
- `recipe.kotlin.multiplatform` — applies KMP, sets D-05 targets, JVM toolchain, adds koin-bom/koin-core/kermit to commonMain, kotlin-test to commonTest, allWarningsAsErrors
- `recipe.compose.multiplatform` — applies Compose MP + hot-reload on top of KMP, adds compose-* deps to commonMain
- `recipe.android.application` — applies com.android.application, sets namespace + SDK versions
- `recipe.jvm.server` — applies kotlin(jvm) + io.ktor.plugin + flyway + all server deps + quoted-config dependency block
- `recipe.quality` — applies Spotless + allWarningsAsErrors safety net
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Rewrite composeApp/build.gradle.kts and shared/build.gradle.kts, delete shared/src/jsMain/</name>
  <files>composeApp/build.gradle.kts, shared/build.gradle.kts, shared/src/jsMain</files>
  <read_first>
    - composeApp/build.gradle.kts (current 114 lines — target of rewrite)
    - shared/build.gradle.kts (current 55 lines — target of rewrite)
    - .planning/phases/01-project-infrastructure-module-wiring/01-PATTERNS.md lines 574-672 (exact deltas for composeApp + shared)
    - .planning/phases/01-project-infrastructure-module-wiring/01-CONTEXT.md D-01 (drop js), D-03 (no desktop packaging), D-12 (explicitApi on shared only), D-20 (namespace + baseName)
    - .planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md lines 1144-1155 (Open Question #1 — keep com.android.library on shared/ in Phase 1)
  </read_first>
  <action>
  Two file rewrites plus one directory deletion.

  **Rewrite 1: `composeApp/build.gradle.kts`** — replace entire file content with:

  ```kotlin
  plugins {
      id("recipe.kotlin.multiplatform")
      id("recipe.compose.multiplatform")
      id("recipe.android.application")
      id("recipe.quality")
  }

  kotlin {
      sourceSets {
          androidMain.dependencies {
              implementation(libs.compose.uiToolingPreview)
              implementation(libs.androidx.activity.compose)
              implementation(libs.koin.android)
          }
          commonMain.dependencies {
              implementation(libs.compose.uiToolingPreview)
              implementation(projects.shared)
          }
          jvmMain.dependencies {
              implementation(compose.desktop.currentOs)
              implementation(libs.kotlinx.coroutinesSwing)
          }
      }
  }

  dependencies {
      debugImplementation(libs.compose.uiTooling)
  }
  ```

  DELETIONS relative to the current file:
  - DROP all 3 imports on lines 1-3 (no longer needed — convention plugins supply JvmTarget/ExperimentalWasmDsl/TargetFormat)
  - DROP the original `plugins { alias(...) alias(...) }` block (lines 5-11) — replaced with 4 recipe.* IDs
  - DROP the `kotlin { androidTarget { ... } iosArm64() iosSimulatorArm64() jvm { ... } js { ... } wasmJs { ... } }` structural block (lines 13-46) — moved into `recipe.kotlin.multiplatform`
  - DROP `commonMain.dependencies` Compose entries (lines 52-62 — compose.runtime, compose.foundation, compose.material3, compose.ui, compose.components.resources, androidx.lifecycle.viewmodelCompose, androidx.lifecycle.runtimeCompose) — moved into `recipe.compose.multiplatform`. KEEP `implementation(projects.shared)` and the module-only `implementation(libs.compose.uiToolingPreview)` (the preview tooling is needed by `@Preview` annotations in composeApp's common code).
  - DROP `commonTest.dependencies { implementation(libs.kotlin.test) }` (lines 63-65) — moved into `recipe.kotlin.multiplatform`
  - DROP the entire `android { ... }` block (lines 73-98) — moved into `recipe.android.application`
  - DROP `compose.desktop { application { ... nativeDistributions { ... } } }` (lines 104-114) — D-03 says no desktop packaging

  ADDITIONS:
  - ADD `implementation(libs.koin.android)` to `androidMain.dependencies` (Plan 04's MainApplication.kt calls `androidContext(...)` which comes from koin-android; the catalog alias was added in Plan 01).

  KEEP:
  - `androidMain.dependencies { implementation(libs.compose.uiToolingPreview); implementation(libs.androidx.activity.compose) }` — Android-only deps
  - `jvmMain.dependencies { implementation(compose.desktop.currentOs); implementation(libs.kotlinx.coroutinesSwing) }` — Desktop-only deps
  - `dependencies { debugImplementation(libs.compose.uiTooling) }` — Android debug-only tooling

  **Rewrite 2: `shared/build.gradle.kts`** — replace entire file content with:

  ```kotlin
  plugins {
      id("recipe.kotlin.multiplatform")
      id("recipe.quality")
      alias(libs.plugins.androidLibrary)
  }

  kotlin {
      explicitApi()

      // Override framework baseName: shared exposes "Shared.framework" to Swift, while
      // composeApp's convention-plugin default is "ComposeApp.framework". (D-07 / PITFALL #10)
      targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().configureEach {
          binaries.withType<org.jetbrains.kotlin.gradle.plugin.mpp.Framework>().configureEach {
              baseName = "Shared"
          }
      }

      sourceSets {
          commonMain.dependencies {
              // Phase 1: intentionally empty. Domain models + DTOs land Phase 2+.
              // D-19 / INFRA-06: Do NOT add Ktor, Compose, or SQLDelight deps here — EVER.
          }
      }
  }

  android {
      namespace = "dev.ulfrx.recipe.shared"
      compileSdk = libs.versions.android.compileSdk.get().toInt()
      compileOptions {
          sourceCompatibility = JavaVersion.VERSION_11
          targetCompatibility = JavaVersion.VERSION_11
      }
      defaultConfig {
          minSdk = libs.versions.android.minSdk.get().toInt()
      }
  }
  ```

  DELETIONS relative to the current file:
  - DROP both imports on lines 1-2 (no longer needed)
  - DROP the original `plugins { alias(libs.plugins.kotlinMultiplatform); alias(libs.plugins.androidLibrary) }` (lines 4-7) — replaced with `id("recipe.kotlin.multiplatform")` + kept `alias(libs.plugins.androidLibrary)`
  - DROP the entire `kotlin { androidTarget { ... } iosArm64() iosSimulatorArm64() jvm { ... } js { ... } wasmJs { ... } ... }` structural block (lines 9-41) — moved into `recipe.kotlin.multiplatform`
  - DROP `js { browser() }` (lines 25-27) — D-01

  ADDITIONS:
  - ADD `explicitApi()` inside the `kotlin { }` block (D-12 — strict on shared/ only, configured directly in module)
  - ADD the framework baseName override block targeting `KotlinNativeTarget`/`Framework` (overrides the convention plugin's `"ComposeApp"` default to `"Shared"` — D-07 / PITFALL #10)

  KEEP:
  - `android { namespace = "dev.ulfrx.recipe.shared"; compileSdk; compileOptions; defaultConfig.minSdk }` — per 01-RESEARCH.md Open Question #1, keep `com.android.library` applied in Phase 1 (deferring the "do we need it" question to a future `recipe.android.library` plugin)

  Note on `libs.versions.android.compileSdk.get().toInt()` vs `libs.findVersion(...)`: the `libs.versions.*` accessor IS available in MODULE `build.gradle.kts` files (it's only unavailable in precompiled plugins — PITFALL #1 applies only there). So the typed accessor is correct here.

  **Directory deletion: `shared/src/jsMain/`**

  Delete the entire `shared/src/jsMain/` directory (contains `kotlin/dev/ulfrx/recipe/Platform.js.kt`). D-01 drops the `js` target; with `recipe.kotlin.multiplatform` no longer declaring `js()`, this source directory becomes orphaned.

  Run: `rm -rf shared/src/jsMain`

  Do NOT delete `shared/src/wasmJsMain/` — `wasmJs` is kept per D-01. `composeApp/src/webMain/` is the wasmJs source set, also kept.
  </action>
  <verify>
    <automated>grep -q 'id("recipe.kotlin.multiplatform")' composeApp/build.gradle.kts && grep -q 'id("recipe.compose.multiplatform")' composeApp/build.gradle.kts && grep -q 'id("recipe.android.application")' composeApp/build.gradle.kts && grep -q 'id("recipe.quality")' composeApp/build.gradle.kts && ! grep -q 'androidTarget' composeApp/build.gradle.kts && ! grep -q 'iosArm64' composeApp/build.gradle.kts && ! grep -q 'js {' composeApp/build.gradle.kts && ! grep -q 'nativeDistributions' composeApp/build.gradle.kts && ! grep -q '^android {' composeApp/build.gradle.kts && grep -q 'implementation(libs.koin.android)' composeApp/build.gradle.kts && grep -q 'id("recipe.kotlin.multiplatform")' shared/build.gradle.kts && grep -q 'id("recipe.quality")' shared/build.gradle.kts && grep -q 'explicitApi()' shared/build.gradle.kts && grep -q 'baseName = "Shared"' shared/build.gradle.kts && ! grep -q 'js {' shared/build.gradle.kts && ! test -d shared/src/jsMain && bash tools/verify-no-version-literals.sh && bash tools/verify-shared-pure.sh</automated>
  </verify>
  <acceptance_criteria>
    - `composeApp/build.gradle.kts` has exactly 4 `id("recipe.*")` lines: `recipe.kotlin.multiplatform`, `recipe.compose.multiplatform`, `recipe.android.application`, `recipe.quality`
    - `composeApp/build.gradle.kts` does NOT contain `androidTarget`, `iosArm64`, `iosSimulatorArm64`, `jvm {`, `js {`, `wasmJs {`, or any `binaries.framework` block (all moved to convention plugin)
    - `composeApp/build.gradle.kts` does NOT contain an `^android {` block header (moved to `recipe.android.application`)
    - `composeApp/build.gradle.kts` does NOT contain `nativeDistributions` or `compose.desktop { application { ... } }` (D-03)
    - `composeApp/build.gradle.kts` does NOT contain `import org.jetbrains.compose.desktop.application.dsl.TargetFormat` (D-03)
    - `composeApp/build.gradle.kts` contains `implementation(libs.koin.android)` inside an `androidMain.dependencies` block
    - `composeApp/build.gradle.kts` contains `implementation(projects.shared)` in `commonMain.dependencies` (preserved for Plan 04 usage)
    - `composeApp/build.gradle.kts` line count ≤ 30 (was 114)
    - `shared/build.gradle.kts` has `id("recipe.kotlin.multiplatform")` + `id("recipe.quality")` + `alias(libs.plugins.androidLibrary)` (exactly 3 plugin applications)
    - `shared/build.gradle.kts` contains `explicitApi()` (D-12)
    - `shared/build.gradle.kts` contains `baseName = "Shared"` (exactly that capitalization — PITFALL #10)
    - `shared/build.gradle.kts` does NOT contain `js {` or `iosX64`
    - `shared/build.gradle.kts` contains the `android { namespace = "dev.ulfrx.recipe.shared" }` block (kept per Open Question #1)
    - `shared/src/jsMain` directory no longer exists (`test ! -d shared/src/jsMain`)
    - `tools/verify-no-version-literals.sh` exits 0 (no version literals leaked during rewrite)
    - `tools/verify-shared-pure.sh` exits 0 (shared/commonMain has only Greeting.kt/Platform.kt/Constants.kt — no forbidden imports)
  </acceptance_criteria>
  <done>Both module builds apply recipe.* conventions; js target source dir deleted; explicitApi + Shared basename set on shared/.</done>
</task>

<task type="auto">
  <name>Task 2: Rewrite server/build.gradle.kts</name>
  <files>server/build.gradle.kts</files>
  <read_first>
    - server/build.gradle.kts (current 23 lines — target of rewrite)
    - .planning/phases/01-project-infrastructure-module-wiring/01-PATTERNS.md lines 674-706 (server/build.gradle.kts delta)
    - .planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md lines 556-605 (§ Pattern 7 — what's ALREADY in the convention plugin and does NOT need to be in the module)
    - .planning/phases/01-project-infrastructure-module-wiring/01-CONTEXT.md D-16 (server scope — Flyway, Postgres, /health)
  </read_first>
  <action>
  Replace the entire content of `server/build.gradle.kts` with:

  ```kotlin
  plugins {
      id("recipe.jvm.server")
      id("recipe.quality")
  }

  group = "dev.ulfrx.recipe"
  version = "1.0.0"

  application {
      mainClass.set("dev.ulfrx.recipe.ApplicationKt")

      val isDevelopment: Boolean = project.ext.has("development")
      applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
  }

  dependencies {
      implementation(projects.shared)
  }
  ```

  DELETIONS:
  - DROP original plugins block (lines 1-5 — `alias(libs.plugins.kotlinJvm); alias(libs.plugins.ktor); application`) → replaced with 2 recipe.* IDs. The `application` plugin is applied by `recipe.jvm.server`.
  - DROP individual dependency lines (lines 16-22 — `libs.logback`, `libs.ktor.serverCore`, `libs.ktor.serverNetty`, `libs.ktor.serverTestHost`, `libs.kotlin.testJunit`) → all moved into `recipe.jvm.server`.

  KEEP:
  - `group = "dev.ulfrx.recipe"` and `version = "1.0.0"` (module coordinates — per-module concern)
  - `application { mainClass.set(...) }` + `applicationDefaultJvmArgs` (per-module config — the `application` plugin is applied by `recipe.jvm.server` but this config is module-specific)
  - `implementation(projects.shared)` — module-specific project dependency (server depends on shared for Greeting, SERVER_PORT, future DTOs)

  Note: `ktor-serverContentNegotiation`, `ktor-serializationKotlinxJson`, `flyway-core`, `flyway-database-postgresql`, `postgresql` are ALL bundled in `recipe.jvm.server` and do NOT need to be declared here.
  </action>
  <verify>
    <automated>grep -q 'id("recipe.jvm.server")' server/build.gradle.kts && grep -q 'id("recipe.quality")' server/build.gradle.kts && ! grep -q 'libs.plugins.kotlinJvm' server/build.gradle.kts && ! grep -q 'libs.plugins.ktor' server/build.gradle.kts && grep -q 'mainClass.set("dev.ulfrx.recipe.ApplicationKt")' server/build.gradle.kts && grep -q 'implementation(projects.shared)' server/build.gradle.kts && ! grep -q 'libs.logback' server/build.gradle.kts && ! grep -q 'libs.ktor.serverCore' server/build.gradle.kts && bash tools/verify-no-version-literals.sh</automated>
  </verify>
  <acceptance_criteria>
    - `server/build.gradle.kts` has exactly 2 `id("recipe.*")` lines: `recipe.jvm.server`, `recipe.quality`
    - `server/build.gradle.kts` does NOT contain `alias(libs.plugins.kotlinJvm)` or `alias(libs.plugins.ktor)`
    - `server/build.gradle.kts` does NOT contain `libs.logback`, `libs.ktor.serverCore`, `libs.ktor.serverNetty`, `libs.ktor.serverTestHost`, or `libs.kotlin.testJunit` (all relocated to convention plugin)
    - `server/build.gradle.kts` contains `mainClass.set("dev.ulfrx.recipe.ApplicationKt")` (unchanged)
    - `server/build.gradle.kts` contains `implementation(projects.shared)` (unchanged)
    - `server/build.gradle.kts` contains `group = "dev.ulfrx.recipe"` and `version = "1.0.0"` (unchanged module coordinates)
    - `server/build.gradle.kts` line count ≤ 20 (was 23; effectively unchanged but deps block shrinks)
    - `tools/verify-no-version-literals.sh` exits 0
  </acceptance_criteria>
  <done>server build applies recipe.jvm.server + recipe.quality; module-only config (mainClass, shared dep) preserved.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Module build scripts → build-logic precompiled plugins | Same repo; plugins apply privileged build configuration (namespace, SDK versions, dep injection). No external trust boundary. |
| Gradle module configuration → dependency resolution | Same as Plan 02 — aliases resolved via `libs.versions.toml` (pinned); no runtime consequences until Plan 04/05 actually compile code. |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-03-01 | Tampering (supply-chain leak) | Accidental version literal in rewrites | mitigate | Every task's `<automated>` runs `tools/verify-no-version-literals.sh` which scans every `*.gradle.kts` after the rewrite. Any inlined version (e.g. a forgotten `"1.0.0"` as a dep version) fails the check. Note: `version = "1.0.0"` on `server/build.gradle.kts` line 2 is PROJECT coordinate, not a dependency version — the verify script targets `version\s*=\s*"[0-9]` inside dependency declarations only; project-version assignments pass (not declared as `libs.*` lookup). Verify script scope matches PATTERNS.md spec. |
| T-01-03-02 | Elevation of Privilege | Compose deps leak into shared/ | mitigate | `shared/build.gradle.kts` applies ONLY `recipe.kotlin.multiplatform` + `recipe.quality` + `androidLibrary` — NOT `recipe.compose.multiplatform`. Plan 07's `tools/verify-shared-pure.sh` will catch forbidden imports if they ever appear. |
| T-01-03-03 | Denial of Service | Missing `recipe.compose.multiplatform` application on composeApp breaks Compose | mitigate | Task 1 `<acceptance_criteria>` greps for all 4 recipe IDs explicitly. Plan 04 will fail at compile time if the Compose plugin ID is missing. |
| T-01-03-04 | Tampering | `js` target remnants in source tree after D-01 drop | mitigate | Task 1 explicitly deletes `shared/src/jsMain/` directory and greps for `js {` blocks. `composeApp/src/webMain/` (wasmJs target, kept) is NOT touched. |
</threat_model>

<verification>
Phase-level verification for this plan:

- All three `tools/verify-*.sh` scripts exit 0 after rewrites.
- `shared/src/jsMain/` directory no longer exists.
- `composeApp/build.gradle.kts` shrinks from 114 to ~30 lines — INFRA-02 payoff visible.
- `shared/build.gradle.kts` shrinks from 55 to ~35 lines and now sets `explicitApi()`.

Optional sanity check (NOT in `<automated>` — Plan 07 runs the full gate):
- `./gradlew :composeApp:help -q` emits a non-empty help output without a configuration error (proves plugin IDs resolve). Skip for speed — Plan 04 and Plan 05 will surface plugin-application errors via their own `./gradlew` targets.
</verification>

<success_criteria>
- `composeApp/build.gradle.kts` applies 4 recipe.* IDs and contains NO `kotlin { androidTarget { ... } ... }` structural block and NO `android { ... }` block and NO `nativeDistributions`
- `shared/build.gradle.kts` applies 3 plugins (2 recipe.* + androidLibrary), enables `explicitApi()`, overrides baseName to `"Shared"`
- `server/build.gradle.kts` applies 2 recipe.* IDs and keeps only `application { mainClass }` + `implementation(projects.shared)`
- `shared/src/jsMain/` deleted
- `tools/verify-no-version-literals.sh` exits 0
- `tools/verify-shared-pure.sh` exits 0
</success_criteria>

<output>
After completion, create `.planning/phases/01-project-infrastructure-module-wiring/01-03-SUMMARY.md` recording: final LOC of each module build file (target: composeApp ≤30, shared ≤35, server ≤20), any deviations from the canonical patterns (expected: none), and confirmation that `shared/src/jsMain/` is gone.
</output>
