{
  "templateVersion": "1.0.0",
  "description": "Golden Compose Multiplatform skeleton (green on Android + iOS). Stamp via token replacement + package-dir rename + feature-marker stripping.",
  "placeholders": [
    "__APP_NAME__",
    "__PACKAGE__",
    "__PACKAGE_PATH__",
    "__IOS_BUNDLE_ID__",
    "__REGION__",
    "__THEME_PREFIX__"
  ],
  "placeholderNotes": {
    "__APP_NAME__": "Human display name (Android label, iOS CFBundleDisplayName, Gradle rootProject.name). NOTE: rootProject.name in settings.gradle.kts cannot contain spaces — the engine must slugify the app name there (e.g. 'My App' -> 'MyApp' or 'my-app'). Used as a plain string elsewhere.",
    "__PACKAGE__": "Reverse-DNS package id, e.g. com.acme.app. Replaces BOTH the Android namespace/applicationId AND every Kotlin `package`/`import` statement in file CONTENTS. Also used as iOS bundle id default.",
    "__PACKAGE_PATH__": "Slash form of __PACKAGE__ (com/acme/app). Appears only inside string replacements where a path is needed; package SOURCE DIRECTORIES are stored literally as com/example/app and renamed by the engine (see packageSourceRoots + renameDirs).",
    "__IOS_BUNDLE_ID__": "iOS bundle id (project.yml PRODUCT_BUNDLE_IDENTIFIER + bundleIdPrefix, Info.plist, GoogleService-Info.plist).",
    "__REGION__": "Firebase Cloud Functions region, e.g. us-central1 (data/remote/FirebaseConfig.kt FIREBASE_FUNCTIONS_REGION).",
    "__THEME_PREFIX__": "PascalCase theme symbol prefix (e.g. AcmeTheme, AcmeColors, AcmeTokens, AcmeShapes, rememberAcmeTypography)."
  },
  "tokenizationNotes": [
    "Replace tokens in BOTH file contents and file/dir paths.",
    "Package source directories are stored literally as `.../kotlin/com/example/app`. The engine renames the `com/example/app` segment to __PACKAGE_PATH__ for each root in packageSourceRoots. File CONTENTS use __PACKAGE__ in package/import lines (already tokenized).",
    "The generated Compose resources accessor package is PINNED in composeApp/build.gradle.kts via `compose.resources { packageOfResClass = \"__PACKAGE__.generated.resources\" }`, so resource imports are stable and do NOT depend on rootProject.name. Do not remove that block.",
    "Replacement order is safe because all tokens are distinct literals; replace __PACKAGE_PATH__ and __PACKAGE__ independently."
  ],
  "packageSourceRoots": [
    "composeApp/src/commonMain/kotlin",
    "composeApp/src/commonTest/kotlin",
    "composeApp/src/desktopTest/kotlin",
    "composeApp/src/androidMain/kotlin",
    "composeApp/src/androidDebug/kotlin",
    "composeApp/src/androidRelease/kotlin",
    "composeApp/src/androidInstrumentedTest/kotlin",
    "composeApp/src/iosMain/kotlin",
    "composeApp/src/desktopMain/kotlin"
  ],
  "renameDirs": {
    "literalSegment": "com/example/app",
    "replaceWith": "__PACKAGE_PATH__",
    "appliesTo": "each entry in packageSourceRoots"
  },
  "binaryAssets": [
    "gradle/wrapper/gradle-wrapper.jar",
    "composeApp/src/commonMain/composeResources/font/DMSans_Regular.ttf",
    "composeApp/src/commonMain/composeResources/font/DMSans_Medium.ttf",
    "composeApp/src/commonMain/composeResources/font/DMSans_SemiBold.ttf",
    "composeApp/src/commonMain/composeResources/font/DMSans_Bold.ttf",
    "composeApp/src/androidMain/res/mipmap-*/ic_launcher*.png",
    "iosApp/iosApp/Assets.xcassets/**/*.png"
  ],
  "featureMarkers": {
    "syntax": "// >>> cmp:feature <name>  ...  // <<< cmp:feature <name>",
    "negation": "A name prefixed with ! (e.g. `cmp:feature !firebase`) marks a block kept ONLY when that feature is DISABLED. When the feature is ENABLED, the engine removes the whole negated block (markers + body); when DISABLED, it strips just the negated markers and keeps the body. Plain (non-negated) blocks are the inverse.",
    "nesting": "Blocks may nest (e.g. `room` containing `ios` in composeApp/build.gradle.kts and KoinHelper.kt). Process as a stack: a child block is dropped if it OR any ancestor is dropped.",
    "commentStyles": "Markers appear as Kotlin/Swift `//` comments, Ruby `#` comments (iosApp/Podfile), and could be any line-comment; match on the `cmp:feature` substring, not a fixed comment prefix.",
    "whenEnabled": "Strip only the two marker lines, keep the body.",
    "whenDisabled": "Remove the marker lines AND the body between them."
  },
  "features": {
    "harness": {
      "enabledByDefault": true,
      "paths": [
        "specs",
        ".claude/skills",
        ".githooks",
        "qa/approvals.json",
        "qa/comments.json",
        "qa/evidence"
      ],
      "notes": "The verification harness — the mode split (`--minimal` disables it; `create-cmp harden` installs it back). Three mechanisms share the subtraction, each already owned by the engine: (1) the paths above delete the app-owned governance surfaces (specs/, approvals+comments ledgers, evidence, generator skills, the pre-push receipt hook); (2) `cmp:feature harness`/`!harness` marker blocks give CLAUDE.md, AGENTS.md, README.md, CONTRIBUTING.md, docs/TESTING.md and .github/workflows/verify.yml their two honest renderings from one file; (3) the machine-owned lane region is subtracted by the ENGINE (src/lib/minimal.mjs), which keeps qa/preview-gallery.mjs plus its transitive qa/lib imports — derived from import statements, never listed here, so it cannot rot. .claude/settings.json is rewritten to the advisory-only hook set via the src/lib/hooks.mjs classifier (Stop hook and qa/-referencing nudges are full-mode only). qa/golden/ stays in BOTH modes — the desktopTest golden-tree tests read it and minimal keeps the full JVM test tier. qa/e2e is its own feature and stays orthogonal (Maestro flows run standalone)."
    },
    "ios": {
      "enabledByDefault": true,
      "paths": [
        "iosApp",
        "composeApp/src/iosMain"
      ],
      "notes": "When off: delete the listed paths AND strip the `ios` marker blocks from composeApp/build.gradle.kts (iosX64/iosArm64/iosSimulatorArm64 targets, iosMain deps, ios KSP room.compiler rows, ExperimentalForeignApi optIn). Also drop iosApp from the build/verify."
    },
    "firebase": {
      "enabledByDefault": true,
      "paths": [
        "composeApp/google-services.json",
        "iosApp/iosApp/GoogleService-Info.plist"
      ],
      "notes": "GitLive deps + emulator wiring. When off: strip `firebase` marker blocks in composeApp/build.gradle.kts (GitLive deps + the google-services plugin + the firebase debug/release buildTypes), build.gradle.kts (google-services plugin), AppApplication.kt, KoinHelper.kt, iOSApp.swift, iosApp/Podfile; and KEEP the `!firebase` blocks in composeApp/build.gradle.kts (the no-Firebase debug/release buildTypes). Remove google-services.json + GoogleService-Info.plist. data/remote/FirebaseConfig.kt holds only a const and is harmless to leave, but may be removed. The google-services plugin REQUIRES a google-services.json whose client.android_client_info.package_name matches applicationId — the template ships a structurally-valid placeholder with __PACKAGE__ already in that field; instruct the user to replace it with the real file from the Firebase console."
    },
    "room": {
      "enabledByDefault": true,
      "paths": [
        "composeApp/src/commonMain/kotlin/com/example/app/data/local",
        "composeApp/src/androidMain/kotlin/com/example/app/data/local",
        "composeApp/src/iosMain/kotlin/com/example/app/data/local",
        "composeApp/src/desktopMain/kotlin/com/example/app/data/local",
        "composeApp/schemas"
      ],
      "notes": "When off: strip `room` marker blocks in build.gradle.kts (ksp + room plugins), composeApp/build.gradle.kts (room/sqlite deps, kspAndroid/kspIos/kspDesktop room.compiler, the room {} schema block), AppApplication.kt, KoinHelper.kt and DesktopModule.kt (AppDatabase single + appContext). Delete the data/local dirs (incl. the desktopMain one when dev-client is on). The example `home` feature does NOT depend on Room (its ItemRepositoryImpl is in-memory), so it keeps building with Room off."
    },
    "e2e": {
      "enabledByDefault": true,
      "paths": [
        "qa/e2e"
      ],
      "notes": "Device-level E2E harness — Maestro flows (qa/e2e/*.yaml; replaced the Appium Node+pytest runners 2026-07-06). Feature key renamed from `appium` to `e2e` in 0.3.0 (old `--no-appium` CLI flag kept as a deprecated alias for `--no-e2e`). testTag selectors via TestTagAutomation resource-ids. No in-file markers except the docs sections; toggled by deleting qa/e2e."
    },
    "inspector": {
      "enabledByDefault": true,
      "paths": [
        "composeApp/src/androidDebug/kotlin/com/example/app/inspector",
        "composeApp/src/androidRelease/kotlin/com/example/app/inspector",
        "composeApp/src/desktopMain/kotlin/com/example/app/inspector",
        "qa/preview-gallery.mjs"
      ],
      "notes": "Two loops in one feature. (1) Live on-device inspector: debug-only loopback HTTP server on 127.0.0.1:9500 (GET /inspect/health|tree|design-system) serving the semantics tree + design-token catalog to the cmp-inspector MCP via `adb forward tcp:9500 tcp:9500`. The androidRelease dir holds only the no-op startInspector() twin — release builds contain no inspector code structurally. (2) Headless preview harness (tier 0): the desktopMain inspector dir (PreviewRegistry/PreviewHarness/PreviewSemanticsJson) + the :composeApp:renderScreens task render every registered screen to PNG + contract tree JSON with no device; qa/preview-gallery.mjs builds a self-contained index.html from the output (vendored pure-logic render libs live in qa/lib and stay when the feature is off — they are inert without the harness). The harness starts its OWN Koin (independent of the dev-client feature's DesktopModule) and its PreviewRegistry tab entries are regenerated from the configured `tabs` by the scaffolder (pipeline step b.3). When off: delete all three inspector dirs + qa/preview-gallery.mjs, strip the `inspector` marker blocks in AppApplication.kt (import + startInspector() call), composeApp/src/androidDebug/AndroidManifest.xml (INTERNET permission), and composeApp/build.gradle.kts (desktopMain compose.uiTest dep + the renderScreens task). On-device server: zero dependencies (java.net.ServerSocket + kotlinx-serialization from commonMain)."
    },
    "dev-client": {
      "enabledByDefault": true,
      "paths": [
        "composeApp/src/desktopMain/kotlin/com/example/app/main.kt",
        "composeApp/src/desktopMain/kotlin/com/example/app/di/DesktopModule.kt",
        "docs/dev-client.md"
      ],
      "notes": "Desktop dev-client: the hot-reload WINDOW on top of the always-present JVM tier. IMPORTANT (changed 2026-07-06): the jvm(\"desktop\") target, desktopMain deps, platform actuals (NetworkMonitor/DatabaseBuilder/TestTagAutomation .desktop.kt), and the kspDesktop room.compiler row are HARNESS INFRASTRUCTURE and are NOT gated by this feature — the verify lane's unit/conformance/golden/UI tests run on :composeApp:desktopTest in every feature combination. This feature gates only the interactive window: main.kt (411x891dp __APP_NAME__ dev-client window) + DesktopModule.kt (initDesktopKoin), the compose-hot-reload plugin (build.gradle.kts + composeApp/build.gradle.kts markers), the settings.gradle.kts foojay-resolver (JBR auto-provisioning), and the compose.desktop application block. The dev-client NEVER initializes/contacts Firebase — desktop DI binds NetworkMonitor (always-online) + Room (BundledSQLiteDriver into the OS temp dir); the example ItemRepositoryImpl is in-memory on every platform."
    }
  },
  "verify": {
    "android": "node qa/verify.mjs --profile scaffold",
    "androidMinimal": "./gradlew :composeApp:desktopTest :composeApp:assembleDebug",
    "androidBuildOnly": "./gradlew :composeApp:assembleDebug",
    "androidSmoke": "./gradlew :composeApp:installDebug && maestro test qa/e2e/smoke.yaml",
    "iosLink": "./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64",
    "ios": "cd iosApp && export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 && xcodegen generate && pod install && SIM_UDID=$(xcrun simctl list devices available | grep -Eo '[0-9A-F-]{36}' | head -1) && xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -configuration Debug ARCHS=arm64 ONLY_ACTIVE_ARCH=YES EXCLUDED_ARCHS=x86_64 -destination \"id=$SIM_UDID\" build",
    "iosNote": "On Apple Silicon, the KMP embedAndSignAppleFrameworkForXcode prebuild phase produces ONLY the active-arch (arm64) slice of ComposeApp.framework. A `-destination generic/platform=iOS Simulator` build demands a universal arm64+x86_64 binary and FAILS at CreateUniversalBinary. Constrain to arm64 (ARCHS=arm64 EXCLUDED_ARCHS=x86_64) and target a concrete arm64 simulator UDID — matching the linkDebugFrameworkIosSimulatorArm64 gate."
  },
  "stampPipeline": [
    "1. Copy template/ -> targetDir (preserve binary files).",
    "2. Delete disabled-feature paths. MUST happen BEFORE the package-dir rename: feature paths are declared with the literal com/example/app segment, so deleting after the rename silently misses package-rooted paths (the --no-room stale-sources bug).",
    "3. Strip/keep feature-marker blocks per the enabled feature set (stack-based; handle ! negation).",
    "4. Replace placeholder tokens in all text-file CONTENTS.",
    "5. Rename package source dirs com/example/app -> __PACKAGE_PATH__ under each packageSourceRoots entry.",
    "6. Slugify __APP_NAME__ for settings.gradle.kts rootProject.name (no spaces).",
    "7. Write local.properties (sdk.dir) and run verify.android (+ verify.ios on macOS when ios enabled)."
  ]
}
