nativeapptemplate-agent 0.2.0 → 0.2.2

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 (47) hide show
  1. package/AGENTS.md +142 -0
  2. package/CLAUDE.md +1 -139
  3. package/README.md +89 -9
  4. package/dist/agents/judge.d.ts.map +1 -1
  5. package/dist/agents/judge.js +68 -16
  6. package/dist/agents/judge.js.map +1 -1
  7. package/dist/agents/repair.js +1 -1
  8. package/dist/agents/repair.js.map +1 -1
  9. package/dist/agents/workers/android.js +1 -1
  10. package/dist/agents/workers/ios.js +1 -1
  11. package/dist/agents/workers/rails.js +1 -1
  12. package/dist/mobile.d.ts +1 -0
  13. package/dist/mobile.d.ts.map +1 -1
  14. package/dist/mobile.js +6 -0
  15. package/dist/mobile.js.map +1 -1
  16. package/dist/repair-loop.js +1 -1
  17. package/dist/validation/discover.js +1 -1
  18. package/dist/validation/discover.js.map +1 -1
  19. package/dist/validation/index.d.ts +2 -2
  20. package/dist/validation/index.d.ts.map +1 -1
  21. package/dist/validation/index.js +1 -1
  22. package/dist/validation/index.js.map +1 -1
  23. package/dist/validation/launch.d.ts.map +1 -1
  24. package/dist/validation/launch.js +26 -9
  25. package/dist/validation/launch.js.map +1 -1
  26. package/dist/validation/layer2.js +1 -1
  27. package/dist/validation/layer2.js.map +1 -1
  28. package/dist/validation/scenarios/queue.d.ts.map +1 -1
  29. package/dist/validation/scenarios/queue.js +12 -0
  30. package/dist/validation/scenarios/queue.js.map +1 -1
  31. package/dist/validation/stage1.d.ts +2 -0
  32. package/dist/validation/stage1.d.ts.map +1 -1
  33. package/dist/validation/stage1.js +2 -0
  34. package/dist/validation/stage1.js.map +1 -1
  35. package/dist/validation/stage2-judge.d.ts +2 -0
  36. package/dist/validation/stage2-judge.d.ts.map +1 -1
  37. package/dist/validation/stage2-judge.js +22 -1
  38. package/dist/validation/stage2-judge.js.map +1 -1
  39. package/dist/validation/stage2.d.ts +1 -0
  40. package/dist/validation/stage2.d.ts.map +1 -1
  41. package/dist/validation/stage2.js +15 -0
  42. package/dist/validation/stage2.js.map +1 -1
  43. package/dist/validation/visual-judge.d.ts +37 -0
  44. package/dist/validation/visual-judge.d.ts.map +1 -1
  45. package/dist/validation/visual-judge.js +100 -16
  46. package/dist/validation/visual-judge.js.map +1 -1
  47. package/package.json +2 -1
