@shaykec/app-agent 1.0.9 → 1.0.11

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 (202) hide show
  1. package/.claude/agents/catalog-analyzer.md +57 -0
  2. package/.claude/skills/android-customizer/SKILL.md +23 -10
  3. package/.claude/skills/bug-fixer/SKILL.md +59 -0
  4. package/.claude/skills/catalog-analyzer/SKILL.md +96 -0
  5. package/.claude/skills/customization-planner/SKILL.md +44 -5
  6. package/.claude/skills/design-selector/SKILL.md +3 -1
  7. package/.claude/skills/design-system/SKILL.md +1 -1
  8. package/.claude/skills/exploratory-tester/SKILL.md +82 -0
  9. package/.claude/skills/ios-customizer/SKILL.md +29 -8
  10. package/.claude/skills/module-integrator/SKILL.md +1 -1
  11. package/.claude/skills/react-native-customizer/SKILL.md +22 -10
  12. package/.claude/skills/test-planner/SKILL.md +72 -0
  13. package/.cursor/agents/README.md +3 -1
  14. package/.cursor/agents/catalog-analyzer.md +83 -0
  15. package/.cursor/rules/safety-guardrails.mdc +1 -1
  16. package/.cursor/rules/workflow.mdc +52 -18
  17. package/.cursor/skills/android-customizer/SKILL.md +43 -19
  18. package/.cursor/skills/bug-fixer/SKILL.md +189 -0
  19. package/.cursor/skills/catalog-analyzer/SKILL.md +222 -0
  20. package/.cursor/skills/customization-planner/SKILL.md +55 -8
  21. package/.cursor/skills/design-selector/SKILL.md +6 -5
  22. package/.cursor/skills/design-system/SKILL.md +8 -7
  23. package/.cursor/skills/exploratory-tester/SKILL.md +223 -0
  24. package/.cursor/skills/ios-customizer/SKILL.md +47 -12
  25. package/.cursor/skills/module-integrator/SKILL.md +2 -2
  26. package/.cursor/skills/output-validator/SKILL.md +1 -1
  27. package/.cursor/skills/react-native-customizer/SKILL.md +46 -16
  28. package/.cursor/skills/test-planner/SKILL.md +199 -0
  29. package/.cursor/skills/web-analyzer/SKILL.md +310 -0
  30. package/.cursor/skills/web-crawler/SKILL.md +252 -0
  31. package/AGENTS.md +32 -11
  32. package/CLAUDE.md +78 -33
  33. package/README.md +77 -11
  34. package/designs/DESIGN_CATALOG.md +17 -15
  35. package/designs/DESIGN_PRINCIPLES.md +53 -0
  36. package/designs/brands/accessible-high-contrast.md +14 -0
  37. package/designs/brands/corporate-professional.md +14 -0
  38. package/designs/brands/dark-luxe.md +14 -0
  39. package/designs/brands/kids-playful.md +14 -0
  40. package/designs/brands/medical-clinical.md +14 -0
  41. package/designs/brands/modern-minimal.md +14 -0
  42. package/designs/brands/nature-organic.md +14 -0
  43. package/designs/brands/neo-brutalist.md +14 -0
  44. package/designs/brands/retro-vintage.md +14 -0
  45. package/designs/brands/soft-gradient.md +14 -0
  46. package/designs/brands/sport-athletic.md +14 -0
  47. package/designs/brands/tech-dynamic.md +14 -0
  48. package/designs/brands/vibrant-playful.md +14 -0
  49. package/dist/cli.d.ts +4 -2
  50. package/dist/cli.d.ts.map +1 -1
  51. package/dist/cli.js +123 -11
  52. package/dist/cli.js.map +1 -1
  53. package/dist/config.d.ts +8 -1
  54. package/dist/config.d.ts.map +1 -1
  55. package/dist/config.js +6 -1
  56. package/dist/config.js.map +1 -1
  57. package/dist/engines/claude-engine.d.ts.map +1 -1
  58. package/dist/engines/claude-engine.js +16 -4
  59. package/dist/engines/claude-engine.js.map +1 -1
  60. package/dist/engines/types.d.ts +1 -1
  61. package/dist/engines/types.d.ts.map +1 -1
  62. package/dist/engines/types.js +31 -2
  63. package/dist/engines/types.js.map +1 -1
  64. package/dist/github.d.ts +3 -0
  65. package/dist/github.d.ts.map +1 -1
  66. package/dist/github.js +47 -4
  67. package/dist/github.js.map +1 -1
  68. package/dist/index.js +294 -16
  69. package/dist/index.js.map +1 -1
  70. package/dist/prompt-builder.d.ts +17 -1
  71. package/dist/prompt-builder.d.ts.map +1 -1
  72. package/dist/prompt-builder.js +272 -1
  73. package/dist/prompt-builder.js.map +1 -1
  74. package/dist/validator.d.ts +7 -2
  75. package/dist/validator.d.ts.map +1 -1
  76. package/dist/validator.js +61 -41
  77. package/dist/validator.js.map +1 -1
  78. package/dist/workspace.js +2 -2
  79. package/dist/workspace.js.map +1 -1
  80. package/package.json +2 -4
  81. package/prompts/agent-prompt.md +35 -18
  82. package/prompts/deep-test-agent-prompt.md +122 -0
  83. package/prompts/fix-agent-prompt.md +90 -0
  84. package/prompts/quick-agent-prompt.md +32 -2
  85. package/prompts/scratch-agent-prompt.md +5 -8
  86. package/prompts/web-clone-agent-prompt.md +179 -0
  87. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  88. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  89. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/conversations/ConversationsScreen.kt +1 -1
  90. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  91. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/navigation/MainScreen.kt +1 -0
  92. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  93. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/navigation/MainNavigation.kt +5 -1
  94. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  95. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  96. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/core/animation/MotionPreferencesScreen.kt +3 -3
  97. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/navigation/Navigation.kt +1 -1
  98. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/settings/SettingsScreen.kt +1 -1
  99. package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  100. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  101. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  102. package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  103. package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/settings/SettingsScreen.kt +3 -2
  104. package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  105. package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/features/settings/SettingsScreen.kt +1 -1
  106. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  107. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/features/cart/CartScreen.kt +3 -2
  108. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  109. package/templates/android/Skeleton/tests/03_detail_screen.yaml +1 -1
  110. package/templates/android/Skeleton/tests/04_favorites.yaml +1 -1
  111. package/templates/android/Skeleton/tests/08_full_e2e.yaml +7 -1
  112. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  113. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  114. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/settings/SettingsScreen.kt +3 -2
  115. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/core/animation/AnimatedTransitionsModifiers.kt +188 -0
  116. package/templates/ios/BookTemplate/BookTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  117. package/templates/ios/ChatTemplate/ChatTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  118. package/templates/ios/DashTemplate/DashTemplate/App/AppConfig.swift +1 -0
  119. package/templates/ios/DashTemplate/DashTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  120. package/templates/ios/DashTemplate/DashTemplate/Core/Strings.swift +13 -0
  121. package/templates/ios/DashTemplate/DashTemplate.xcodeproj/project.pbxproj +32 -20
  122. package/templates/ios/FamilyTemplate/FamilyTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  123. package/templates/ios/FinanceTemplate/FinanceTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  124. package/templates/ios/FinanceTemplate/FinanceTemplate/Core/Strings.swift +42 -0
  125. package/templates/ios/FinanceTemplate/FinanceTemplate.xcodeproj/project.pbxproj +36 -30
  126. package/templates/ios/GameTemplate/GameTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  127. package/templates/ios/HealthTemplate/HealthTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  128. package/templates/ios/LearnTemplate/LearnTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  129. package/templates/ios/MapTemplate/MapTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  130. package/templates/ios/MediaTemplate/MediaTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  131. package/templates/ios/ReferenceTemplate/ReferenceTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  132. package/templates/ios/ReferenceTemplate/ReferenceTemplate/Core/Strings.swift +12 -0
  133. package/templates/ios/ReferenceTemplate/ReferenceTemplate/Features/SkeletonLoading/SkeletonLoadingView.swift +2 -37
  134. package/templates/ios/ShopTemplate/ShopTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  135. package/templates/ios/Skeleton/Skeleton/Core/Animation/AnimatedTransitionsView.swift +201 -0
  136. package/templates/ios/Skeleton/tests/08_full_e2e.yaml +4 -0
  137. package/templates/ios/SocialTemplate/SocialTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  138. package/templates/ios/TaskTemplate/TaskTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  139. package/templates/ios/TrackTemplate/TrackTemplate/Core/Animation/AnimatedTransitionsView.swift +201 -0
  140. package/templates/react-native/BookTemplate/src/animation/useAnimatedList.ts +219 -2
  141. package/templates/react-native/BookTemplate/src/animation/useMotionPreferences.ts +23 -9
  142. package/templates/react-native/BookTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  143. package/templates/react-native/ChatTemplate/src/animation/useAnimatedList.ts +219 -2
  144. package/templates/react-native/ChatTemplate/src/animation/useMotionPreferences.ts +23 -9
  145. package/templates/react-native/ChatTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  146. package/templates/react-native/DashTemplate/src/animation/useAnimatedList.ts +219 -2
  147. package/templates/react-native/DashTemplate/src/animation/useMotionPreferences.ts +23 -9
  148. package/templates/react-native/DashTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  149. package/templates/react-native/FamilyTemplate/src/animation/useAnimatedList.ts +219 -2
  150. package/templates/react-native/FamilyTemplate/src/animation/useMotionPreferences.ts +23 -9
  151. package/templates/react-native/FamilyTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  152. package/templates/react-native/FinanceTemplate/src/animation/useAnimatedList.ts +219 -2
  153. package/templates/react-native/FinanceTemplate/src/animation/useMotionPreferences.ts +23 -9
  154. package/templates/react-native/FinanceTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  155. package/templates/react-native/GameTemplate/src/animation/useAnimatedList.ts +219 -2
  156. package/templates/react-native/GameTemplate/src/animation/useMotionPreferences.ts +23 -9
  157. package/templates/react-native/GameTemplate/src/screens/GameDetail/GameDetailScreen.tsx +2 -1
  158. package/templates/react-native/GameTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  159. package/templates/react-native/HealthTemplate/src/animation/useAnimatedList.ts +219 -2
  160. package/templates/react-native/HealthTemplate/src/animation/useMotionPreferences.ts +23 -9
  161. package/templates/react-native/HealthTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  162. package/templates/react-native/HealthTemplate/src/screens/WorkoutDetail/WorkoutDetailScreen.tsx +1 -1
  163. package/templates/react-native/LearnTemplate/src/animation/useAnimatedList.ts +219 -2
  164. package/templates/react-native/LearnTemplate/src/animation/useMotionPreferences.ts +23 -9
  165. package/templates/react-native/LearnTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  166. package/templates/react-native/MapTemplate/src/animation/useAnimatedList.ts +219 -2
  167. package/templates/react-native/MapTemplate/src/animation/useMotionPreferences.ts +23 -9
  168. package/templates/react-native/MapTemplate/src/screens/Map/MapScreen.tsx +14 -0
  169. package/templates/react-native/MapTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  170. package/templates/react-native/MediaTemplate/src/animation/useAnimatedList.ts +219 -2
  171. package/templates/react-native/MediaTemplate/src/animation/useMotionPreferences.ts +23 -9
  172. package/templates/react-native/MediaTemplate/src/screens/PlaylistDetail/PlaylistDetailScreen.tsx +1 -1
  173. package/templates/react-native/MediaTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  174. package/templates/react-native/ReferenceTemplate/src/animation/useAnimatedList.ts +219 -2
  175. package/templates/react-native/ReferenceTemplate/src/animation/useMotionPreferences.ts +23 -9
  176. package/templates/react-native/ReferenceTemplate/src/screens/Settings/SettingsScreen.tsx +1 -1
  177. package/templates/react-native/ShopTemplate/src/animation/useAnimatedList.ts +219 -2
  178. package/templates/react-native/ShopTemplate/src/animation/useMotionPreferences.ts +23 -9
  179. package/templates/react-native/ShopTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  180. package/templates/react-native/Skeleton/TESTING_MANIFEST.md +1 -1
  181. package/templates/react-native/Skeleton/src/animation/useAnimatedList.ts +219 -2
  182. package/templates/react-native/Skeleton/src/animation/useMotionPreferences.ts +23 -9
  183. package/templates/react-native/Skeleton/src/screens/Profile/ProfileScreen.tsx +1 -1
  184. package/templates/react-native/Skeleton/tests/07_profile.yaml +3 -2
  185. package/templates/react-native/Skeleton/tests/08_full_e2e.yaml +12 -1
  186. package/templates/react-native/SocialTemplate/src/animation/useAnimatedList.ts +219 -2
  187. package/templates/react-native/SocialTemplate/src/animation/useMotionPreferences.ts +23 -9
  188. package/templates/react-native/SocialTemplate/src/screens/Feed/FeedScreen.tsx +1 -0
  189. package/templates/react-native/SocialTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  190. package/templates/react-native/TaskTemplate/src/animation/useAnimatedList.ts +219 -2
  191. package/templates/react-native/TaskTemplate/src/animation/useMotionPreferences.ts +23 -9
  192. package/templates/react-native/TaskTemplate/src/screens/Profile/ProfileScreen.tsx +1 -1
  193. package/templates/react-native/TrackTemplate/src/animation/useAnimatedList.ts +219 -2
  194. package/templates/react-native/TrackTemplate/src/animation/useMotionPreferences.ts +23 -9
  195. package/templates/react-native/TrackTemplate/src/screens/Settings/SettingsScreen.tsx +1 -1
  196. package/templates/shared/ios/AnimatedTransitions/AnimatedTransitionsView.swift +233 -93
  197. package/.claude/agents/template-selector.md +0 -39
  198. package/.claude/skills/module-selector/SKILL.md +0 -81
  199. package/.claude/skills/template-selector/SKILL.md +0 -44
  200. package/.cursor/agents/template-selector.md +0 -52
  201. package/.cursor/skills/module-selector/SKILL.md +0 -135
  202. package/.cursor/skills/template-selector/SKILL.md +0 -123
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import fs from "fs-extra";
4
4
  import path from "node:path";
