diff --git a/README.md b/README.md
index b042293..e412cd1 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,15 @@ systems like Buck and Bazel.
[Presentation Link](https://www.beautiful.ai/player/-MLn7RnBBWeh7vePDoDq)
+## Getting started
+
+**New to Forma?** Start here:
+
+1. [**Android project tutorial**](docs/GETTING-STARTED.md) — mental model, run the sample, greenfield skeleton, target cheat sheet (F-015 / GH #53)
+2. [Sample app gold standard](docs/SAMPLE-APP.md) — multi-feature layout to copy
+3. [Dependency matrix](docs/DEPENDENCY-MATRIX.md) — what may depend on what
+4. [External deps catalogs](docs/DEPS-CATALOG.md) · [Compose](docs/COMPOSE.md) · [Environment](docs/ENV.md)
+
Configuration made easy:
``` gradle
@@ -177,6 +186,8 @@ Jetpack Compose: per-target `compose` flag + `composeWidget` — **F-013**
([`docs/COMPOSE.md`](docs/COMPOSE.md)).
Sample multi-feature gold standard: **F-014**
([`docs/SAMPLE-APP.md`](docs/SAMPLE-APP.md)).
+Android getting-started tutorial: **F-015**
+([`docs/GETTING-STARTED.md`](docs/GETTING-STARTED.md)).
Icons made by Freepik
from www.flaticon.com
diff --git a/TICKETS.md b/TICKETS.md
index 0a8075c..c40574b 100644
--- a/TICKETS.md
+++ b/TICKETS.md
@@ -22,7 +22,7 @@ Update this file when picking or finishing work. Cron workers must pick the **hi
| F-012 | done | External deps catalog UX + tooling polish | `library()` + pure generators + GAV validation + unit tests + `docs/DEPS-CATALOG.md` |
| F-013 | done | Compose support for Android library/ui targets | GH #96; `compose` flags + `composeWidget`; `docs/COMPOSE.md` |
| F-014 | done | Sample app: gold-standard multi-feature structure | packageName + source-root alignment; `docs/SAMPLE-APP.md`; home/characters pattern |
-| F-015 | todo | Android project tutorial (getting started) | GH #53 |
+| F-015 | done | Android project tutorial (getting started) | GH #53; `docs/GETTING-STARTED.md` + README entry |
| F-016 | todo | Plugin publish path (Portal user + target publish config) | GH #132, #133 |
| F-017 | todo | Configuration-time performance pass | GH #106, #42 |
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 5bd39e1..b93c331 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -324,6 +324,7 @@ Suggested extraction order (tickets F-020…F-024):
| ~~AGP 7.4.2 compile vs 8.1.2 runtime~~ (aligned 8.1.2) | F-003 done |
| ~~CI missing SDK + Java on some jobs~~ (GHA green on PR #153) | F-004 done |
| ~~Compose flag in settings, limited target support~~ → per-target flags + `composeWidget` | F-013 done |
+| ~~Missing Android getting-started tutorial~~ → [`docs/GETTING-STARTED.md`](GETTING-STARTED.md) | F-015 done |
| Shared `library` suffix for JVM vs Android library | F-020 |
| Plugin publish / Portal path | F-016 |
| Configuration-time cost (includer walk, stores) | F-017 |
@@ -334,13 +335,14 @@ Suggested extraction order (tickets F-020…F-024):
| Goal | Start here |
|------|------------|
+| New user / first project | [`docs/GETTING-STARTED.md`](GETTING-STARTED.md) |
| New target type | `AndroidTargets.kt` + new DSL file under `plugins/android/src/main/java/` + validator list |
| Tighten dep rules | `validator(...)` in that DSL file; update this doc §2.2 |
| Global SDK/AGP defaults | `androidProjectConfiguration` + sample `application/build.gradle.kts` |
| External deps UX | `plugins/deps` catalog + `build-dependencies` |
| Auto module discovery | `includer/` |
| CI | `.github/workflows/main.yml` |
-| Sample structure | `application/feature/**`, `binary/` |
+| Sample structure | `application/feature/**`, `binary/` · [SAMPLE-APP.md](SAMPLE-APP.md) |
---
diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md
new file mode 100644
index 0000000..dbb685e
--- /dev/null
+++ b/docs/GETTING-STARTED.md
@@ -0,0 +1,440 @@
+# Getting started — Android project with Forma (F-015 / GH #53)
+
+Forma is a **meta build system** for Android (Gradle plugin). You declare
+**targets** with typed, single-method builders (`api`, `impl`, `androidLibrary`,
+…) instead of hand-wiring AGP plugins, source sets, and ad-hoc dependency
+rules. Forma applies plugins, shared Android settings, and **dependency
+visibility validation** for you.
+
+This tutorial gets you from zero to a working multi-target Android app. For the
+full multi-feature reference layout, see [SAMPLE-APP.md](SAMPLE-APP.md).
+
+---
+
+## 1. Mental model (5 minutes)
+
+| Concept | Meaning |
+|---------|---------|
+| **Target** | One Gradle project / module with a Forma type (`impl`, `api`, …). Prefer “target” over “module” in Forma docs. |
+| **Suffix** | Folder / project name ending that encodes type (`…-impl`, `…-api`, `…-res`, `binary`, …). Validators use suffixes. |
+| **Composition root** | `androidApp` and/or `androidBinary` — where feature graphs are wired (Dagger-friendly). |
+| **Feature slice** | Typical feature folder: `api` + `impl` + `res` + `viewbinding`. |
+| **Catalogs** | External deps declared once (`projectDependencies` / typed catalogs), consumed via `deps(...)`. |
+
+**What Forma does for each target**
+
+1. Validates the project name matches the expected suffix.
+2. Applies the right Android / Kotlin plugins and shared `androidProjectConfiguration`.
+3. Enforces content rules (e.g. `api` cannot ship `res/`; `androidRes` is resources-only).
+4. Checks **project** dependencies against the [dependency matrix](DEPENDENCY-MATRIX.md).
+
+You still write Kotlin, layouts, and choose library coordinates — Forma owns
+structure and boundaries, not business logic.
+
+---
+
+## 2. Prerequisites
+
+| Tool | Version / notes |
+|------|-----------------|
+| JDK | **17+** |
+| Android SDK | Platform **34** (`compileSdk`; required for modern Compose AARs), **33** useful for `targetSdk` |
+| Build tools | 33.0.2 / 34.0.0 as used by the sample |
+| Gradle | Use the wrapper in the project (`./gradlew`); sample uses **8.4**, plugins build **8.3** |
+
+Worker / macOS install steps: [ENV.md](ENV.md) (`source scripts/env-mac.sh`).
+
+```bash
+# After SDK + JDK are installed:
+source scripts/env-mac.sh # if using this repo’s helper
+printf 'sdk.dir=%s\n' "$ANDROID_HOME" > application/local.properties
+```
+
+---
+
+## 3. Path A — run the sample app first (recommended)
+
+The `application/` directory is the **gold-standard product**. Build it before
+starting a greenfield project so you see real targets and wiring.
+
+```bash
+git clone https://github.com/formatools/forma.git
+cd forma
+source scripts/env-mac.sh # or export JAVA_HOME / ANDROID_HOME yourself
+printf 'sdk.dir=%s\n' "$ANDROID_HOME" > application/local.properties
+
+cd application
+./gradlew :binary:assembleDebug
+# or full verification:
+./gradlew build
+```
+
+Open `application/` in Android Studio (import the `application` Gradle project,
+not necessarily the monorepo root). Explore:
+
+| Path | Target | Role |
+|------|--------|------|
+| `binary/` | `androidBinary` | APK entry / composition root |
+| `root-app/` | `androidApp` | Application class + root DI |
+| `feature/home/{api,impl,res,viewbinding}` | feature slices | One vertical feature |
+| `common/greeting/compose-widget/` | `composeWidget` | Compose UI sample |
+| `settings.gradle.kts` | — | Plugin management, catalogs, includer |
+| `build.gradle.kts` | — | `androidProjectConfiguration` |
+
+Deep dive: [SAMPLE-APP.md](SAMPLE-APP.md).
+
+---
+
+## 4. Path B — greenfield project skeleton
+
+You can consume Forma from the **Gradle Plugin Portal** or via **composite
+`includeBuild`** (as this monorepo does for development).
+
+Published plugin id: `tools.forma.android` (badge version on the [README](../README.md);
+sample monorepo develops **0.1.3** line). Companion settings plugin:
+`tools.forma.includer` (auto-`include` subprojects that contain `build.gradle.kts`).
+
+### 4.1 `settings.gradle.kts`
+
+Minimal shape (Portal publish path — adjust version to the badge):
+
+```kotlin
+pluginManagement {
+ repositories {
+ google()
+ gradlePluginPortal()
+ mavenCentral()
+ }
+}
+
+plugins {
+ id("tools.forma.includer") version "0.2.0"
+ id("tools.forma.android") version "0.1.3"
+}
+
+// Discover every build.gradle.kts under this root (skip nested settings.gradle*).
+includer {
+ // true when folder names are not forced to end in the suffix only
+ // (sample uses nested paths like feature/home/impl).
+ arbitraryBuildScriptNames = true
+}
+
+rootProject.name = "my-app"
+
+// Optional: settings-level version catalog — see docs/DEPS-CATALOG.md
+// projectDependencies("libs", "com.jakewharton.timber:timber:5.0.1", …)
+```
+
+**Monorepo / composite** (how `application/` is wired today):
+
+```kotlin
+pluginManagement {
+ repositories { google() }
+ includeBuild("../plugins")
+ includeBuild("../includer")
+ // + build-settings conventions as in application/settings.gradle.kts
+}
+plugins {
+ id("tools.forma.includer")
+ id("tools.forma.android")
+}
+includer { arbitraryBuildScriptNames = true }
+```
+
+Do **not** list every module with `include(...)` when using Includer — drop a
+`build.gradle.kts` in a directory and it becomes a project. Nested projects that
+have their own `settings.gradle.kts` are skipped.
+
+Project path mapping (Includer): `feature/home/impl` → Gradle name
+`:feature-home-impl` (type-safe accessors). Forma `target(":feature:home:impl")`
+uses colon path form.
+
+### 4.2 Root `build.gradle.kts`
+
+```kotlin
+buildscript {
+ androidProjectConfiguration(
+ project = rootProject,
+ minSdk = 21,
+ targetSdk = 33,
+ compileSdk = 34, // 34 if you use modern Compose transitive AARs
+ agpVersion = "8.1.2", // keep aligned with plugin compile AGP
+ // compose = false, // project default for per-target compose flags
+ // composeCompilerVersion = "1.5.3", // match your Kotlin (1.9.10 → 1.5.3)
+ extraPlugins = listOf(
+ // optional: navigation safe-args, KSP, crashlytics, …
+ ),
+ )
+}
+```
+
+This stores shared Android settings, puts AGP on the buildscript classpath, and
+registers a root `clean` task. Child targets read these values automatically.
+
+Useful `gradle.properties` (sample-aligned):
+
+```properties
+android.useAndroidX=true
+android.nonTransitiveRClass=true
+org.gradle.parallel=true
+org.gradle.caching=true
+```
+
+### 4.3 First targets — “hello APK”
+
+Create folders + one-line builders. Suggested minimal tree:
+
+```
+my-app/
+├── settings.gradle.kts
+├── build.gradle.kts
+├── binary/build.gradle.kts # androidBinary
+├── root-app/build.gradle.kts # androidApp (Application + DI shell)
+├── root-res/build.gradle.kts # androidRes (launcher / app strings)
+└── feature/hello/
+ ├── api/build.gradle.kts
+ ├── impl/build.gradle.kts
+ ├── res/build.gradle.kts
+ └── viewbinding/build.gradle.kts # optional until you have layouts
+```
+
+**`binary/build.gradle.kts`** — composition root + APK:
+
+```kotlin
+androidBinary(
+ packageName = "com.example.myapp",
+ versionCode = 1,
+ versionName = "0.1.0",
+ dependencies = deps(
+ target(":root-app"),
+ target(":feature:hello:api"),
+ target(":feature:hello:impl"),
+ ),
+)
+```
+
+**`root-app/build.gradle.kts`**:
+
+```kotlin
+androidApp(
+ packageName = "com.example.myapp.root",
+ dependencies = deps(
+ androidx.core_ktx, // when using typed demo catalogs; or libs.* / bare deps
+ androidx.appcompat,
+ google.material,
+ ) + deps(
+ target(":root-res"),
+ ),
+)
+```
+
+> **Note on external catalogs:** the sample uses both typed catalogs under
+> `build-dependencies/` (`androidx.*`, `google.*`) and settings
+> `projectDependencies` (`libs.*`). A Portal-only app can start with
+> `projectDependencies` only — see [DEPS-CATALOG.md](DEPS-CATALOG.md).
+
+**`feature/hello/api/build.gradle.kts`** — JVM contracts only (no `res/`):
+
+```kotlin
+api(
+ packageName = "com.example.myapp.feature.hello.api",
+)
+```
+
+**`feature/hello/res/build.gradle.kts`** — resources only under `src/main/res`:
+
+```kotlin
+androidRes(
+ packageName = "com.example.myapp.feature.hello.res",
+)
+```
+
+**`feature/hello/impl/build.gradle.kts`** — Android implementation:
+
+```kotlin
+impl(
+ packageName = "com.example.myapp.feature.hello.impl",
+ dependencies = deps(
+ // external libs…
+ ) + deps(
+ target(":feature:hello:api"),
+ target(":feature:hello:res"),
+ // target(":feature:hello:viewbinding"),
+ ),
+)
+```
+
+Put Kotlin under `src/main/java//…` matching `packageName`.
+
+### 4.4 Build
+
+```bash
+printf 'sdk.dir=%s\n' "$ANDROID_HOME" > local.properties
+./gradlew :binary:assembleDebug
+```
+
+If validation fails, the error names the **consumer suffix**, the **illegal
+dependency**, and the allowed set — fix the edge, don’t weaken the graph.
+
+---
+
+## 5. Target cheat sheet (when to use what)
+
+| DSL | Suffix | Use for |
+|-----|--------|---------|
+| `androidBinary` | `binary` | Installable APK; top composition root |
+| `androidApp` | `app` | Application / root Android library (DI shell) |
+| `api` | `api` | Feature public contracts (JVM); no Android resources |
+| `impl` | `impl` | Feature implementation (UI, ViewModels, feature DI) |
+| `androidRes` | `res` | Strings, drawables, themes only |
+| `viewBinding` | `viewbinding` | Layout XML (+ light binding-facing types) |
+| `widget` | `widget` | Custom Views |
+| `composeWidget` | `compose-widget` | Compose UI components (always Compose) |
+| `androidLibrary` | `library` | Shared Android library (not a feature impl) |
+| `library` | `library` | Shared pure JVM library |
+| `uiLibrary` | `ui-library` | Shared UI building blocks for widgets |
+| `util` / `androidUtil` / `testUtil` | `util` / `android-util` / `test-util` | Small helpers |
+
+Full matrix and content rules: [DEPENDENCY-MATRIX.md](DEPENDENCY-MATRIX.md).
+Compose flags: [COMPOSE.md](COMPOSE.md).
+
+---
+
+## 6. Declaring dependencies
+
+### External
+
+```kotlin
+dependencies = deps(
+ libs.jakewhartonTimber, // from projectDependencies catalog
+ libs.bundles.room,
+ // or typed catalog objects in the sample: androidx.appcompat, google.dagger
+)
+```
+
+Combine groups with `+`:
+
+```kotlin
+dependencies = deps(androidx.core_ktx, google.material) +
+ deps(libs.jakewhartonTimber) +
+ deps(target(":feature:hello:api"))
+```
+
+### Project / internal
+
+```kotlin
+target(":feature:hello:api") // Forma path (colons)
+// Gradle project path is often :feature-hello-api (Includer dashes)
+```
+
+**Composition roots must list feature `api` + `impl` explicitly** — do not rely
+on transitive feature wiring alone. That keeps Dagger graphs honest and matches
+validator intent (F-011).
+
+---
+
+## 7. Rules that bite on day one
+
+1. **`impl` → `impl` is forbidden.** Cross-feature collaboration goes through
+ `api` (or shared `core` libraries).
+2. **`androidLibrary` cannot depend on `impl`.** Shared libs depend on contracts,
+ not feature implementations.
+3. **`api` cannot contain `res/`.** Put resources in `androidRes` / `viewBinding`.
+4. **`androidRes` only allows `res/` content** under `src/main`.
+5. **`viewBinding` only allows layout resource types.**
+6. **Name your folder / project so the suffix matches the DSL** (`…/impl` →
+ `impl { }`, not `androidLibrary`).
+7. **Align `packageName` with source roots** (`tools.forma.sample…` in the sample).
+
+If you fight the matrix, usually the **type** is wrong — not the tool.
+
+---
+
+## 8. Adding a second feature
+
+Copy the `feature/hello` slice to `feature/settings` (or follow
+`feature/characters/*` in the sample). Then:
+
+1. Implement `api` contracts the other features may need.
+2. Implement UI / DI in `impl` depending only on `api` + shared libs + own
+ `res` / `viewbinding`.
+3. Register **both** `api` and `impl` on `androidBinary` / `androidApp`.
+
+```kotlin
+// binary/build.gradle.kts (excerpt)
+dependencies = deps(
+ target(":root-app"),
+ target(":feature:hello:api"),
+ target(":feature:hello:impl"),
+ target(":feature:settings:api"),
+ target(":feature:settings:impl"),
+)
+```
+
+---
+
+## 9. Compose (optional)
+
+- Project default: `androidProjectConfiguration(compose = …, composeCompilerVersion = …)`.
+- Per target: `impl(…, compose = true)`, `androidBinary(…, compose = true)`, …
+- Dedicated UI: `composeWidget { }` (always enables Compose).
+- You still add Compose **libraries** yourself via catalogs.
+
+Details: [COMPOSE.md](COMPOSE.md). Sample:
+`application/common/greeting/compose-widget`.
+
+---
+
+## 10. Day-to-day workflow
+
+```bash
+# From the app root (application/ in this repo)
+./gradlew :binary:assembleDebug
+./gradlew :feature-home-impl:compileDebugKotlin
+./gradlew build
+```
+
+- Prefer small targets; keep feature folders consistent.
+- Put shared non-feature code under `core/` or `common/` (see sample).
+- When a validator fails, fix the dependency edge or re-slice the target —
+ don’t disable validation.
+
+---
+
+## 11. Common pitfalls
+
+| Symptom | Likely cause |
+|---------|----------------|
+| “Java not found” / old JDK | Use JDK 17+; export `JAVA_HOME` ([ENV.md](ENV.md)) |
+| SDK / `compileSdk` errors | Install platform 34; set `local.properties` `sdk.dir` |
+| Project not included | Missing `build.gradle.kts`, or nested `settings.gradle.kts` blocked Includer |
+| Illegal project dependency | Matrix violation — see [DEPENDENCY-MATRIX.md](DEPENDENCY-MATRIX.md) |
+| Empty / wrong package | `packageName` ≠ directory under `src/main/java` |
+| Compose compiler mismatch | Align `composeCompilerVersion` with Kotlin (sample: 1.5.3 ↔ 1.9.10) |
+| AGP resolution conflicts | Align consumer `agpVersion` with plugin AGP line (**8.1.2** today) |
+
+---
+
+## 12. Next reading
+
+| Doc | Topic |
+|-----|--------|
+| [SAMPLE-APP.md](SAMPLE-APP.md) | Multi-feature gold standard layout |
+| [DEPENDENCY-MATRIX.md](DEPENDENCY-MATRIX.md) | Allowed project-dep edges (code truth) |
+| [DEPS-CATALOG.md](DEPS-CATALOG.md) | External catalogs (`library`, `bundle`, `plugin`) |
+| [COMPOSE.md](COMPOSE.md) | Compose flags + `composeWidget` |
+| [ENV.md](ENV.md) | JDK / SDK bootstrap |
+| [ARCHITECTURE.md](ARCHITECTURE.md) | Monorepo + plugin module graph |
+| [VISION.md](VISION.md) | forma-core → JVM → Bazel roadmap |
+
+---
+
+## 13. Checklist — “I’m using Forma correctly”
+
+- [ ] Root `androidProjectConfiguration` sets SDK + AGP once
+- [ ] Includer (or explicit includes) discovers every target
+- [ ] Features use `api` / `impl` (+ `res` / `viewbinding` as needed)
+- [ ] No `impl` → `impl` edges
+- [ ] Composition roots list feature entrypoints explicitly
+- [ ] External versions live in a catalog, not copy-pasted GAVs in every target
+- [ ] `./gradlew :binary:assembleDebug` succeeds on a clean machine with JDK 17 + SDK 34
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index c7891d5..33e5711 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -2,6 +2,22 @@
Newest entries first.
+## 2026-07-12 — F-015 Android project tutorial (getting started)
+
+- **Ticket:** F-015 → `done` (GH #53)
+- **Branch:** `forma/F-015-android-tutorial` (from `origin/v2`)
+- **Docs:**
+ - Added `docs/GETTING-STARTED.md` — mental model, prerequisites, Path A (build
+ sample), Path B (greenfield settings/root/targets), target cheat sheet,
+ deps, day-one rules, second feature, Compose pointer, pitfalls, checklist
+ - README: Getting started section + F-015 pointer under Progress
+ - SAMPLE-APP + TICKETS status links
+- **Code:** docs-only (no plugin/sample behavior change)
+- **Verify:** markdown + cross-links only (no Gradle required for this slice)
+- **Commits/PRs:** this run — push + PR base `v2`
+- **Blockers:** none
+- **Next step:** F-016 Plugin publish path (Portal user + target publish config)
+
## 2026-07-12 — F-014 Sample app gold-standard multi-feature structure
- **Ticket:** F-014 → `done`
diff --git a/docs/SAMPLE-APP.md b/docs/SAMPLE-APP.md
index 306ada0..fc93b4f 100644
--- a/docs/SAMPLE-APP.md
+++ b/docs/SAMPLE-APP.md
@@ -4,7 +4,8 @@ The `application/` composite build is Forma’s **product reference**: a multi-f
Android app wired with the full target set (`api` / `impl` / `res` / `viewbinding` /
`widget` / `composeWidget` / composition roots).
-Use this layout when teaching Forma (F-015) or copying a starter structure.
+Use this layout when teaching Forma or copying a starter structure.
+**Getting started tutorial:** [GETTING-STARTED.md](GETTING-STARTED.md) (F-015 / GH #53).
## Layout