package/AGENTS.md ADDED
@@ -0,0 +1,142 @@
1
+ # AGENTS.md
2
+
3
+ Project-wide instructions for AI coding agents (Claude Code, Codex, etc.). Keep this file short — it's loaded into context every session. For deep context, read the linked docs on demand.
4
+
5
+ ## What this project is
6
+
7
+ `nativeapptemplate-agent` — a Claude Code agent that turns a natural-language SaaS spec (e.g. "a walk-in queue for a barbershop") into a working three-platform implementation: Rails 8.1 API + native SwiftUI iOS + native Jetpack Compose Android. Built during the Built with Opus 4.7 Claude Code Hackathon, April 21–27, 2026.
8
+
9
+ **Read `docs/SPEC.md` before making non-trivial changes.** It's the source of truth for architecture, three-layer validation, operation paths (adapt vs. replace), and hackathon success criteria.
10
+
11
+ ## Model
12
+
13
+ - Use `claude-opus-4-7` for all agent invocations.
14
+ - Requires Claude Agent SDK `>= 0.2.111`.
15
+ - Do not set `thinking.type.enabled` — use `thinking: {type: "adaptive"}` instead (Opus 4.7 uses adaptive thinking).
16
+
17
+ ## Stack
18
+
19
+ - **Host language:** TypeScript (Claude Agent SDK `@anthropic-ai/claude-agent-sdk`)
20
+ - **Ruby subprocesses** for Rails AST / ERB / migration work (called from TypeScript)
21
+ - **External MCP:** [`mobile-next/mobile-mcp`](https://github.com/mobile-next/mobile-mcp) for iOS Simulator + Android Emulator UI automation. **Pin `npx -y @mobilenext/mobile-mcp@0.0.54`** — 0.0.55+ closes the stdio connection on startup (the pin lives in `src/mobile.ts` and the plugin's `.mcp.json`).
22
+ - **Node 22+ required.**
23
+
24
+ ## Coding conventions
25
+
26
+ - TypeScript `strict: true`; no implicit `any`.
27
+ - Prefer functions over classes — introduce a class only when state + lifecycle justify it (e.g. a long-lived MCP client wrapper). Stateless transforms are functions.
28
+ - Ruby subprocesses: shell out via `execFile("ruby", ["scripts/ruby/<script>.rb", ...])` from a thin wrapper in `src/ruby.ts`. Pass structured data as JSON on stdin/stdout, not positional CLI args. Each script must be self-contained and re-entrant.
29
+ - No comments explaining *what* the code does — name things well instead. Reserve comments for non-obvious *why*.
30
+
31
+ ## Substrate (what the agent operates on)
32
+
33
+ The agent targets BOTH the paid and free (MIT-licensed) editions — the same code path handles both, validated end-to-end. **Test the paid edition first**: the free edition is a strict subset of paid (paid = free + multi-tenancy + invitations + role permissions + org switching), so anything that works on paid works on free, but not vice-versa. Catching paid-only regressions first avoids "free passes, paid breaks" surprises.
34
+
35
+ Env vars point at the paid edition by default. Pick which substrate to use by pointing them at the corresponding repos:
36
+
37
+ - `$NATIVEAPPTEMPLATE_API` — Rails 8.1 API repo (`nativeapptemplateapi`, single repo serves both editions; multi-tenancy + invitations are conditional features)
38
+ - `$NATIVEAPPTEMPLATE_IOS` — SwiftUI iOS repo. Paid: `NativeAppTemplate` (Swift 23,003 LOC, adds org switching, invitations, role permissions). Free: `NativeAppTemplate-Free-iOS` (Swift 15,311 LOC).
39
+ - `$NATIVEAPPTEMPLATE_ANDROID` — Jetpack Compose Android repo. Paid: `NativeAppTemplate` (Kotlin 28,401 LOC). Free: `NativeAppTemplate-Free-Android` (Kotlin 19,521 LOC).
40
+
41
+ The renamer's substitution rules (Shop / Shopkeeper / ItemTag / NativeAppTemplate) apply identically to both editions. Paid-only concepts (Account, Member, Invitation) aren't in the rename plan, so paid features survive the generation intact. Both editions hit the same Rails OpenAPI server, so the reviewer's three-way diff works against either.
42
+
43
+ **Substrate repos are read-only from the agent's perspective.** The agent never commits, pushes, edits, or runs destructive commands inside the substrate directories. To customize, the agent copies the substrate into `./out/<spec-slug>/{rails,ios,android}/` as fresh git-initialized project directories, and works exclusively inside `./out/` thereafter. Treat the substrate paths the same way you'd treat a read-only mount.
44
+
45
+ ## Domain model (do not misread)
46
+
47
+ - `Shop` → per-tenant primary resource (not "store", not "shop" in e-commerce sense — a physical location with a walk-in queue)
48
+ - `Shopkeeper` → the authenticated user/owner of a Shop
49
+ - `ItemTag` (aka Number Tag) → a queue entry, attached to a Shop
50
+ - **ItemTag has TWO states: `Idled` ↔ `Completed`.** It's a toggle, not a three-state machine. If a user's spec wants three states (e.g. "in-service"), extend the machine rather than assume it.
51
+ - Free edition is single-organization (personal account is transparent). Paid edition adds multi-tenancy, invitations, roles, org switching — all out of scope for the rename pipeline (paid-only concepts aren't in the rename plan), but the agent still operates against paid substrates by default since paid is a superset of free.
52
+
53
+ ## Repository state
54
+
55
+ Pre-implementation as of this writing: `src/` and `scripts/ruby/` are empty, and there is no `package.json`. The npm commands below are the target interface — don't try to run them until the scaffolding lands. Check `src/` before assuming.
56
+
57
+ ## Commands
58
+
59
+ ```bash
60
+ # Build & test the agent (run from this repo root — once package.json exists)
61
+ npm run build
62
+ npm run test
63
+ npm run dev -- "your spec here"
64
+
65
+ # Substrate commands — run from the matching generated project dir in ./out/<slug>/
66
+ bin/dev # Rails API
67
+ xcodebuild -scheme NativeAppTemplate -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.2' build # iOS
68
+ ./gradlew assembleDebug && ./gradlew installDebug # Android
69
+
70
+ # Validation (run from this repo root)
71
+ rg -n "Shop|Shopkeeper|ItemTag" ./out/<slug>/ # Layer 1 rename completeness check
72
+ cloc --vcs=git "$NATIVEAPPTEMPLATE_API" # LOC measurement for a specific substrate repo
73
+
74
+ # mobile-mcp — invoked via natural language through Claude Code
75
+ # "Take a screenshot", "Tap on Sign Up", "List elements on screen", etc.
76
+ ```
77
+
78
+ ## Operations the agent performs
79
+
80
+ Three clean operations on the substrate (details in `docs/SPEC.md` section 4):
81
+
82
+ 1. **Rename the skeleton** — Shop → Clinic, Shopkeeper → Staff, propagated coherently across Rails + Swift + Kotlin.
83
+ 2. **Adapt or replace the domain module** — keep ItemTag for queue variants; strip and insert a new resource for non-queue SaaS.
84
+ 3. **Drive the build green** — Rails tests + xcodebuild + gradlew must all pass.
85
+
86
+ ## Validation (three layers, gated)
87
+
88
+ 1. **Structural** — ripgrep for leftover domain tokens + OpenAPI contract parity (Rails ↔ iOS ↔ Android).
89
+ 2. **Runtime** — Rails boots, iOS builds & launches, Android builds & launches (Stage 1); UI-driven CRUD scenario via mobile-mcp (Stage 2).
90
+ 3. **Semantic** — Opus 4.7 as judge (text + vision) scoring against a structured rubric. Three runs per screenshot, take the median.
91
+
92
+ ## Guardrails
93
+
94
+ - **Self-repair loop hard-capped at 5 iterations** per generated project, opt-in via `NATIVEAPPTEMPLATE_REPAIR` (targets Layer 1/2 only; Layer 3 + reviewer are surfaced, not auto-repaired). On exceed, surface residuals and exit. Code: `src/repair-loop.ts` + `src/agents/repair.ts`.
95
+ - Known-cryptic failure modes: Jetpack Compose compilation, Hilt DI. Slow down and verify rather than pattern-match on those.
96
+ - **Do not invent tests for the generated code.** The substrate already has tests; use them.
97
+ - **Never modify the substrate repos** — clone them fresh into `./out/<slug>/{rails,ios,android}` before editing.
98
+ - Token budget: keep per-sub-agent context under 500k tokens to leave headroom for self-repair loops.
99
+
100
+ ## Output layout
101
+
102
+ ```
103
+ ./out/<spec-slug>/
104
+ ├── rails/ # customized Rails API (git-initialized)
105
+ ├── ios/ # customized iOS project (git-initialized)
106
+ └── android/ # customized Android project (git-initialized)
107
+ ```
108
+
109
+ Each is an independent, buildable git repo.
110
+
111
+ ## Packaging
112
+
113
+ Ships as three surfaces today:
114
+ - `npx nativeapptemplate-agent "your spec"` — standalone CLI (primary)
115
+ - `npx -y -p nativeapptemplate-agent nativeapptemplate-agent-mcp` — stdio MCP server wrapping `dispatch()` as a `generate_app` tool, for any MCP-capable assistant (Claude Code, Cursor, Cline, Goose). The distribution multiplier. (The `-mcp` entry point is a *bin* of the `nativeapptemplate-agent` package, not its own package — hence `-p`; `npx -y nativeapptemplate-agent-mcp` 404s.)
116
+ - **Claude Code plugin** in `plugin/` — `claude --plugin-dir ./plugin` loads two skills: `/nativeapptemplate-agent:generate-app` (generate → validate → explain) and `/nativeapptemplate-agent:walk-app` (drive the running app via `mobile-mcp`). Bundles the generator MCP server + mobile-mcp. See `plugin/README.md`.
117
+
118
+ Plugin `.mcp.json` pins are load-bearing: `nativeapptemplate-agent@latest` (MCP servers spawn with cwd = repo root; without `@latest`, `npx -p` resolves the *local* package and fails) and `@mobilenext/mobile-mcp@0.0.54` (0.0.55+ closes stdio).
119
+
120
+ ## Hackathon success criteria (quick reminder — details in docs/SPEC.md section 11)
121
+
122
+ **Must-have for the demo video:**
123
+ - Clinic queue spec produces 3 projects passing Layer 1 + Layer 2 Stage 1
124
+ - Layer 3 rubric scores above threshold for captured home-screen shots
125
+ - 90s demo video of end-to-end run with validation report
126
+
127
+ **Stretch:** restaurant waitlist + task tracker also pass; self-repair loop fixes at least one real failure in the video.
128
+
129
+ ## Sibling docs
130
+
131
+ - `docs/SPEC.md` — full technical specification (read first for any non-trivial change)
132
+ - `ROADMAP.md` — post-hackathon direction, OSS vs hosted tracks, durable scope boundaries
133
+ - `docs-private/GTM.md` — business strategy (relevant when discussing product surface, pricing, or Phase A/B transitions). Gitignored, not for publication.
134
+ - `docs-private/PREP-CHECKLIST.md` — pre-hackathon setup (should already be all-green by the time development starts). Gitignored.
135
+
136
+ ## Anti-patterns to avoid
137
+
138
+ - **Don't reimplement mobile UI automation.** Use mobile-mcp. The hackathon is not about inventing device automation.
139
+ - **Don't extend scope beyond the queue / simple-CRUD-SaaS family** during the hackathon week. See `docs/SPEC.md` section 9 for the full non-goals list.
140
+ - **Test the paid edition first.** Free is a strict subset of paid, so running paid first catches regressions that wouldn't surface against free alone. The OSS reproducibility story (judges/contributors with no paid license) lives in the free edition — once paid is green, point env vars at the free repos and re-run for the public demo.
141
+ - **Don't skip the validation layers to save time.** They are the demo story. A run that green-builds without passing Layer 3 is a failed run.
142
+ - **Don't edit, commit, push, or run `git clean` / `rm` inside `$NATIVEAPPTEMPLATE_API`, `$NATIVEAPPTEMPLATE_IOS`, or `$NATIVEAPPTEMPLATE_ANDROID`.** Those are the developer's working copies of the substrate (paid by default; possibly free), shared with other projects on the same machine. Copy them into `./out/<slug>/` first; change nothing in place. If unsure whether a command is safe, ask.
package/CLAUDE.md CHANGED
@@ -1,139 +1 @@
1
- # CLAUDE.md
2
-
3
- Project-wide Claude Code instructions. Keep this file short — it's loaded into context every session. For deep context, read the linked docs on demand.
4
-
5
- ## What this project is
6
-
7
- `nativeapptemplate-agent` — a Claude Code agent that turns a natural-language SaaS spec (e.g. "a walk-in queue for a barbershop") into a working three-platform implementation: Rails 8.1 API + native SwiftUI iOS + native Jetpack Compose Android. Built during the Built with Opus 4.7 Claude Code Hackathon, April 21–27, 2026.
8
-
9
- **Read `docs/SPEC.md` before making non-trivial changes.** It's the source of truth for architecture, three-layer validation, operation paths (adapt vs. replace), and hackathon success criteria.
10
-
11
- ## Model
12
-
13
- - Use `claude-opus-4-7` for all agent invocations.
14
- - Requires Claude Agent SDK `>= 0.2.111`.
15
- - Do not set `thinking.type.enabled` — use `thinking: {type: "adaptive"}` instead (Opus 4.7 uses adaptive thinking).
16
-
17
- ## Stack
18
-
19
- - **Host language:** TypeScript (Claude Agent SDK `@anthropic-ai/claude-agent-sdk`)
20
- - **Ruby subprocesses** for Rails AST / ERB / migration work (called from TypeScript)
21
- - **External MCP:** [`mobile-next/mobile-mcp`](https://github.com/mobile-next/mobile-mcp) for iOS Simulator + Android Emulator UI automation (`npx -y @mobilenext/mobile-mcp@latest`)
22
- - **Node 22+ required.**
23
-
24
- ## Coding conventions
25
-
26
- - TypeScript `strict: true`; no implicit `any`.
27
- - Prefer functions over classes — introduce a class only when state + lifecycle justify it (e.g. a long-lived MCP client wrapper). Stateless transforms are functions.
28
- - Ruby subprocesses: shell out via `execFile("ruby", ["scripts/ruby/<script>.rb", ...])` from a thin wrapper in `src/ruby.ts`. Pass structured data as JSON on stdin/stdout, not positional CLI args. Each script must be self-contained and re-entrant.
29
- - No comments explaining *what* the code does — name things well instead. Reserve comments for non-obvious *why*.
30
-
31
- ## Substrate (what the agent operates on)
32
-
33
- The agent targets BOTH the paid and free (MIT-licensed) editions — the same code path handles both, validated end-to-end. **Test the paid edition first**: the free edition is a strict subset of paid (paid = free + multi-tenancy + invitations + role permissions + org switching), so anything that works on paid works on free, but not vice-versa. Catching paid-only regressions first avoids "free passes, paid breaks" surprises.
34
-
35
- Env vars point at the paid edition by default. Pick which substrate to use by pointing them at the corresponding repos:
36
-
37
- - `$NATIVEAPPTEMPLATE_API` — Rails 8.1 API repo (`nativeapptemplateapi`, single repo serves both editions; multi-tenancy + invitations are conditional features)
38
- - `$NATIVEAPPTEMPLATE_IOS` — SwiftUI iOS repo. Paid: `NativeAppTemplate` (Swift 23,003 LOC, adds org switching, invitations, role permissions). Free: `NativeAppTemplate-Free-iOS` (Swift 15,311 LOC).
39
- - `$NATIVEAPPTEMPLATE_ANDROID` — Jetpack Compose Android repo. Paid: `NativeAppTemplate` (Kotlin 28,401 LOC). Free: `NativeAppTemplate-Free-Android` (Kotlin 19,521 LOC).
40
-
41
- The renamer's substitution rules (Shop / Shopkeeper / ItemTag / NativeAppTemplate) apply identically to both editions. Paid-only concepts (Account, Member, Invitation) aren't in the rename plan, so paid features survive the generation intact. Both editions hit the same Rails OpenAPI server, so the reviewer's three-way diff works against either.
42
-
43
- **Substrate repos are read-only from the agent's perspective.** The agent never commits, pushes, edits, or runs destructive commands inside the substrate directories. To customize, the agent copies the substrate into `./out/<spec-slug>/{rails,ios,android}/` as fresh git-initialized project directories, and works exclusively inside `./out/` thereafter. Treat the substrate paths the same way you'd treat a read-only mount.
44
-
45
- ## Domain model (do not misread)
46
-
47
- - `Shop` → per-tenant primary resource (not "store", not "shop" in e-commerce sense — a physical location with a walk-in queue)
48
- - `Shopkeeper` → the authenticated user/owner of a Shop
49
- - `ItemTag` (aka Number Tag) → a queue entry, attached to a Shop
50
- - **ItemTag has TWO states: `Idled` ↔ `Completed`.** It's a toggle, not a three-state machine. If a user's spec wants three states (e.g. "in-service"), extend the machine rather than assume it.
51
- - Free edition is single-organization (personal account is transparent). Paid edition adds multi-tenancy, invitations, roles, org switching — all out of scope for the rename pipeline (paid-only concepts aren't in the rename plan), but the agent still operates against paid substrates by default since paid is a superset of free.
52
-
53
- ## Repository state
54
-
55
- Pre-implementation as of this writing: `src/` and `scripts/ruby/` are empty, and there is no `package.json`. The npm commands below are the target interface — don't try to run them until the scaffolding lands. Check `src/` before assuming.
56
-
57
- ## Commands
58
-
59
- ```bash
60
- # Build & test the agent (run from this repo root — once package.json exists)
61
- npm run build
62
- npm run test
63
- npm run dev -- "your spec here"
64
-
65
- # Substrate commands — run from the matching generated project dir in ./out/<slug>/
66
- bin/dev # Rails API
67
- xcodebuild -scheme NativeAppTemplate -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build # iOS
68
- ./gradlew assembleDebug && ./gradlew installDebug # Android
69
-
70
- # Validation (run from this repo root)
71
- rg -n "Shop|Shopkeeper|ItemTag" ./out/<slug>/ # Layer 1 rename completeness check
72
- cloc --vcs=git "$NATIVEAPPTEMPLATE_API" # LOC measurement for a specific substrate repo
73
-
74
- # mobile-mcp — invoked via natural language through Claude Code
75
- # "Take a screenshot", "Tap on Sign Up", "List elements on screen", etc.
76
- ```
77
-
78
- ## Operations the agent performs
79
-
80
- Three clean operations on the substrate (details in `docs/SPEC.md` section 4):
81
-
82
- 1. **Rename the skeleton** — Shop → Clinic, Shopkeeper → Staff, propagated coherently across Rails + Swift + Kotlin.
83
- 2. **Adapt or replace the domain module** — keep ItemTag for queue variants; strip and insert a new resource for non-queue SaaS.
84
- 3. **Drive the build green** — Rails tests + xcodebuild + gradlew must all pass.
85
-
86
- ## Validation (three layers, gated)
87
-
88
- 1. **Structural** — ripgrep for leftover domain tokens + OpenAPI contract parity (Rails ↔ iOS ↔ Android).
89
- 2. **Runtime** — Rails boots, iOS builds & launches, Android builds & launches (Stage 1); UI-driven CRUD scenario via mobile-mcp (Stage 2).
90
- 3. **Semantic** — Opus 4.7 as judge (text + vision) scoring against a structured rubric. Three runs per screenshot, take the median.
91
-
92
- ## Guardrails
93
-
94
- - **Self-repair loop hard-capped at 5 iterations** per generated project, opt-in via `NATIVEAPPTEMPLATE_REPAIR` (targets Layer 1/2 only; Layer 3 + reviewer are surfaced, not auto-repaired). On exceed, surface residuals and exit. Code: `src/repair-loop.ts` + `src/agents/repair.ts`.
95
- - Known-cryptic failure modes: Jetpack Compose compilation, Hilt DI. Slow down and verify rather than pattern-match on those.
96
- - **Do not invent tests for the generated code.** The substrate already has tests; use them.
97
- - **Never modify the substrate repos** — clone them fresh into `./out/<slug>/{rails,ios,android}` before editing.
98
- - Token budget: keep per-sub-agent context under 500k tokens to leave headroom for self-repair loops.
99
-
100
- ## Output layout
101
-
102
- ```
103
- ./out/<spec-slug>/
104
- ├── rails/ # customized Rails API (git-initialized)
105
- ├── ios/ # customized iOS project (git-initialized)
106
- └── android/ # customized Android project (git-initialized)
107
- ```
108
-
109
- Each is an independent, buildable git repo.
110
-
111
- ## Packaging
112
-
113
- Ships as:
114
- - `npx nativeapptemplate-agent "your spec"` — standalone CLI
115
- - A Claude Code plugin (slash commands + skills)
116
-
117
- ## Hackathon success criteria (quick reminder — details in docs/SPEC.md section 11)
118
-
119
- **Must-have for the demo video:**
120
- - Clinic queue spec produces 3 projects passing Layer 1 + Layer 2 Stage 1
121
- - Layer 3 rubric scores above threshold for captured home-screen shots
122
- - 90s demo video of end-to-end run with validation report
123
-
124
- **Stretch:** restaurant waitlist + task tracker also pass; self-repair loop fixes at least one real failure in the video.
125
-
126
- ## Sibling docs
127
-
128
- - `docs/SPEC.md` — full technical specification (read first for any non-trivial change)
129
- - `ROADMAP.md` — post-hackathon direction, OSS vs hosted tracks, durable scope boundaries
130
- - `docs-private/GTM.md` — business strategy (relevant when discussing product surface, pricing, or Phase A/B transitions). Gitignored, not for publication.
131
- - `docs-private/PREP-CHECKLIST.md` — pre-hackathon setup (should already be all-green by the time development starts). Gitignored.
132
-
133
- ## Anti-patterns to avoid
134
-
135
- - **Don't reimplement mobile UI automation.** Use mobile-mcp. The hackathon is not about inventing device automation.
136
- - **Don't extend scope beyond the queue / simple-CRUD-SaaS family** during the hackathon week. See `docs/SPEC.md` section 9 for the full non-goals list.
137
- - **Test the paid edition first.** Free is a strict subset of paid, so running paid first catches regressions that wouldn't surface against free alone. The OSS reproducibility story (judges/contributors with no paid license) lives in the free edition — once paid is green, point env vars at the free repos and re-run for the public demo.
138
- - **Don't skip the validation layers to save time.** They are the demo story. A run that green-builds without passing Layer 3 is a failed run.
139
- - **Don't edit, commit, push, or run `git clean` / `rm` inside `$NATIVEAPPTEMPLATE_API`, `$NATIVEAPPTEMPLATE_IOS`, or `$NATIVEAPPTEMPLATE_ANDROID`.** Those are the developer's working copies of the substrate (paid by default; possibly free), shared with other projects on the same machine. Copy them into `./out/<slug>/` first; change nothing in place. If unsure whether a command is safe, ask.
1
+ @AGENTS.md
package/README.md CHANGED
@@ -14,10 +14,45 @@ Coherent across all three, in under an hour.
14
14
  [![node: >=22](https://img.shields.io/node/v/nativeapptemplate-agent.svg)](https://nodejs.org/)
15
15
 
16
16
  ```bash
17
- npx nativeapptemplate-agent "a walk-in clinic queue for small veterinary practices"
17
+ npx nativeapptemplate-agent "a walk-in clinic queue for small veterinary practices" --project-name="VetClinic"
18
18
  ```
19
19
 
20
- > **Status: v0.1.2 stable.** First built during [Built with Opus 4.7: a Claude Code Hackathon](https://cerebralvalley.ai/e/built-with-4-7-hackathon) (April 21–27, 2026); shipped to npm post-hackathon. Verified end-to-end on the three demo specs below from a fresh `/tmp/` cwd. Stage 2 (scripted-CRUD walk-through via `mobile-mcp` against a live Rails server) and paid-edition parity validation landed post-launch. Active development continues — see the [roadmap](./ROADMAP.md) for what's next.
20
+ > **Status: 0.2.2 stable.** First built during [Built with Opus 4.7: a Claude Code Hackathon](https://cerebralvalley.ai/e/built-with-4-7-hackathon) (April 21–27, 2026); shipped to npm post-hackathon. Verified end-to-end on the three demo specs below from a fresh `/tmp/` cwd — the full 3-spec × 2-edition × 2-platform matrix is green at `NATIVEAPPTEMPLATE_VISUAL=2` (see the [matrix](#demo)). Stage 2 (scripted-CRUD walk-through via `mobile-mcp` against a live Rails server) and paid-edition parity validation landed post-launch. Active development continues — see the [roadmap](./ROADMAP.md) for what's next.
21
+
22
+ ---
23
+
24
+ ## Quick Start
25
+
26
+ ### Option 1: Claude Code plugin (recommended)
27
+
28
+ Install the plugin once, then drive the agent with two slash commands — `/nativeapptemplate-agent:generate-app` (generate → validate → explain) and `/nativeapptemplate-agent:walk-app` (launch the app on a simulator/emulator and walk its UI via `mobile-mcp`, screenshots inline).
29
+
30
+ ```bash
31
+ /plugin marketplace add nativeapptemplate/nativeapptemplate-agent
32
+ /plugin install nativeapptemplate-agent@nativeapptemplate
33
+ ```
34
+
35
+ Then in Claude Code:
36
+
37
+ ```
38
+ /nativeapptemplate-agent:generate-app a walk-in clinic queue for small veterinary practices. project name is VetClinic.
39
+ ```
40
+
41
+ Or load it locally without the marketplace: `claude --plugin-dir ./plugin`. See [`plugin/README.md`](./plugin/README.md) for the reference card, and the [A-to-Z plugin guide](./docs/PLUGIN-GUIDE.md) for a full walkthrough.
42
+
43
+ ### Option 2: Standalone CLI
44
+
45
+ No editor required — runs anywhere Node 22+ is installed.
46
+
47
+ ```bash
48
+ npx nativeapptemplate-agent "a walk-in clinic queue for small veterinary practices" --project-name="VetClinic"
49
+ ```
50
+
51
+ Generated output appears under `./out/<slug>/`. See [Usage](#usage) for rename overrides and report flags.
52
+
53
+ > **Pass a project name.** `--project-name` (or "project name is …" in the plugin) pins the Pascal project name, output slug (`out/vet-clinic/`), Xcode scheme, and display name across all three platforms. Omit it and the planner picks one — fine for one-off exploration, but it'll differ across re-runs.
54
+
55
+ > Using Cursor, Cline, Goose, or another MCP-capable editor? The generator also ships as an MCP server: `npx -y -p nativeapptemplate-agent nativeapptemplate-agent-mcp` exposes a `generate_app` tool you can wire into your editor's MCP config.
21
56
 
22
57
  ---
23
58
 
@@ -29,6 +64,8 @@ Classic mobile boilerplates sell "save 12–16 weeks of setup." AI coding tools
29
64
 
30
65
  This agent is an answer to that: turn a boilerplate into a generator that produces coherent three-platform implementations on demand, with structural and semantic validation built in.
31
66
 
67
+ > 📝 **Background:** [*One sentence becomes a native iOS app, a native Android app, and a Rails API*](https://medium.com/@dadachix/one-sentence-becomes-a-native-ios-app-a-native-android-app-and-a-rails-api-0000aaf9010c) — the long-form version of this argument: why cross-platform coherence, not code volume, is the problem AI didn't make easier.
68
+
32
69
  ## What it does
33
70
 
34
71
  Point the agent at a natural-language spec:
@@ -43,14 +80,18 @@ It will:
43
80
  2. **Copy the free-edition substrate** (three MIT-licensed repos covering Rails + iOS + Android) into `./out/<spec-slug>/{rails,ios,android}/`.
44
81
  3. **Rename the skeleton** — `Shop → Clinic`, `Shopkeeper → Vet`, etc. — consistently across Ruby migrations, Swift models, Kotlin data classes, policies, tests, and localized copy.
45
82
  4. **Adapt or replace the domain module** — keep `ItemTag` for walk-in queue variants; strip and insert a new resource for non-queue SaaS.
46
- 5. **Drive the build green** — `bin/rails test`, `xcodebuild test`, `./gradlew test` must all pass before the agent exits.
83
+ 5. **Drive the build green** — every platform must pass before the agent exits. By default this is a fast gate: Rails routes load (`bin/rails routes`) and the iOS/Android projects resolve (`xcodebuild -list`, `./gradlew --version`). With `NATIVEAPPTEMPLATE_VISUAL=1`+ it escalates to full builds — Rails boots (`bin/rails db:prepare` + `bin/rails runner`), iOS builds (`xcodebuild build`), Android builds (`./gradlew assembleDebug`).
47
84
  6. **Validate the output** across three layers (structural, runtime, semantic) and write a self-contained HTML + JSON [validation report](#validation-report). Details in [`docs/SPEC.md`](./docs/SPEC.md) section 6.
48
85
 
49
86
  ## Demo
50
87
 
88
+ **Quick look (40s)** — what the agent does, the three distribution surfaces, and a live walk of the generated app: **▸ [Watch on YouTube](https://youtu.be/fsjfskPWecQ)**
89
+
90
+ **Full end-to-end run (90s)** — spec → renamed Rails API + iOS app + Android app, all three platforms validated:
91
+
51
92
  https://github.com/user-attachments/assets/bd1ed091-93d8-45d7-b502-c21720218484
52
93
 
53
- 90-second end-to-end run: spec → renamed Rails API + iOS app + Android app, all three platforms validated. Also on [YouTube](https://youtu.be/z08ueZX-02I) for full-screen viewing.
94
+ Also on [YouTube](https://youtu.be/z08ueZX-02I) for full-screen viewing.
54
95
 
55
96
  Three demo specs, both adapt and replace paths, all four validation layers green end-to-end:
56
97
 
@@ -60,12 +101,38 @@ Three demo specs, both adapt and replace paths, all four validation layers green
60
101
  | `"a restaurant waitlist for casual dining"` | `Shop → Restaurant`, `Shopkeeper → Host` | adapt | Layer 1 3/3 · Layer 2 3/3 · Layer 3 2/2 · Reviewer PASS |
61
102
  | `"a personal task tracker with due dates"` | `ItemTag → Todo` (replaces queue entry entirely) | replace | Layer 1 3/3 · Layer 2 3/3 · Layer 3 2/2 · Reviewer PASS |
62
103
 
63
- Layer 2 ran in build mode — real `xcodebuild build` and `./gradlew assembleDebug`, full app builds installed on iPhone 17 simulator and Android emulator. Layer 3 captured the home-screen via `xcrun simctl io booted screenshot` / `adb exec-out screencap` and judged against the rubric using Opus 4.7 vision (median of 3 samples per criterion). With `NATIVEAPPTEMPLATE_VISUAL=2`, Layer 2 additionally drives a scripted-CRUD walk-through via `mobile-mcp` against a live Rails server — Welcome → Sign Up → email-confirm via `bin/rails runner` → Sign In → drill into the auto-seeded sample — and Layer 3 then judges the post-walk screenshot against a domain-content rubric.
104
+ Layer 2 ran in build mode — real `xcodebuild build` and `./gradlew assembleDebug`, full app builds installed on iPhone 17 Pro simulator and Android emulator. Layer 3 captured the home-screen via `xcrun simctl io booted screenshot` / `adb exec-out screencap` and judged against the rubric using Opus 4.7 vision (median of 3 samples per criterion). With `NATIVEAPPTEMPLATE_VISUAL=2`, Layer 2 additionally drives a scripted-CRUD walk-through via `mobile-mcp` against a live Rails server — Welcome → Sign Up → email-confirm via `bin/rails runner` → Sign In → drill into the auto-seeded sample — and Layer 3 then judges the post-walk screenshot against a domain-content rubric.
64
105
 
65
106
  The agent works on either the **free (MIT) edition** or the **paid edition** without code changes — the same pipeline handles both substrates; multi-tenant features (org switching, invitations, role permissions) survive the rename pipeline when targeting paid. The agent tests paid first because free is a strict subset of paid; running paid first catches regressions that wouldn't surface against free alone.
66
107
 
108
+ ### Validation matrix — 12/12 green at `VISUAL=2`
109
+
110
+ All three specs pass a full end-to-end `NATIVEAPPTEMPLATE_VISUAL=2` run (build → rename → boot → scripted-CRUD walk → vision judge) on **both editions and both platforms** — 12 cells (3 specs × 2 editions × 2 platforms), each **Layer 1 3/3 · Layer 2 3/3 · Layer 3 2/2 · reviewer PASS** on a real iPhone 17 Pro simulator + Android emulator:
111
+
112
+ | Spec | Free (iOS · Android) | Paid (iOS · Android) |
113
+ |---|---|---|
114
+ | Walk-in queue | ✅ · ✅ | ✅ · ✅ |
115
+ | Restaurant / sushi waitlist | ✅ · ✅ | ✅ · ✅ |
116
+ | Personal task tracker | ✅ · ✅ | ✅ · ✅ |
117
+
118
+ After the queue cell's Stage-2 hardening landed, sushi and the task tracker passed first-try on both editions with no further changes — the scripted scenario is genuinely spec- and edition-agnostic (the task tracker is a non-queue spec running the same walk).
119
+
67
120
  Both screenshots are real captures from the booted iOS Simulator and Android emulator post-`./gradlew assembleDebug` / `xcodebuild build`, after the agent installed and launched the generated app.
68
121
 
122
+ ## Runtime
123
+
124
+ End-to-end wall-clock per run, measured from `report.json`'s `meta.durationMs` on a 2021 M1 Max with both simulators pre-booted:
125
+
126
+ | `NATIVEAPPTEMPLATE_VISUAL` | What runs | Observed time | Approx cost |
127
+ |---|---|---|---|
128
+ | `0` (default) | Layer 1 (ripgrep) + Layer 2 *fast* (Rails boot probe, iOS/Android type-check) + reviewer | ~2–3 min | ~$0.05 |
129
+ | `1` | + full `xcodebuild build` + `./gradlew assembleDebug` + home-screen capture + Stage 1 vision judge | ~2.5 min (barbershop-queue · 2026-05-24) | — |
130
+ | `2` | + Rails server boot + scripted-CRUD walk via `mobile-mcp` + Stage 2 vision judge | ~7–8 min (sentova / sushi / task-tracker · 2026-05-23) | — |
131
+
132
+ Cold builds, first-run cocoapods/gradle dependency resolution, or unbooted simulators add a one-time minute or two. The self-repair loop (opt-in, hard-capped at 5 iterations) can extend a failing run substantially — budget for it if you set `NATIVEAPPTEMPLATE_REPAIR=on`.
133
+
134
+ **Cost** scales with model usage, not wall-clock. The agent makes real `claude-opus-4-7` API calls (planner, workers, reviewer, judge) and a single VISUAL=2 run consumes tens of thousands of tokens across multiple sub-agents. Set a workspace spend cap (see [Security](#security)) as a backstop — the agent exits non-zero on validation failure, but doesn't gate on spend itself.
135
+
69
136
  ## Architecture
70
137
 
71
138
  ```mermaid
@@ -105,6 +172,12 @@ npx nativeapptemplate-agent "a walk-in clinic queue for small veterinary practic
105
172
  npx nativeapptemplate-agent "a restaurant waitlist for casual dining"
106
173
  npx nativeapptemplate-agent "a personal task tracker with due dates"
107
174
 
175
+ # Name the project explicitly (sets display name + output slug, independent of the
176
+ # domain rename). The plugin/MCP path accepts the same intent in plain language —
177
+ # "…detection. project name is Sentova." — mapped to the projectName argument.
178
+ npx nativeapptemplate-agent "a two-device home monitor for household pest detection" --project-name="Sentova"
179
+ # → out/sentova/ · Sentova.xcodeproj · "Sentova API"; adapts ItemTag (e.g. Shop→Household, ItemTag→Sighting)
180
+
108
181
  # Generated output appears under ./out/<slug>/
109
182
  tree ./out/clinic-queue/
110
183
  # ├── rails/ ← Rails 8.1 API, git-initialized, buildable
@@ -114,7 +187,14 @@ tree ./out/clinic-queue/
114
187
  # └── validation-report.html ← self-contained visual report (open in a browser)
115
188
  ```
116
189
 
117
- The agent will also be available as a Claude Code plugin.
190
+ The same generator also ships as an MCP server — `npx -y -p nativeapptemplate-agent nativeapptemplate-agent-mcp` exposes a `generate_app` tool, so any MCP-capable assistant (Claude Code, Cursor, Cline, Goose) can invoke it without leaving the editor. And a **Claude Code plugin** ships in [`plugin/`](./plugin/) with two skills: `/nativeapptemplate-agent:generate-app` (generate → validate → explain) and `/nativeapptemplate-agent:walk-app` (launch the app on a simulator/emulator and walk its UI via `mobile-mcp`, screenshots inline). Install it via the bundled marketplace:
191
+
192
+ ```bash
193
+ /plugin marketplace add nativeapptemplate/nativeapptemplate-agent
194
+ /plugin install nativeapptemplate-agent@nativeapptemplate
195
+ ```
196
+
197
+ …or load it locally with `claude --plugin-dir ./plugin`. See [`plugin/README.md`](./plugin/README.md) for the reference card, or the **[A-to-Z plugin guide](./docs/PLUGIN-GUIDE.md)** for a full follow-along walkthrough.
118
198
 
119
199
  ## Requirements
120
200
 
@@ -206,17 +286,17 @@ Report flags:
206
286
 
207
287
  **Don't** paste a real key into shell history (`HISTFILE` captures it), commit a `.env`, or echo the key into a non-private channel.
208
288
 
209
- The agent strips `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and `NATIVEAPPTEMPLATE_AGENT_ANTHROPIC_KEY` from the environment of every subprocess it spawns — Ruby scripts, `git`, `psql`, `xcodebuild`, `gradlew`, the future mobile-mcp client. Keys are only seen by the Anthropic SDK in the Node process. Set spend limits on your API workspace as a backstop, and rotate the key if you suspect leak.
289
+ The agent strips `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and `NATIVEAPPTEMPLATE_AGENT_ANTHROPIC_KEY` from the environment of every subprocess it spawns — Ruby scripts, `git`, `psql`, `xcodebuild`, `gradlew`, the mobile-mcp client. Keys are only seen by the Anthropic SDK in the Node process. Set spend limits on your API workspace as a backstop, and rotate the key if you suspect leak.
210
290
 
211
291
  ## Project docs
212
292
 
213
293
  - [`docs/SPEC.md`](./docs/SPEC.md) — full technical specification
214
294
  - [`ROADMAP.md`](./ROADMAP.md) — where this project is headed, OSS vs hosted, what stays out of scope
215
- - [`CLAUDE.md`](./CLAUDE.md) — Claude Code project instructions (read if you're running Claude Code against this repo)
295
+ - [`AGENTS.md`](./AGENTS.md) — AI coding agent instructions (read if you're running Claude Code, Codex, etc. against this repo; `CLAUDE.md` imports it)
216
296
 
217
297
  ## Contributing
218
298
 
219
- Issues and PRs welcome. The repository is stable now (v0.1.x) — no more hackathon-pace rewrites. A `CONTRIBUTING.md` with detailed guidelines will land alongside v0.2.
299
+ Issues and PRs welcome. The repository is stable now (v0.2.x) — no more hackathon-pace rewrites. A `CONTRIBUTING.md` with detailed guidelines is still to come.
220
300
 
221
301
  For now, the simplest path is: open an issue describing what you're trying to do, and we'll figure out the right shape together before code lands. Bug reports with reproducible commands (and the `/tmp/<dir>/tmp/trace/` log) are especially welcome.
222
302
 
@@ -1 +1 @@
1
- {"version":3,"file":"judge.d.ts","sourceRoot":"","sources":["../../src/agents/judge.ts"],"names":[],"mappings":"AAIA,OAAO,EAAa,KAAK,UAAU,EAAqB,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EAGX,cAAc,EAGd,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC;IACpB,GAAG,EAAE,YAAY,CAAC;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B,CAAC;AAWF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE;QACP,mBAAmB,EAAE,MAAM,CAAC;QAC5B,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;KACrC,CAAC;CACH,CAAC;AAQF,wBAAsB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAkEtE"}
1
+ {"version":3,"file":"judge.d.ts","sourceRoot":"","sources":["../../src/agents/judge.ts"],"names":[],"mappings":"AAMA,OAAO,EAAa,KAAK,UAAU,EAAqB,MAAM,yBAAyB,CAAC;AAMxF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EAGX,cAAc,EAGd,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC;IACpB,GAAG,EAAE,YAAY,CAAC;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B,CAAC;AAWF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE;QACP,mBAAmB,EAAE,MAAM,CAAC;QAC5B,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;KACrC,CAAC;CACH,CAAC;AAQF,wBAAsB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAkEtE"}
@@ -1,10 +1,14 @@
1
1
  import { resolve } from "node:path";
2
+ import { setTimeout as sleep } from "node:timers/promises";
2
3
  import { trace } from "../trace.js";
3
4
  import { isStub } from "../stub.js";
5
+ import { createMobileClient } from "../mobile.js";
4
6
  import { runLayer1 } from "../validation/layer1.js";
5
7
  import { runLayer2 } from "../validation/layer2.js";
6
8
  import { runStage1Visual } from "../validation/stage1.js";
7
- import { runStage2Visual } from "../validation/stage2-judge.js";
9
+ import { runStage2Visual, selectDevice } from "../validation/stage2-judge.js";
10
+ import { recoverFromErrorScreen } from "../validation/stage2.js";
11
+ import { discoverIosArtifact, discoverAndroidArtifact } from "../validation/discover.js";
8
12
  import { buildQueueScenario } from "../validation/scenarios/queue.js";
9
13
  export async function runJudge(input) {
10
14
  if (isStub("judge"))
@@ -76,23 +80,64 @@ async function runVisualPhase(config, domain) {
76
80
  platforms.push("android");
77
81
  const rubric = config.rubric;
78
82
  trace("judge", `Layer 3 (semantic) — judging ${platforms.join(" + ")} home screen against rubric`);
79
- const stage1 = await runStage1Visual({
80
- ...(config.iosDir !== undefined ? { iosDir: config.iosDir } : {}),
81
- ...(config.androidDir !== undefined ? { androidDir: config.androidDir } : {}),
82
- spec: config.spec ?? domain.displayName,
83
- ...(rubric !== undefined ? { rubric } : {}),
84
- ...(config.screenshotDir !== undefined ? { screenshotDir: config.screenshotDir } : {}),
85
- });
86
- const report = {};
87
- if (stage1.ios) {
88
- report.ios = toPlatformReport(stage1.ios);
89
- trace("judge", `Layer 3 ios: ${stage1.ios.ok ? "PASS" : "FAIL"}` + (stage1.ios.error ? ` — ${stage1.ios.error}` : ""));
83
+ // iOS recovery for Stage 1: the paid app intermittently lands on a "Something
84
+ // went wrong" error screen at launch. On a renders-cleanly retry, tap its
85
+ // "Back to Start Screen" button (logs out + returns to welcome) so the
86
+ // re-capture sees a clean screen. Only when WDA is available (VISUAL=2, i.e.
87
+ // stage2 configured) and not in stub mode; the mobile client is created
88
+ // lazily on first retry and closed in the finally below.
89
+ let recoverClient;
90
+ let recoverUnavailable = false;
91
+ const canRecover = config.stage2 !== undefined && config.iosDir !== undefined && !isStub("judge");
92
+ const iosRecover = canRecover
93
+ ? async () => {
94
+ if (recoverUnavailable)
95
+ return;
96
+ if (!recoverClient) {
97
+ try {
98
+ recoverClient = await createMobileClient();
99
+ const err = await selectDevice(recoverClient, "ios");
100
+ if (err) {
101
+ recoverUnavailable = true;
102
+ return;
103
+ }
104
+ }
105
+ catch {
106
+ recoverUnavailable = true;
107
+ return;
108
+ }
109
+ }
110
+ const tapped = await recoverFromErrorScreen(recoverClient);
111
+ if (tapped) {
112
+ trace("judge", "Layer 3 ios: tapped 'Back to Start Screen' to clear error screen, re-capturing");
113
+ await sleep(2_000); // let the welcome screen render before re-capture
114
+ }
115
+ }
116
+ : undefined;
117
+ try {
118
+ const stage1 = await runStage1Visual({
119
+ ...(config.iosDir !== undefined ? { iosDir: config.iosDir } : {}),
120
+ ...(config.androidDir !== undefined ? { androidDir: config.androidDir } : {}),
121
+ spec: config.spec ?? domain.displayName,
122
+ ...(rubric !== undefined ? { rubric } : {}),
123
+ ...(config.screenshotDir !== undefined ? { screenshotDir: config.screenshotDir } : {}),
124
+ ...(iosRecover ? { iosRecover } : {}),
125
+ });
126
+ const report = {};
127
+ if (stage1.ios) {
128
+ report.ios = toPlatformReport(stage1.ios);
129
+ trace("judge", `Layer 3 ios: ${stage1.ios.ok ? "PASS" : "FAIL"}` + (stage1.ios.error ? ` — ${stage1.ios.error}` : ""));
130
+ }
131
+ if (stage1.android) {
132
+ report.android = toPlatformReport(stage1.android);
133
+ trace("judge", `Layer 3 android: ${stage1.android.ok ? "PASS" : "FAIL"}` + (stage1.android.error ? ` — ${stage1.android.error}` : ""));
134
+ }
135
+ return report;
90
136
  }
91
- if (stage1.android) {
92
- report.android = toPlatformReport(stage1.android);
93
- trace("judge", `Layer 3 android: ${stage1.android.ok ? "PASS" : "FAIL"}` + (stage1.android.error ? ` — ${stage1.android.error}` : ""));
137
+ finally {
138
+ if (recoverClient)
139
+ await recoverClient.close().catch(() => { });
94
140
  }
95
- return report;
96
141
  }
97
142
  async function runStage2Phase(base, config, domain) {
98
143
  if (!config.stage2)
@@ -121,6 +166,11 @@ async function runStage2Phase(base, config, domain) {
121
166
  }
122
167
  const platforms = [wantIos && "ios", wantAndroid && "android"].filter(Boolean).join(" + ");
123
168
  trace("judge", `Stage 2 — scripted-CRUD walk via mobile-mcp on ${platforms}`);
169
+ // Resolve the app ids so Stage 2 can foreground the app before walking (it
170
+ // can drop to springboard between stages). Best-effort: a discovery miss
171
+ // just skips the foreground step.
172
+ const iosArtifact = wantIos && config.iosDir !== undefined ? await discoverIosArtifact(config.iosDir) : null;
173
+ const androidArtifact = wantAndroid && config.androidDir !== undefined ? await discoverAndroidArtifact(config.androidDir) : null;
124
174
  const merged = { ...base };
125
175
  // Guard the whole Stage 2 walk: mobile-mcp runs in a child process, and if
126
176
  // it exits mid-walk the SDK rejects with `McpError: Connection closed`. That
@@ -131,6 +181,8 @@ async function runStage2Phase(base, config, domain) {
131
181
  spec: config.spec ?? domain.displayName,
132
182
  ...(wantIos ? { iosScenario } : {}),
133
183
  ...(wantAndroid ? { androidScenario } : {}),
184
+ ...(iosArtifact ? { iosAppId: iosArtifact.bundleId } : {}),
185
+ ...(androidArtifact ? { androidAppId: androidArtifact.packageName } : {}),
134
186
  ...(config.stage2.rubric !== undefined ? { rubric: config.stage2.rubric } : {}),
135
187
  ...(config.screenshotDir !== undefined ? { screenshotDir: config.screenshotDir } : {}),
136
188
  });