5
5
  import { execSync } from "node:child_process";
6
6
  import { collectUserInput, confirmInput, parseCliArgs, parseManagementCommand } from "./cli.js";
7
- import { buildAgentPrompt, buildTestPrompt, buildScratchPrompt, buildQuickPrompt, buildUpdatePrompt } from "./prompt-builder.js";
7
+ import { buildAgentPrompt, buildTestPrompt, buildDeepTestPrompt, buildFixPrompt, buildScratchPrompt, buildQuickPrompt, buildUpdatePrompt, buildWebClonePrompt } from "./prompt-builder.js";
8
8
  import { createEngine } from "./engines/index.js";
9
9
  import { validateOutput, reportValidation } from "./validator.js";
10
10
  import { validatePrompt, formatSanitizationResult } from "./prompt-sanitizer.js";
@@ -16,6 +16,7 @@ import { scaffoldWorkspace, cleanupWorkspace } from "./workspace.js";
16
16
  import { resolveUpdateContext } from "./updater.js";
17
17
  import { generatePreview } from "./preview-generator.js";
18
18
  import { createServer } from "node:http";
19
+ import { URL } from "node:url";
19
20
  import open from "open";
20
21
  async function main() {
21
22
  // ── Management commands (no build agent needed) ─────────────────────
@@ -30,6 +31,12 @@ async function main() {
30
31
  else if (mgmtCmd.command === "test") {
31
32
  await testApp(mgmtCmd.appName, mgmtCmd.engine, mgmtCmd.model);
32
33
  }
34
+ else if (mgmtCmd.command === "deep-test") {
35
+ await deepTestApp(mgmtCmd.appName, mgmtCmd.autoFix ?? false, mgmtCmd.engine, mgmtCmd.model);
36
+ }
37
+ else if (mgmtCmd.command === "fix") {
38
+ await fixApp(mgmtCmd.appName, mgmtCmd.engine, mgmtCmd.model);
39
+ }
33
40
  else if (mgmtCmd.command === "preview") {
34
41
  await handlePreview(mgmtCmd.appName);
35
42
  }
@@ -55,6 +62,10 @@ async function main() {
55
62
  await runApp(cmd.appName);
56
63
  else if (cmd.command === "test")
57
64
  await testApp(cmd.appName, cmd.engine, cmd.model);
65
+ else if (cmd.command === "deep-test")
66
+ await deepTestApp(cmd.appName, cmd.autoFix ?? false, cmd.engine, cmd.model);
67
+ else if (cmd.command === "fix")
68
+ await fixApp(cmd.appName, cmd.engine, cmd.model);
58
69
  else if (cmd.command === "preview")
59
70
  await handlePreview(cmd.appName);
60
71
  else if (cmd.command === "screenshot")
@@ -96,11 +107,22 @@ async function main() {
96
107
  p.log.info("Mode: Update");
97
108
  }
98
109
  else {
99
- p.log.info(`App: ${input.description}`);
110
+ if (input.url) {
111
+ p.log.info(`URL: ${input.url}`);
112
+ if (input.description && !input.description.startsWith("Clone website:")) {
113
+ p.log.info(`Focus: ${input.description}`);
114
+ }
115
+ }
116
+ else {
117
+ p.log.info(`App: ${input.description}`);
118
+ }
100
119
  p.log.info(`Platform: ${input.platform}`);
101
120
  p.log.info(`Engine: ${resolvedEngine.name}`);
102
121
  p.log.info(`Model: ${input.model}`);
103
- if (input.quick) {
122
+ if (input.url) {
123
+ p.log.info("Mode: Web Clone");
124
+ }
125
+ else if (input.quick) {
104
126
  p.log.info("Mode: Quick");
105
127
  }
106
128
  else if (input.scratch) {
@@ -153,7 +175,23 @@ async function main() {
153
175
  const monorepoDir = path.join(PATHS.output, appName); // repo root for GitHub
154
176
  const appDir = path.join(monorepoDir, singlePlatform); // where code lives
155
177
  let knownAppDir = appDir;
156
- if (input.quick) {
178
+ if (input.url) {
179
+ // Web clone mode: agent crawls the website, analyzes it, then builds an app
180
+ await fs.ensureDir(appDir);
181
+ // Create the web-crawl screenshots directory for the crawler
182
+ await fs.ensureDir(path.join(appDir, "web-crawl", "screenshots"));
183
+ prompt = await buildWebClonePrompt({ ...input, platform: singlePlatform });
184
+ // Redirect agent output to the platform subdir (monorepo layout)
185
+ prompt += `\n\n## OUTPUT DIRECTORY OVERRIDE\n\n` +
186
+ `IMPORTANT: This build uses a monorepo layout. You MUST write ALL output to \`${appDir}/\` instead of \`output/{app-name}/\`.\n` +
187
+ `Use "${appName}" as the app name (do NOT append the platform suffix).\n` +
188
+ `The app directory is: ${appDir}\n` +
189
+ `Reports go to: ${appDir}/reports/\n` +
190
+ `Tests go to: ${appDir}/tests/\n` +
191
+ `Web crawl screenshots go to: ${appDir}/web-crawl/screenshots/\n`;
192
+ p.log.info("Web clone prompt built successfully.");
193
+ }
194
+ else if (input.quick) {
157
195
  // Quick mode: orchestrator pre-clones the skeleton template, renames it, and generates project
158
196
  const displayName = deriveDisplayName(appName);
159
197
  const bundleId = deriveBundleId(appName);
@@ -246,7 +284,8 @@ async function main() {
246
284
  displayMaestroResults(agentResult);
247
285
  // ── Validate output ─────────────────────────────────────────────────
248
286
  p.log.step("Validating generated app...");
249
- const validation = await validateOutput(singlePlatform, knownAppDir);
287
+ const buildMode = input.url ? "web-clone" : input.quick ? "quick" : input.scratch ? "scratch" : "full";
288
+ const validation = await validateOutput(singlePlatform, knownAppDir, { mode: buildMode });
250
289
  reportValidation(validation);
251
290
  if (!validation.valid) {
252
291
  p.outro("Validation failed. The stop hook may have already retried. Check output/ for details.");
@@ -410,9 +449,10 @@ async function runParallelBuild(input, engine, cliResult, cliFlags, ghAvailable)
410
449
  // Validate each platform
411
450
  p.log.step("Validating generated apps...");
412
451
  let allValid = true;
452
+ const dualBuildMode = input.quick ? "quick" : input.scratch ? "scratch" : "full";
413
453
  for (const plat of platforms) {
414
454
  const platDir = path.join(monorepoDir, plat);
415
- const validation = await validateOutput(plat, platDir);
455
+ const validation = await validateOutput(plat, platDir, { mode: dualBuildMode });
416
456
  if (validation.valid) {
417
457
  p.log.success(`[${plat}] Validation passed`);
418
458
  }
@@ -515,9 +555,10 @@ async function handleGitHubPush(cliResult, cliFlags, ghAvailable, appDir, artifa
515
555
  }
516
556
  if (cliResult && cliFlags.push && appDir) {
517
557
  if (!ghAvailable) {
518
- p.log.error("Cannot push to GitHub: prerequisites not met. Run 'gh auth login' first.");
558
+ p.log.warn("Cannot push to GitHub: prerequisites not met. Run 'gh auth login' first.");
519
559
  p.outro(`App was generated at: ${appDir}${artifactLine}`);
520
- process.exit(1);
560
+ // Don't exit(1) — the app was generated successfully; push failure is non-fatal
561
+ return;
521
562
  }
522
563
  const repoName = cliFlags.repoName || deriveRepoName(appDir);
523
564
  const ghResult = await pushToGitHub(appDir, repoName);
@@ -525,9 +566,9 @@ async function handleGitHubPush(cliResult, cliFlags, ghAvailable, appDir, artifa
525
566
  p.outro(`Done! Your app is at: ${ghResult.repoUrl}${artifactLine}`);
526
567
  }
527
568
  else {
528
- p.log.error(`GitHub push failed: ${ghResult.error}`);
569
+ p.log.warn(`GitHub push failed: ${ghResult.error}`);
529
570
  p.outro(`App was generated at: ${appDir}${artifactLine}\nPush manually with: cd ${appDir} && gh repo create`);
530
- process.exit(1);
571
+ // Don't exit(1) — the app was generated successfully; push failure is non-fatal
531
572
  }
532
573
  }
533
574
  else if (cliResult) {
@@ -567,6 +608,50 @@ async function handleGitHubPush(cliResult, cliFlags, ghAvailable, appDir, artifa
567
608
  // ---------------------------------------------------------------------------
568
609
  // Preview & Screenshot
569
610
  // ---------------------------------------------------------------------------
611
+ const MIME_TYPES = {
612
+ ".html": "text/html",
613
+ ".css": "text/css",
614
+ ".js": "application/javascript",
615
+ ".json": "application/json",
616
+ ".png": "image/png",
617
+ ".jpg": "image/jpeg",
618
+ ".jpeg": "image/jpeg",
619
+ ".gif": "image/gif",
620
+ ".svg": "image/svg+xml",
621
+ ".ico": "image/x-icon",
622
+ ".webp": "image/webp",
623
+ };
624
+ /**
625
+ * Simple static file server using Node built-ins.
626
+ * Replaces serve-handler to avoid transitive dependency issues with npx.
627
+ */
628
+ function serveStatic(root, req, res) {
629
+ const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
630
+ let pathname = decodeURIComponent(parsedUrl.pathname);
631
+ // Default to index.html
632
+ if (pathname === "/")
633
+ pathname = "/index.html";
634
+ // Prevent directory traversal
635
+ const safePath = path.normalize(pathname).replace(/^(\.\.[/\\])+/, "");
636
+ const filePath = path.join(root, safePath);
637
+ // Ensure the resolved path is within root
638
+ if (!filePath.startsWith(root)) {
639
+ res.writeHead(403);
640
+ res.end("Forbidden");
641
+ return;
642
+ }
643
+ const ext = path.extname(filePath).toLowerCase();
644
+ const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
645
+ fs.readFile(filePath)
646
+ .then((data) => {
647
+ res.writeHead(200, { "Content-Type": contentType });
648
+ res.end(data);
649
+ })
650
+ .catch(() => {
651
+ res.writeHead(404);
652
+ res.end("Not Found");
653
+ });
654
+ }
570
655
  /**
571
656
  * Handle --preview <app-name> command.
572
657
  * Generates preview HTML from screenshots and serves it on a local HTTP server.
@@ -596,8 +681,7 @@ async function handlePreview(appName) {
596
681
  await generatePreview(appDir);
597
682
  // Start local server
598
683
  const PORT = 4321;
599
- const handler = await import("serve-handler");
600
- const server = createServer((req, res) => handler.default(req, res, { public: previewDir }));
684
+ const server = createServer((req, res) => serveStatic(previewDir, req, res));
601
685
  server.listen(PORT, async () => {
602
686
  const url = `http://localhost:${PORT}`;
603
687
  p.log.success(`Preview server running at: ${url}`);
@@ -635,6 +719,40 @@ async function handleScreenshot(appName) {
635
719
  if (!platform) {
636
720
  platform = hasXcodeproj ? "ios" : hasBuildGradle ? "android" : null;
637
721
  }
722
+ // Check subdirectories for nested project structures (e.g. output/app-name/ios/*.xcodeproj)
723
+ if (!platform) {
724
+ for (const entry of appDirEntries) {
725
+ const subPath = path.join(appDir, entry);
726
+ try {
727
+ const stat = await fs.stat(subPath);
728
+ if (!stat.isDirectory() || entry.startsWith(".") || entry === "node_modules")
729
+ continue;
730
+ const subEntries = await fs.readdir(subPath);
731
+ if (subEntries.some((e) => e.endsWith(".xcodeproj"))) {
732
+ platform = "ios";
733
+ break;
734
+ }
735
+ if (subEntries.includes("build.gradle.kts") || subEntries.includes("build.gradle")) {
736
+ platform = "android";
737
+ break;
738
+ }
739
+ // Check for React Native in subdirectory
740
+ if ((subEntries.includes("metro.config.js") || subEntries.includes("metro.config.ts")) && subEntries.includes("package.json")) {
741
+ try {
742
+ const pkgJson = await fs.readJson(path.join(subPath, "package.json"));
743
+ if (pkgJson.dependencies?.["react-native"] || pkgJson.devDependencies?.["react-native"]) {
744
+ platform = "react-native";
745
+ break;
746
+ }
747
+ }
748
+ catch { /* continue */ }
749
+ }
750
+ }
751
+ catch {
752
+ // Skip unreadable directories
753
+ }
754
+ }
755
+ }
638
756
  if (!platform) {
639
757
  p.log.error("Could not detect platform (no .xcodeproj, build.gradle.kts, or react-native package.json found).");
640
758
  process.exit(1);
@@ -731,8 +849,7 @@ async function autoOpenPreview(appDir, noPreview) {
731
849
  await generatePreview(appDir);
732
850
  // Start local server
733
851
  const PORT = 4321;
734
- const handler = await import("serve-handler");
735
- const server = createServer((req, res) => handler.default(req, res, { public: previewDir }));
852
+ const server = createServer((req, res) => serveStatic(previewDir, req, res));
736
853
  server.listen(PORT, async () => {
737
854
  const url = `http://localhost:${PORT}`;
738
855
  p.log.success(`Preview: ${url}`);
@@ -834,6 +951,152 @@ async function testApp(appName, engineOverride, modelOverride) {
834
951
  }
835
952
  p.outro(`UI testing complete for: ${ctx.dirPath}`);
836
953
  }
954
+ /**
955
+ * Deep-test an existing app: generate test plan, run exploratory + structured testing,
956
+ * produce bug report, and optionally auto-fix bugs.
957
+ */
958
+ async function deepTestApp(appName, autoFix, engineOverride, modelOverride) {
959
+ const fixLabel = autoFix ? " + Fix" : "";
960
+ p.intro(`AppAgent — Deep Test${fixLabel}: ${appName}`);
961
+ // ── Resolve app context ─────────────────────────────────────────────
962
+ const ctx = await resolveAppContext(appName);
963
+ p.log.info(`App: ${ctx.appName}`);
964
+ p.log.info(`Platform: ${ctx.platform}`);
965
+ p.log.info(`Bundle: ${ctx.bundleId}`);
966
+ p.log.info(`Mode: Deep Test${fixLabel}`);
967
+ if (ctx.artifactPath) {
968
+ p.log.info(`Artifact: ${ctx.artifactPath}`);
969
+ }
970
+ else {
971
+ p.log.warn("Build artifact not found — agent will rebuild before testing.");
972
+ }
973
+ if (ctx.manifestContent) {
974
+ p.log.info("Manifest: found (parallel batch testing enabled)");
975
+ }
976
+ // ── Determine engine and model ──────────────────────────────────────
977
+ const selectedEngine = engineOverride ?? DEFAULT_ENGINE;
978
+ const selectedModel = modelOverride ?? DEFAULT_MODEL;
979
+ const engine = createEngine(selectedEngine);
980
+ // ── Pre-flight check ────────────────────────────────────────────────
981
+ const agentCheck = await engine.checkInstallation();
982
+ if (!agentCheck.ok) {
983
+ p.log.error(agentCheck.error);
984
+ process.exit(1);
985
+ }
986
+ p.log.info(`Engine: ${engine.name}`);
987
+ p.log.info(`Model: ${selectedModel}`);
988
+ // ── Scaffold workspace (npx mode) ───────────────────────────────────
989
+ await scaffoldWorkspace();
990
+ // ── Build deep-test prompt ──────────────────────────────────────────
991
+ const prompt = await buildDeepTestPrompt(ctx, autoFix);
992
+ p.log.info("Deep test prompt built successfully.");
993
+ // ── Run the agent engine ────────────────────────────────────────────
994
+ p.log.step(`Launching ${engine.name} agent for deep testing...`);
995
+ const agentResult = await engine.run(prompt, selectedModel);
996
+ if (!agentResult.success) {
997
+ p.log.error(`Agent failed: ${agentResult.error}`);
998
+ p.outro("Deep testing did not complete successfully. Check the output directory for partial results.");
999
+ process.exit(1);
1000
+ }
1001
+ // ── Proof of work summary ──────────────────────────────────────────
1002
+ const { proof } = agentResult;
1003
+ p.log.step("Proof of work:");
1004
+ p.log.info(` Files written: ${proof.filesWritten}`);
1005
+ p.log.info(` Files read: ${proof.filesRead}`);
1006
+ p.log.info(` Shell commands: ${proof.shellCommands.length}`);
1007
+ p.log.info(` MCP tool calls: ${proof.mcpCalls.length}`);
1008
+ if (proof.costUsd > 0) {
1009
+ p.log.info(` API cost: $${proof.costUsd.toFixed(4)}`);
1010
+ }
1011
+ if (proof.subagentCalls.length > 0) {
1012
+ p.log.info(` Subagent delegations: ${proof.subagentCalls.join(", ")}`);
1013
+ }
1014
+ // ── Check reports ─────────────────────────────────────────────────
1015
+ const reportsDir = path.join(ctx.dirPath, "reports");
1016
+ const testPlanPath = path.join(reportsDir, "10-test-plan.md");
1017
+ const bugReportPath = path.join(reportsDir, "11-bug-report.md");
1018
+ const fixReportPath = path.join(reportsDir, "12-fix-report.md");
1019
+ if (await fs.pathExists(testPlanPath)) {
1020
+ p.log.info(" Test plan: reports/10-test-plan.md ✓");
1021
+ }
1022
+ if (await fs.pathExists(bugReportPath)) {
1023
+ p.log.info(" Bug report: reports/11-bug-report.md ✓");
1024
+ }
1025
+ if (autoFix && (await fs.pathExists(fixReportPath))) {
1026
+ p.log.info(" Fix report: reports/12-fix-report.md ✓");
1027
+ }
1028
+ // ── Report-based summary ───────────────────────────────────────────
1029
+ await displayReportSummary(appName);
1030
+ p.outro(`Deep testing complete for: ${ctx.dirPath}`);
1031
+ }
1032
+ /**
1033
+ * Fix bugs from a previous deep-test run: read bug report, fix each bug,
1034
+ * rebuild, and verify.
1035
+ */
1036
+ async function fixApp(appName, engineOverride, modelOverride) {
1037
+ p.intro(`AppAgent — Fix Bugs: ${appName}`);
1038
+ // ── Resolve app context ─────────────────────────────────────────────
1039
+ const ctx = await resolveAppContext(appName);
1040
+ p.log.info(`App: ${ctx.appName}`);
1041
+ p.log.info(`Platform: ${ctx.platform}`);
1042
+ p.log.info(`Bundle: ${ctx.bundleId}`);
1043
+ // ── Check for bug report ────────────────────────────────────────────
1044
+ const bugReportPath = path.join(ctx.dirPath, "reports", "11-bug-report.md");
1045
+ if (!(await fs.pathExists(bugReportPath))) {
1046
+ p.log.error(`No bug report found at: ${bugReportPath}`);
1047
+ p.log.error("Run --deep-test first to generate a bug report, then use --fix to fix the bugs.");
1048
+ process.exit(1);
1049
+ }
1050
+ p.log.info("Bug report: found ✓");
1051
+ if (ctx.artifactPath) {
1052
+ p.log.info(`Artifact: ${ctx.artifactPath}`);
1053
+ }
1054
+ else {
1055
+ p.log.warn("Build artifact not found — agent will rebuild.");
1056
+ }
1057
+ // ── Determine engine and model ──────────────────────────────────────
1058
+ const selectedEngine = engineOverride ?? DEFAULT_ENGINE;
1059
+ const selectedModel = modelOverride ?? DEFAULT_MODEL;
1060
+ const engine = createEngine(selectedEngine);
1061
+ // ── Pre-flight check ────────────────────────────────────────────────
1062
+ const agentCheck = await engine.checkInstallation();
1063
+ if (!agentCheck.ok) {
1064
+ p.log.error(agentCheck.error);
1065
+ process.exit(1);
1066
+ }
1067
+ p.log.info(`Engine: ${engine.name}`);
1068
+ p.log.info(`Model: ${selectedModel}`);
1069
+ // ── Scaffold workspace (npx mode) ───────────────────────────────────
1070
+ await scaffoldWorkspace();
1071
+ // ── Build fix prompt ───────────────────────────────────────────────
1072
+ const prompt = await buildFixPrompt(ctx);
1073
+ p.log.info("Fix prompt built successfully.");
1074
+ // ── Run the agent engine ────────────────────────────────────────────
1075
+ p.log.step(`Launching ${engine.name} agent for bug fixing...`);
1076
+ const agentResult = await engine.run(prompt, selectedModel);
1077
+ if (!agentResult.success) {
1078
+ p.log.error(`Agent failed: ${agentResult.error}`);
1079
+ p.outro("Bug fixing did not complete successfully. Check the output directory for partial results.");
1080
+ process.exit(1);
1081
+ }
1082
+ // ── Proof of work summary ──────────────────────────────────────────
1083
+ const { proof } = agentResult;
1084
+ p.log.step("Proof of work:");
1085
+ p.log.info(` Files written: ${proof.filesWritten}`);
1086
+ p.log.info(` Files read: ${proof.filesRead}`);
1087
+ p.log.info(` Shell commands: ${proof.shellCommands.length}`);
1088
+ if (proof.costUsd > 0) {
1089
+ p.log.info(` API cost: $${proof.costUsd.toFixed(4)}`);
1090
+ }
1091
+ // ── Check fix report ──────────────────────────────────────────────
1092
+ const fixReportPath = path.join(ctx.dirPath, "reports", "12-fix-report.md");
1093
+ if (await fs.pathExists(fixReportPath)) {
1094
+ p.log.info(" Fix report: reports/12-fix-report.md ✓");
1095
+ }
1096
+ // ── Report-based summary ───────────────────────────────────────────
1097
+ await displayReportSummary(appName);
1098
+ p.outro(`Bug fixing complete for: ${ctx.dirPath}`);
1099
+ }
837
1100
  /**
838
1101
  * Update an existing app: resolve source, run agent, commit, and optionally create a PR.
839
1102
  */
@@ -1107,13 +1370,28 @@ function deriveAppName(description) {
1107
1370
  }
1108
1371
  else {
1109
1372
  // Take the first few meaningful words from the description
1110
- const stopWords = new Set(["a", "an", "the", "and", "or", "for", "with", "to", "of", "in", "on", "app", "application"]);
1373
+ const stopWords = new Set([
1374
+ // Articles & conjunctions
1375
+ "a", "an", "the", "and", "or", "but", "not", "nor",
1376
+ // Prepositions
1377
+ "for", "with", "to", "of", "in", "on", "at", "by", "from", "into", "about", "between",
1378
+ // Common verbs / auxiliaries
1379
+ "is", "are", "was", "be", "has", "have", "will", "would", "could", "should",
1380
+ "can", "do", "does", "did",
1381
+ // Pronouns & determiners
1382
+ "my", "your", "its", "their", "our", "this", "that", "these", "those",
1383
+ "it", "them", "which", "where", "when", "who", "what", "how",
1384
+ // Filler / noise words
1385
+ "app", "application", "based", "using", "like", "just", "very", "also",
1386
+ "so", "such", "each", "every", "some", "any", "no", "only", "own", "more",
1387
+ "than", "been", "all",
1388
+ ]);
1111
1389
  const words = description
1112
1390
  .toLowerCase()
1113
1391
  .replace(/[^a-z0-9\s]/g, "")
1114
1392
  .split(/\s+/)
1115
1393
  .filter((w) => w.length > 1 && !stopWords.has(w))
1116
- .slice(0, 3);
1394
+ .slice(0, 2);
1117
1395
  baseName = words.join("-") || "my-app";
1118
1396
  }
1119
1397
  // Convert to kebab-case