create-cmp-cli 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +65 -27
  2. package/bin/create-cmp.mjs +2 -1
  3. package/options.schema.json +5 -2
  4. package/package.json +2 -2
  5. package/src/commands/create.mjs +26 -3
  6. package/src/lib/toggle.mjs +2 -2
  7. package/template/.claude/settings.json +12 -0
  8. package/template/.claude/skills/add-feature/SKILL.md +131 -0
  9. package/template/.claude/skills/add-repository/SKILL.md +106 -0
  10. package/template/.claude/skills/add-screen/SKILL.md +130 -0
  11. package/template/.github/workflows/verify.yml +30 -8
  12. package/template/.gradle/8.11.1/checksums/checksums.lock +0 -0
  13. package/template/.gradle/8.11.1/fileChanges/last-build.bin +0 -0
  14. package/template/.gradle/8.11.1/fileHashes/fileHashes.lock +0 -0
  15. package/template/.gradle/8.11.1/gc.properties +0 -0
  16. package/template/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  17. package/template/.gradle/buildOutputCleanup/cache.properties +2 -0
  18. package/template/.gradle/vcs-1/gc.properties +0 -0
  19. package/template/CHANGELOG.md +14 -0
  20. package/template/CLAUDE.md +70 -0
  21. package/template/CONTRIBUTING.md +39 -0
  22. package/template/README.md +103 -0
  23. package/template/composeApp/build.gradle.kts +15 -8
  24. package/template/composeApp/src/commonMain/kotlin/com/example/app/di/AppModule.kt +4 -0
  25. package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/home/DetailScreen.kt +10 -2
  26. package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/home/HomeScreen.kt +10 -0
  27. package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/home/HomeViewModel.kt +6 -2
  28. package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/navigation/AppNavHost.kt +1 -0
  29. package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/navigation/Screen.kt +2 -1
  30. package/template/composeApp/src/commonTest/kotlin/com/example/app/data/remote/ItemRepositoryImplTest.kt +26 -0
  31. package/template/composeApp/src/commonTest/kotlin/com/example/app/domain/usecase/GetItemsUseCaseTest.kt +37 -0
  32. package/template/composeApp/src/commonTest/kotlin/com/example/app/presentation/home/HomeViewModelTest.kt +104 -0
  33. package/template/composeApp/src/commonTest/kotlin/com/example/app/testing/fakes/FakeItemRepository.kt +29 -0
  34. package/template/composeApp/src/desktopTest/kotlin/com/example/app/conformance/A11yConformanceTest.kt +64 -0
  35. package/template/composeApp/src/desktopTest/kotlin/com/example/app/conformance/ArchitectureConformanceTest.kt +143 -0
  36. package/template/composeApp/src/desktopTest/kotlin/com/example/app/presentation/home/HomeGoldenTreeTest.kt +78 -0
  37. package/template/composeApp/src/desktopTest/kotlin/com/example/app/presentation/home/HomeScreenTest.kt +77 -0
  38. package/template/composeApp/src/desktopTest/kotlin/com/example/app/presentation/navigation/AppShellTest.kt +79 -0
  39. package/template/composeApp/src/desktopTest/kotlin/com/example/app/testing/ComposeTestExt.kt +16 -0
  40. package/template/composeApp/src/desktopTest/kotlin/com/example/app/testing/StructuralTree.kt +63 -0
  41. package/template/docs/ARCHITECTURE.md +51 -0
  42. package/template/docs/TESTING.md +64 -0
  43. package/template/docs/adr/0001-adopt-the-create-cmp-harness-conventions.md +32 -0
  44. package/template/docs/adr/template.md +17 -0
  45. package/template/gitignore +8 -1
  46. package/template/gradle/libs.versions.toml +2 -0
  47. package/template/manifest.json +11 -8
  48. package/template/qa/e2e/README.md +29 -0
  49. package/template/qa/e2e/smoke.yaml +35 -0
  50. package/template/qa/evidence/schema.json +56 -0
  51. package/template/qa/golden/home.json +15 -0
  52. package/template/qa/lib/inputs-hash.mjs +112 -0
  53. package/template/qa/lib/token-drift.mjs +94 -0
  54. package/template/qa/receipt-check.mjs +113 -0
  55. package/template/qa/refusal-demo.mjs +491 -0
  56. package/template/qa/scaffold-feature.mjs +488 -0
  57. package/template/qa/verify.mjs +467 -0
  58. package/template/specs/README.md +33 -0
  59. package/template/specs/app-base.spec.md +30 -0
  60. package/template/specs/home.spec.md +18 -0
  61. package/template/qa/appium/README.md +0 -23
  62. package/template/qa/appium/lib/appium-client.mjs +0 -225
  63. package/template/qa/appium/package.json +0 -8
  64. package/template/qa/appium/run-android-smoke.mjs +0 -39
  65. package/template/tests/appium/cmp/conftest.py +0 -96
  66. package/template/tests/appium/cmp/test_smoke.py +0 -17
package/README.md CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  # create-cmp
4
4
 
5
- **Scaffold a green-building Kotlin/Compose Multiplatform app (Android + iOS) in minutes not hours.**
5
+ **The AI delivery harness for Kotlin/Compose Multiplatform — scaffold a green-building app (Android + iOS) in minutes, with best practices your AI is mechanically held to.**
6
6
 
7
- Toolchain auto-bootstrapped · Navigation & insets pre-solved · Clean Architecture wired · Appium harness ready.
7
+ Toolchain auto-bootstrapped · Navigation & insets pre-solved · Clean Architecture wired · Test pyramid + E2E flows included · AI-native inspector & verification lane.
8
8
 
9
9
  [![CI](https://github.com/kvdm-co-pilot/create-cmp/actions/workflows/ci.yml/badge.svg)](https://github.com/kvdm-co-pilot/create-cmp/actions/workflows/ci.yml)
10
+ [![npm version](https://img.shields.io/npm/v/create-cmp-cli.svg)](https://www.npmjs.com/package/create-cmp-cli)
10
11
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
11
12
  [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md)
12
13
  [![Kotlin Multiplatform](https://img.shields.io/badge/Kotlin-Multiplatform-7F52FF.svg?logo=kotlin&logoColor=white)](https://kotlinlang.org/docs/multiplatform.html)
@@ -27,6 +28,14 @@ to React Native.
27
28
  freehand-generate your project), bootstraps the toolchain, and **proves a green build** before it
28
29
  reports success.
29
30
 
31
+ And it goes further than scaffolding: `create-cmp` is an **AI delivery harness**. In the age of
32
+ AI-driven development, the scarce thing isn't code — it's a machine-enforceable definition of
33
+ *correct*. Every generated project ships the architecture patterns as running exemplars (with
34
+ tests), the best practices as executable conformance checks, generation tools so the AI extends
35
+ the app right-by-construction, an AI-native structural inspector, and a verification lane that
36
+ refuses "done" without evidence. See [`docs/HARNESS-PLAN.md`](./docs/HARNESS-PLAN.md) for the
37
+ full product definition.
38
+
30
39
  ## Contents
31
40
 
32
41
  - [Quick start](#quick-start)
@@ -73,7 +82,7 @@ except `create` works on **any** KMP project, not only ones it scaffolded:
73
82
  | Command | What it does |
74
83
  |---|---|
75
84
  | `create-cmp [dir]` / `create-cmp create` | Scaffold a new app from the frozen golden template (the default command). |
76
- | `create-cmp doctor [--fix]` | Toolchain preflight (JDK/SDK/Xcode/Appium, consent-gated installs) **plus** project diagnosis when run inside a Gradle project: kotlin↔ksp lockstep, drift vs proven-green sets, the KSP2/iOS catch-22, sdk.dir, `~/.konan` bloat, disk space. `--fix` applies the safe heals. |
85
+ | `create-cmp doctor [--fix]` | Toolchain preflight (JDK/SDK/Xcode/Appium — legacy driver install for the pre-Maestro path, consent-gated installs) **plus** project diagnosis when run inside a Gradle project: kotlin↔ksp lockstep, drift vs proven-green sets, the KSP2/iOS catch-22, sdk.dir, `~/.konan` bloat, disk space. `--fix` applies the safe heals. |
77
86
  | `create-cmp upgrade [--dry-run]` | Migrate `gradle/libs.versions.toml` to the next proven-green version set — diff table first, surgical in-place edits with `.bak-upgrade` backups, kotlin↔ksp lockstep guardrail, `--verify` to prove the result. |
78
87
  | `create-cmp clean` | Cache & build-output hygiene: stale `~/.konan` toolchains + project `build/`/`.gradle/` dirs (sizes shown, consent-gated); `~/.gradle/caches` is size-reported only. |
79
88
  | `create-cmp verify [--target-dir .]` | Run the green-build gate (Android, and iOS on macOS) against an existing project. |
@@ -103,10 +112,25 @@ per run, so it can't silently drift:
103
112
  presentation{components,theme,navigation,<feature>} / di` with Koin modules registered and **one
104
113
  example feature wired end-to-end** as the copy-paste pattern.
105
114
  - **Theme & tokens** — `<Prefix>Theme`, `<Prefix>Tokens`, `<Prefix>Colors`, DM Sans.
106
- - **An Appium harness** — an Appium client + smoke runner and a sample smoke asserting the bottom nav
107
- renders, so "done" can be proven on a device.
115
+ - **The test pyramid, pre-built** — unit exemplars (Turbine + fakes), dependency-free architecture
116
+ conformance gates, Compose UI Tests, golden-tree structural baselines, and Maestro E2E flows —
117
+ bound to a committed behavior spec (`specs/*.spec.md`, Given/When/Then, stable clause ids) so a
118
+ `specCoverage` gate catches untested or untraceable behavior — plus the verify lane
119
+ (`qa/verify.mjs`) that runs it all and emits a typed PASS/FAIL/SKIP verdict + evidence receipt.
120
+ - **Extend it with Claude Code, no plugin required** — every generated project ships its own
121
+ `add-feature`/`add-screen`/`add-repository` skills (backed by the deterministic
122
+ `qa/scaffold-feature.mjs` stamper) that clone the exemplar feature end-to-end: Screen →
123
+ ViewModel → UseCase → Repository → DI → nav, tests at every layer, golden baseline included.
124
+ - **Mechanically enforced, not honor-system** — a generated `.claude/settings.json` Stop hook
125
+ refuses "done" unless a fresh, evidence-bound PASS receipt covers the changed surface (content-
126
+ hash bound, rebase/merge-proof), and CI independently re-checks the committed receipt still
127
+ attests `HEAD`. Proven against real violations: hardcoded colors, illegal layer imports, deleted
128
+ spec tests, and structural regressions are each refused and named by clause, 4/4 in the
129
+ repo's refusal-demo script.
108
130
  - **A toolchain doctor** — diagnoses *and* heals JDK, Android SDK + AVD, Xcode/CLT, CocoaPods,
109
- XcodeGen, Appium + drivers, Node. Idempotent, OS-aware, consent-gated.
131
+ XcodeGen, Appium + drivers (the legacy pre-Maestro path), Node. Idempotent, OS-aware, consent-gated.
132
+ The E2E flows themselves run on Maestro — install with
133
+ `curl -fsSL https://get.maestro.mobile.dev | bash`.
110
134
  - **AI-inspectable by default** — every generated app ships a debug-only live inspector
111
135
  (`127.0.0.1:9500`, loopback, structurally absent from release). An agent runs `connect_live` and
112
136
  reads the running UI as JSON — hierarchy, geometry, resolved design tokens, real navigation
@@ -134,7 +158,7 @@ placeholders and clear "drop your Firebase config here" instructions.
134
158
  | Auth | `email` / `phone` / `both` / `none` | `both` |
135
159
  | Firebase services | Firestore · Storage · Functions · FCM | all on |
136
160
  | Room local cache | on / off | on |
137
- | Appium harness | on / off | on |
161
+ | E2E flows (Maestro) | on / off | on |
138
162
  | Bottom-nav tabs | label + icon, any count | Home, Profile |
139
163
 
140
164
  _(Web/PWA is intentionally out of scope — Android + iOS only.)_
@@ -149,6 +173,10 @@ shared engine, two front doors. Install from the bundled marketplace:
149
173
  /plugin install create-cmp
150
174
  ```
151
175
 
176
+ No `/plugin` UI available (headless/non-interactive sessions)? See
177
+ [the settings.json fallback in `docs/USAGE.md`](./docs/USAGE.md#2-setup) — it's a real two-step
178
+ install (marketplace registration, then plugin install), not a one-line tweak.
179
+
152
180
  It bundles eight skills (plus the `cmp-inspector` MCP server), each with a deterministic engine
153
181
  behind it:
154
182
 
@@ -164,9 +192,10 @@ behind it:
164
192
  - **cmp-firebase-connect** — post-scaffold onboarding: create/reuse a Firebase project, register
165
193
  the app, and replace the placeholder config via the Firebase CLI (consent-gated), proven by a
166
194
  green build.
167
- - **cmp-test** — generate the Appium regression suite by *observing* the app: read the running
168
- UI's semantics tree and emit tests in the shipped harness style.
169
- - **cmp-qa-prep** — brings up the emulator + Appium session + smoke.
195
+ - **cmp-test** — generate the regression suite by *observing* the app: read the running UI's
196
+ semantics tree and emit Maestro E2E flows plus golden-tree snapshots in the shipped harness style.
197
+ - **cmp-qa-prep** — brings up the emulator + Maestro flow run + smoke (legacy Appium bring-up
198
+ path also supported).
170
199
 
171
200
  ## Why CMP, not React Native
172
201
 
@@ -179,8 +208,8 @@ app is **time-to-first-green-build**, and that's a tooling problem, not a merits
179
208
  - **Native performance and platform access** without the RN bridge tax.
180
209
  - **Reproducible by construction.** A frozen, version-locked, CI-gated template means the build that
181
210
  was green yesterday is green today — the exact property ad-hoc CMP setups lack.
182
- - **Proven, not assumed.** Every scaffold ends on a real build (and an Appium smoke), with a
183
- GREEN/FAIL verdict.
211
+ - **Proven, not assumed.** Every scaffold ends on the verify lane — real build + the full JVM
212
+ test tier — with a typed GREEN/FAIL verdict and an evidence receipt.
184
213
 
185
214
  If `create-next-app` made React the default for the web by removing setup friction, the goal here is
186
215
  the same for multiplatform mobile.
@@ -191,35 +220,44 @@ the same for multiplatform mobile.
191
220
  Front doors: npx create-cmp-cli + Claude Code plugin (cmp-new / cmp-doctor / cmp-qa-prep)
192
221
  │ one shared engine, two front doors
193
222
  Engine (Node, deterministic): copy → token-replace → rename packages → toggle features → VERIFY
194
- Golden template (frozen, CI'd): pinned versions · iOS shell · nav+insets · Clean Arch · DI · Appium
223
+ Golden template (frozen, CI'd): pinned versions · iOS shell · nav+insets · Clean Arch · DI · test pyramid
195
224
  ```
196
225
 
197
226
  The engine never puts an LLM in the hot path: it copies the template, replaces placeholders in file
198
227
  contents *and* paths, atomically renames package directories, toggles features (Firebase / auth type
199
- / Room / Appium), and runs the verify build. Determinism is the moat. See
200
- [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) for the full design.
228
+ / Room / the e2e harness feature key `e2e`, renamed from `appium` in 0.3.0; the old
229
+ `--no-appium` flag still works as a deprecated alias), and runs the verify
230
+ build. Determinism is the moat. See
231
+ [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) for the full design, and
232
+ [`docs/DOCUMENTATION.md`](./docs/DOCUMENTATION.md) for the map of every doc — what's
233
+ authoritative for what, and the standards each implements.
201
234
 
202
235
  ## Requirements
203
236
 
204
237
  - **Node.js ≥ 18** to run the scaffolder.
205
238
  - **macOS** for iOS output (Xcode, CocoaPods, XcodeGen). Android output works on macOS or Linux.
206
- - Everything else — JDK 17, Android SDK + emulator, CocoaPods, XcodeGen, Appium + drivers — the
207
- built-in `doctor` detects and (with your consent) installs. Xcode itself must be installed from the
208
- App Store; the doctor surfaces that as the one manual step.
239
+ - Everything else — JDK 17, Android SDK + emulator, CocoaPods, XcodeGen, Appium + drivers (legacy
240
+ e2e path) — the built-in `doctor` detects and (with your consent) installs. The E2E flows
241
+ themselves run on Maestro, installed separately with
242
+ `curl -fsSL https://get.maestro.mobile.dev | bash`. Xcode itself must be installed from the App
243
+ Store; the doctor surfaces that as the one manual step.
209
244
 
210
245
  ## Roadmap
211
246
 
212
247
  The full plan — six pillars from scaffold to store release, and the sequencing — lives in
213
- [`docs/ROADMAP.md`](./docs/ROADMAP.md). Near-term:
214
-
215
- - [ ] Publish to npm as `create-cmp-cli` (`npx create-cmp-cli`) release cut, publish pending.
216
- - [ ] Record the asciinema demo (`npx create-cmp-cli` → green Android + iOS).
217
- - [ ] Full Android + iOS build matrix in CI (currently CI runs the engine unit tests).
248
+ [`docs/ROADMAP.md`](./docs/ROADMAP.md). The AI delivery harness itself (exemplars → conformance
249
+ gates → in-project generation → mechanical enforcement → observability) is **complete** — see
250
+ [`docs/HARNESS-PLAN.md`](./docs/HARNESS-PLAN.md) for the per-layer detail. Near-term:
251
+
252
+ - [x] Publish to npm as `create-cmp-cli` (`npx create-cmp-cli@latest`) **live on the registry**;
253
+ `0.3.0` (the harness release) is prepared and pending its own publish.
254
+ - [x] AI-native Compose inspector — read a running app's hierarchy, geometry, and *resolved design
255
+ tokens* as structured JSON (no screenshots), including **live on-device inspection** over a
256
+ loopback debug server. See [`docs/INSPECTOR-PLAN.md`](./docs/INSPECTOR-PLAN.md).
257
+ - [x] Android build matrix in CI on every push; iOS matrix runs on manual dispatch.
258
+ - [ ] Publish `create-cmp-cli@0.3.0` to npm (needs an interactive OTP session) + record the
259
+ asciinema demo (scaffold → `add-feature` → gate refuses a violation → fix → PASS).
218
260
  - [ ] More example features and nav shapes.
219
- - **AI-native Compose inspector** — read a running app's hierarchy, geometry, and *resolved design
220
- tokens* as structured JSON (no screenshots). **Phase 0 (headless host-JVM render → inspect) is
221
- built and verified**; live-emulator inspection is next. See
222
- [`docs/INSPECTOR-PLAN.md`](./docs/INSPECTOR-PLAN.md).
223
261
 
224
262
  ## Contributing
225
263
 
@@ -74,8 +74,9 @@ function printHelp() {
74
74
  `create (scaffold) flags:\n` +
75
75
  ` --name --package --bundle-id --region --theme-prefix\n` +
76
76
  ` --ios/--no-ios --firebase/--no-firebase --auth <email|phone|both|none>\n` +
77
- ` --room/--no-room --appium/--no-appium --inspector/--no-inspector\n` +
77
+ ` --room/--no-room --e2e/--no-e2e --inspector/--no-inspector\n` +
78
78
  ` --dev-client/--no-dev-client (desktop JVM window + Compose Hot Reload)\n` +
79
+ ` (--appium/--no-appium accepted as deprecated aliases for --e2e/--no-e2e)\n` +
79
80
  ` --tabs Home:home,Profile:person\n` +
80
81
  ` --target-dir --verify/--no-verify --yes --force --dry-run-verify\n\n` +
81
82
  `doctor flags: --yes --dry-run --no-ios --no-install --target-dir <dir> --fix\n` +
@@ -14,7 +14,7 @@
14
14
  "platforms",
15
15
  "firebase",
16
16
  "room",
17
- "appium",
17
+ "e2e",
18
18
  "inspector",
19
19
  "devClient",
20
20
  "tabs",
@@ -78,7 +78,10 @@
78
78
  }
79
79
  },
80
80
  "room": { "type": "boolean" },
81
- "appium": { "type": "boolean" },
81
+ "e2e": {
82
+ "type": "boolean",
83
+ "description": "Device-level E2E harness (Maestro flows in qa/e2e/). Feature key renamed from `appium` to `e2e` in 0.3.0."
84
+ },
82
85
  "inspector": {
83
86
  "type": "boolean",
84
87
  "description": "Live on-device inspector: debug-only loopback HTTP server (127.0.0.1:9500) serving the Compose semantics tree + design-token catalog to the cmp-inspector MCP. Structurally absent from release builds."
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-cmp-cli",
3
- "version": "0.2.0",
4
- "description": "Scaffold a production-ready Kotlin/Compose Multiplatform app (Android + iOS) that builds green in minutes toolchain auto-bootstrapped, navigation/insets pre-solved, Clean Architecture wired, Appium harness ready. Installs the `create-cmp` command.",
3
+ "version": "0.3.1",
4
+ "description": "The AI delivery harness for Kotlin/Compose Multiplatform — scaffolds a green-building app (Android + iOS) in minutes and holds AI-driven changes to a machine-enforced verify lane with a committed evidence receipt. Installs the `create-cmp` command.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-cmp": "bin/create-cmp.mjs",
@@ -4,6 +4,29 @@
4
4
 
5
5
  import { flagBool } from "../lib/args.mjs";
6
6
 
7
+ // --- deprecated-flag compat -------------------------------------------------
8
+
9
+ /**
10
+ * Tri-state boolean flag with a deprecated alias: prefers `name`/`no-name`;
11
+ * if that's absent but the alias is present, uses it and prints a one-line
12
+ * deprecation warning. `--appium/--no-appium` was renamed to `--e2e/--no-e2e`
13
+ * in 0.3.0 (ADR-0002) — this keeps the old flag working.
14
+ */
15
+ function flagBoolWithAlias(flags, name, aliasName, dflt) {
16
+ const primarySet = flags[name] !== undefined || flags[`no-${name}`] !== undefined;
17
+ if (primarySet) return flagBool(flags, name, dflt);
18
+
19
+ const aliasSet = flags[aliasName] !== undefined || flags[`no-${aliasName}`] !== undefined;
20
+ if (aliasSet) {
21
+ process.stderr.write(
22
+ `Warning: --${aliasName}/--no-${aliasName} is deprecated; use --${name}/--no-${name} instead.\n`
23
+ );
24
+ return flagBool(flags, aliasName, dflt);
25
+ }
26
+
27
+ return dflt;
28
+ }
29
+
7
30
  // --- name helpers ------------------------------------------------------------
8
31
 
9
32
  function parseTabs(str) {
@@ -61,7 +84,7 @@ function buildConfigFromFlags(flags, positional) {
61
84
  fcm: flagBool(flags, "fcm", firebase),
62
85
  },
63
86
  room: flagBool(flags, "room", true),
64
- appium: flagBool(flags, "appium", true),
87
+ e2e: flagBoolWithAlias(flags, "e2e", "appium", true),
65
88
  inspector: flagBool(flags, "inspector", true),
66
89
  devClient: flagBool(flags, "dev-client", true),
67
90
  tabs: parseTabs(flags.tabs) || [
@@ -135,7 +158,7 @@ async function interactiveConfig(positional) {
135
158
  const extras = await prompts(
136
159
  [
137
160
  { type: "confirm", name: "room", message: "Room local cache?", initial: true },
138
- { type: "confirm", name: "appium", message: "Appium test harness?", initial: true },
161
+ { type: "confirm", name: "e2e", message: "E2E test harness (Maestro)?", initial: true },
139
162
  {
140
163
  type: "confirm",
141
164
  name: "inspector",
@@ -173,7 +196,7 @@ async function interactiveConfig(positional) {
173
196
  platforms: { android: true, ios: base.ios },
174
197
  firebase: { enabled: base.firebase, auth, firestore, storage, functions, fcm },
175
198
  room: extras.room,
176
- appium: extras.appium,
199
+ e2e: extras.e2e,
177
200
  inspector: extras.inspector,
178
201
  devClient: extras.devClient,
179
202
  tabs: parseTabs(extras.tabs) || [{ label: "Home", icon: "home" }],
@@ -86,7 +86,7 @@ export function stripFeatureBlocks(content, disabledFeatures) {
86
86
 
87
87
  /**
88
88
  * Map an engine config object to the set of DISABLED feature names that the
89
- * manifest understands: ios, firebase, room, appium, inspector, dev-client.
89
+ * manifest understands: ios, firebase, room, e2e, inspector, dev-client.
90
90
  * @param {object} config
91
91
  * @returns {Set<string>}
92
92
  */
@@ -95,7 +95,7 @@ export function disabledFeaturesFromConfig(config) {
95
95
  if (!config.platforms?.ios) disabled.add("ios");
96
96
  if (!config.firebase?.enabled) disabled.add("firebase");
97
97
  if (!config.room) disabled.add("room");
98
- if (!config.appium) disabled.add("appium");
98
+ if (!config.e2e) disabled.add("e2e");
99
99
  if (!config.inspector) disabled.add("inspector");
100
100
  if (!config.devClient) disabled.add("dev-client");
101
101
  return disabled;
@@ -0,0 +1,12 @@
1
+ {
2
+ "hooks": {
3
+ "Stop": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ { "type": "command", "command": "node qa/receipt-check.mjs --hook" }
8
+ ]
9
+ }
10
+ ]
11
+ }
12
+ }
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: add-feature
3
+ description: >-
4
+ Add a new conforming vertical-slice feature (Screen + ViewModel + UseCase + Repository + spec
5
+ + tests + golden tree + nav route + DI wiring) to this Compose Multiplatform app, cloned
6
+ deterministically from the `home` exemplar. Use this when the user wants to "add a feature",
7
+ "add a screen with data", "scaffold a feature", "create a new screen backed by a repository",
8
+ "add a list screen", or names a new domain noun they want a screen for (e.g. "add a Favorites
9
+ feature", "I need a Bookmarks screen"). Works with NO create-cmp plugin installed — the
10
+ stamper (`qa/scaffold-feature.mjs`) and this skill both ship inside the generated project.
11
+ ---
12
+
13
+ # add-feature — stamp a conforming vertical slice
14
+
15
+ > Spec-first, deterministic-stamp, gate-proven. The script (`qa/scaffold-feature.mjs`) does the
16
+ > mechanical work — copy the `home` exemplar file set, whole-word identifier rename, anchor
17
+ > injection into the three shared files. You (the AI) only refine spec wording and adapt the
18
+ > feature to its real shape. You are not done until `node qa/verify.mjs` PASSes and the receipt
19
+ > is committed — see this project's `CLAUDE.md`.
20
+
21
+ ## Why a stamper and not hand-written files
22
+
23
+ This project's whole thesis is that determinism beats freehand generation for anything
24
+ architecturally load-bearing (see `docs/adr/0001-*.md` if present, or just: every hand-written
25
+ file is a drift chance). `qa/scaffold-feature.mjs` produces a **conforming skeleton by
26
+ construction** — it passes the architecture conformance gates before you write a line of
27
+ feature-specific logic. Your job is to make it *behave* like the real feature, not to make it
28
+ *structurally correct* — that part is already done.
29
+
30
+ ## The flow
31
+
32
+ ### 1. Interview
33
+
34
+ Ask the human for the feature name (PascalCase, plural-ish noun — e.g. `Favorites`,
35
+ `Bookmarks`). Propose a singular entity name by stripping a trailing `s`/`ies` (`Favorites` →
36
+ `Favorite`, `Categories` → `Category`). Naive de-pluralization is unreliable for irregular nouns
37
+ — **always show your proposed entity name and let the human confirm or override it** before
38
+ proceeding (`--entity <EntityName>`).
39
+
40
+ ### 2. Dry-run
41
+
42
+ Run:
43
+
44
+ ```
45
+ node qa/scaffold-feature.mjs <FeatureName> --entity <EntityName> --dry-run
46
+ ```
47
+
48
+ Show the human the file plan and the anchor-injection diffs it prints. Confirm before stamping
49
+ for real — this is the last chance to catch a wrong entity name or a naming collision.
50
+
51
+ ### 3. Stamp
52
+
53
+ Run the same command without `--dry-run`:
54
+
55
+ ```
56
+ node qa/scaffold-feature.mjs <FeatureName> --entity <EntityName>
57
+ ```
58
+
59
+ This writes the new Screen/ViewModel/UseCase/Repository(+impl)/tests/fake, wires them into
60
+ `di/AppModule.kt`, `presentation/navigation/Screen.kt`, and `presentation/navigation/AppNavHost.kt`
61
+ at their `// cmp:anchor` markers, and writes `specs/<feature>.spec.md` with a default six-clause
62
+ set (`<FEATURE>-01..06`: loading, success, error, reload-after-failure, tap-navigates, golden
63
+ tree) — copied verbatim from the `home` exemplar's shape.
64
+
65
+ If it exits non-zero, read the message — it is actionable (name already taken, an anchor marker
66
+ is missing, or a name isn't a valid Kotlin identifier). Do not hand-edit around a stamper
67
+ failure; if an anchor is genuinely missing from a shared file, that is a template defect worth
68
+ flagging, not something to route around by hand-splicing.
69
+
70
+ ### 4. Refine the spec, then the behavior
71
+
72
+ The default spec clauses are placeholders shaped like the `home` exemplar (a plain list of
73
+ title/subtitle rows). **Rewrite the clause prose** in `specs/<feature>.spec.md` to describe the
74
+ feature's real behavior — the six clause **ids stay fixed** (`specCoverage` binds tests to ids,
75
+ not prose), only the wording changes. Propose the rewritten clauses to the human; get them
76
+ confirmed before moving on — this project's contract is spec-first.
77
+
78
+ Then adapt the generated code to match:
79
+
80
+ - If the feature isn't shaped like "a list of `{id, title, subtitle}`", update the entity's
81
+ fields in `domain/model/<Entity>.kt`, the sample data in `<Entity>RepositoryImpl.kt`, and the
82
+ screen's rendering in `presentation/<feature>/<Feature>Screen.kt` together — keep them
83
+ consistent with each other and with the tests.
84
+ - Update the copied tests (`<Feature>ViewModelTest.kt`, `<Feature>ScreenTest.kt`) to match
85
+ whatever you changed. The gate (step 6) will tell you exactly what you missed — a compile
86
+ error names the mismatch; a spec-coverage failure names an orphaned clause or tag.
87
+ - Leave the DI wiring, nav route, and screen scaffold as stamped unless the feature genuinely
88
+ needs a different shape (e.g. no navigation-on-tap — then remove the `-05` clause's
89
+ citing test and strike the clause through, don't leave it dangling).
90
+ - The new screen is reachable via `Screen.<Feature>` but is **not** wired into a bottom-nav tab
91
+ by this generator (MVP scope — pushed-route only). Promoting it to a tab is a manual edit to
92
+ `appTabs()` + `AppShell` call sites; a future `--tab` flag may automate this.
93
+
94
+ ### 5. Capture the golden tree
95
+
96
+ The golden baseline is **not** copied by the stamper (a copied one would silently mismatch the
97
+ adapted screen). Generate it fresh once the screen renders the real behavior:
98
+
99
+ ```
100
+ UPDATE_GOLDEN=1 ./gradlew :composeApp:desktopTest --tests "*<Feature>GoldenTree*"
101
+ ```
102
+
103
+ Review `qa/golden/<feature>.json` briefly — it should reflect the structure you intended, not a
104
+ copy-paste artifact. Commit it alongside the feature.
105
+
106
+ ### 6. Gate
107
+
108
+ ```
109
+ node qa/verify.mjs
110
+ ```
111
+
112
+ This must PASS. It proves: the spec's six clauses are all bound to a citing test
113
+ (`specCoverage`), the build compiles, unit tests pass (ViewModel + UseCase + Repository +
114
+ fakes), architecture conformance holds (`presentation` doesn't import `data`, the new
115
+ `*Screen.kt` carries a `testTag`, the new `*ViewModel.kt` has a matching test), the golden tree
116
+ matches what you just captured, and accessibility holds. **Not done until this is PASS and the
117
+ evidence receipt (`qa/evidence/latest.json`) is committed with your change** — this is this
118
+ project's standing definition of done (see `CLAUDE.md`).
119
+
120
+ If it fails: read the failing step's reason (it is worded for exactly this), fix the actual
121
+ behavior or spec/test binding, and re-run. Do not delete or weaken a test to reach green.
122
+
123
+ ## Guardrails
124
+
125
+ - This flow works identically with or without the create-cmp Claude Code plugin installed —
126
+ everything it needs (`qa/scaffold-feature.mjs`, this file) ships inside the generated project.
127
+ - Respect whichever toggles this project was stamped with (e.g. if Room or Appium/Maestro were
128
+ disabled at scaffold time, don't reintroduce them for the new feature).
129
+ - `add-feature` generates a pushed-nav-route screen, not a bottom-nav tab, and does not generate
130
+ a "tap → detail" destination (the exemplar's `DetailScreen.kt` is intentionally not copied —
131
+ MVP scope). Both are documented future extensions, not omissions to silently work around.
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: add-repository
3
+ description: >-
4
+ Add a data/domain slice ONLY — model, repository interface + impl, use case, and a
5
+ hand-written fake — to this Compose Multiplatform app, cloned deterministically from the
6
+ `home` exemplar's data layer. No screen, no ViewModel, no nav route, no spec clauses. Use this
7
+ when the user wants to "add a repository", "add a data source", "model a new entity", "new
8
+ entity with a repository", or names a domain noun they want backed by data before any UI exists
9
+ (e.g. "add a Tag repository", "I need a data source for Bookmarks"). Works with NO create-cmp
10
+ plugin installed — the stamper (`qa/scaffold-feature.mjs --preset repository`) and this skill
11
+ both ship inside the generated project.
12
+ ---
13
+
14
+ # add-repository — stamp a data/domain slice only
15
+
16
+ > Deterministic-stamp, gate-proven. The script (`qa/scaffold-feature.mjs --preset repository`)
17
+ > does the mechanical work — copy the `home` exemplar's data/domain files, whole-word identifier
18
+ > rename, DI injection for the repository + use case only. You (the AI) only customize the
19
+ > entity's fields and its sample data. You are not done until `node qa/verify.mjs` PASSes and the
20
+ > receipt is committed — see this project's `CLAUDE.md`.
21
+
22
+ This is the `repository` subset of `add-feature` — same stamper, same rename mechanic, filtered
23
+ to just the 5 data/domain files. Use it when you want the data layer to exist before any screen
24
+ consumes it (e.g. you're modeling several entities up front, or a screen for this entity will
25
+ come later via `add-screen`).
26
+
27
+ ## Why a stamper and not hand-written files
28
+
29
+ Every hand-written file is a drift chance in this project's architecture. `qa/scaffold-feature.mjs
30
+ --preset repository` produces a **conforming skeleton by construction** for the data/domain
31
+ layer: a domain model, a domain repository interface, a use case, a data-layer impl, and a
32
+ hand-written fake for tests — wired into Koin. Your job is to make the entity's shape and sample
33
+ data real, not to make the wiring correct — that part is already done.
34
+
35
+ ## The flow
36
+
37
+ ### 1. Name the entity
38
+
39
+ Ask the human for the entity name — PascalCase, singular (e.g. `Tag`, `Bookmark`, `Category`).
40
+ Unlike `add-feature`/`add-screen`, there is no feature name and no `--entity` flag here: the
41
+ positional argument **is** the entity.
42
+
43
+ ### 2. Dry-run
44
+
45
+ ```
46
+ node qa/scaffold-feature.mjs <Entity> --preset repository --dry-run
47
+ ```
48
+
49
+ Show the human the file plan (5 files: `domain/model/<Entity>.kt`,
50
+ `domain/repository/<Entity>Repository.kt`, `domain/usecase/Get<Entity>sUseCase.kt`,
51
+ `data/remote/<Entity>RepositoryImpl.kt`, `testing/fakes/Fake<Entity>Repository.kt`) and the DI
52
+ injection diffs (repository binding + use case factory only — no ViewModel, no nav). Confirm
53
+ before stamping for real.
54
+
55
+ ### 3. Stamp
56
+
57
+ ```
58
+ node qa/scaffold-feature.mjs <Entity> --preset repository
59
+ ```
60
+
61
+ If it exits non-zero, read the message — it is actionable (entity name already taken, or not a
62
+ valid Kotlin identifier). Do not hand-edit around a stamper failure.
63
+
64
+ ### 4. Customize the entity and its data
65
+
66
+ The generated shape mirrors `home`'s `Item` (an `{id, title, subtitle}` list entity). Adapt it to
67
+ the real entity:
68
+
69
+ - Update the fields in `domain/model/<Entity>.kt` to match the real shape.
70
+ - Update the sample/seed data in `<Entity>RepositoryImpl.kt` accordingly.
71
+ - Update `Fake<Entity>Repository.kt` (`testing/fakes/`) to match the model's new fields — it's
72
+ the hand-written fake every future test against this entity will use.
73
+ - Leave the DI wiring (`di/AppModule.kt`) as stamped unless you're renaming something — it's
74
+ already correct: `single<<Entity>Repository> { <Entity>RepositoryImpl() }` and
75
+ `factory { Get<Entity>sUseCase(get()) }`.
76
+
77
+ **No spec clauses are added by this preset, and that's intentional** — a bare repository has no
78
+ observable behavior to specify yet (no screen, no user-facing flow). Clauses attach once a
79
+ screen consumes this entity; that's `add-screen`'s job (`FEATURE-01..06` bound to the repository
80
+ you just built).
81
+
82
+ ### 5. Gate
83
+
84
+ ```
85
+ node qa/verify.mjs
86
+ ```
87
+
88
+ This must PASS. It proves: the build compiles, unit tests pass (use case + repository + fake),
89
+ and architecture conformance holds (`domain` imports nothing app-internal, `data` implements the
90
+ `domain` interface). specCoverage is untouched by this preset — expect the same clause/tag counts
91
+ as before you ran the stamper. **Not done until this is PASS and the evidence receipt
92
+ (`qa/evidence/latest.json`) is committed with your change.**
93
+
94
+ If it fails: read the failing step's reason and fix the actual behavior. Do not delete or weaken
95
+ a test to reach green.
96
+
97
+ ## Guardrails
98
+
99
+ - Works identically with or without the create-cmp Claude Code plugin installed — everything it
100
+ needs (`qa/scaffold-feature.mjs`, this file) ships inside the generated project.
101
+ - Respect whichever toggles this project was stamped with (e.g. Room disabled at scaffold time
102
+ stays disabled — this preset never introduces Room coupling, matching the exemplar's
103
+ dependency-light `RepositoryImpl` pattern).
104
+ - This preset stamps data/domain only. If the human actually wants a screen too, either run
105
+ `add-feature` (the full slice) instead, or follow up with `add-screen --entity <Entity>` once
106
+ this repository exists.