reis-mobile 0.3.6 → 0.4.0

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 (463) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +24 -0
  4. package/README.md +175 -168
  5. package/THIRD_PARTY_NOTICES.md +224 -4
  6. package/agents/android-architect.md +89 -0
  7. package/agents/android-performance-engineer.md +92 -0
  8. package/agents/android-test-engineer.md +97 -0
  9. package/agents/flutter-test-engineer.md +2 -0
  10. package/agents/ios-architect.md +93 -0
  11. package/agents/ios-performance-engineer.md +92 -0
  12. package/agents/ios-test-engineer.md +95 -0
  13. package/agents/lead-mobile.md +10 -1
  14. package/agents/mobile-staff-engineer.md +1 -1
  15. package/agents/rn-architect.md +94 -0
  16. package/agents/rn-performance-engineer.md +92 -0
  17. package/agents/rn-test-engineer.md +98 -0
  18. package/bin/reis-mobile.mjs +59 -17
  19. package/commands/debate.md +10 -1
  20. package/commands/debug.md +60 -0
  21. package/commands/reis-mobile.md +7 -2
  22. package/core/config/project.mjs +59 -0
  23. package/core/registry/frontmatter.mjs +15 -4
  24. package/core/registry/registry.mjs +21 -1
  25. package/core/router/intents.mjs +2 -0
  26. package/core/router/router.mjs +10 -5
  27. package/package.json +1 -1
  28. package/skills/android-adaptive/SKILL.md +304 -0
  29. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/container-behavior.md +112 -0
  30. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md +69 -0
  31. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/index.md +81 -0
  32. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/item-behavior.md +170 -0
  33. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/container-properties.md +415 -0
  34. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md +51 -0
  35. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/index.md +73 -0
  36. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/item-properties.md +170 -0
  37. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/mediaquery/index.md +333 -0
  38. package/skills/android-adaptive/references/android/develop/ui/compose/tooling/debug.md +114 -0
  39. package/skills/android-adaptive/references/android/guide/navigation/navigation-3/recipes/material-listdetail.md +141 -0
  40. package/skills/android-agp-9-upgrade/SKILL.md +106 -0
  41. package/skills/android-agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md +416 -0
  42. package/skills/android-agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md +554 -0
  43. package/skills/android-agp-9-upgrade/references/buildconfig.md +54 -0
  44. package/skills/android-agp-9-upgrade/references/ksp-kapt.md +39 -0
  45. package/skills/android-agp-9-upgrade/references/paparazzi-gradle-9.md +30 -0
  46. package/skills/android-agp-9-upgrade/references/recipes.md +62 -0
  47. package/skills/android-appfunctions/SKILL.md +74 -0
  48. package/skills/android-appfunctions/references/adb-interaction-testing.md +95 -0
  49. package/skills/android-appfunctions/references/context.md +26 -0
  50. package/skills/android-appfunctions/references/feature-discovery-analysis.md +71 -0
  51. package/skills/android-appfunctions/references/implementation-configuration.md +282 -0
  52. package/skills/android-appfunctions/references/kdoc-refinement-optimization.md +75 -0
  53. package/skills/android-appfunctions/references/migrate-to-service-entry-point.md +226 -0
  54. package/skills/android-camerax/SKILL.md +135 -0
  55. package/skills/android-camerax/references/camera1-to-camerax.md +198 -0
  56. package/skills/android-camerax/references/camera2-to-camerax.md +193 -0
  57. package/skills/android-camerax/references/expert-blueprints.md +58 -0
  58. package/skills/android-camerax/references/foldables.md +83 -0
  59. package/skills/android-camerax/references/immutability.md +60 -0
  60. package/skills/android-camerax/references/low-light.md +124 -0
  61. package/skills/android-camerax/references/mlkit-spatial.md +75 -0
  62. package/skills/android-camerax/references/modern-apis.md +42 -0
  63. package/skills/android-camerax/references/testing.md +69 -0
  64. package/skills/android-camerax/references/thermals.md +85 -0
  65. package/skills/android-camerax/references/wear-os.md +76 -0
  66. package/skills/android-camerax/references/xr.md +54 -0
  67. package/skills/android-cli/SKILL.md +474 -0
  68. package/skills/android-cli/references/interact.md +113 -0
  69. package/skills/android-cli/references/journeys.md +120 -0
  70. package/skills/android-code-review/SKILL.md +58 -0
  71. package/skills/android-display-glasses-with-jetpack-compose-glimmer/SKILL.md +340 -0
  72. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/design/ui/ai-glasses/guides/interaction/inputs.md +67 -0
  73. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/access-hardware-projected-context.md +388 -0
  74. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/buttons.md +139 -0
  75. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/cards.md +148 -0
  76. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/focus.md +107 -0
  77. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/icons.md +90 -0
  78. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/text.md +69 -0
  79. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/title-chips.md +82 -0
  80. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/request-hardware-permissions.md +213 -0
  81. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/button-samples-source.md +176 -0
  82. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/button-source.md +203 -0
  83. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/card-samples-source.md +296 -0
  84. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/card-source.md +515 -0
  85. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/deptheffect-source.md +224 -0
  86. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/deptheffectlevels-source.md +105 -0
  87. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/glimmersansflextypography-source.md +270 -0
  88. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/glimmertheme-source.md +182 -0
  89. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/icon-source.md +388 -0
  90. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/iconbutton-source.md +126 -0
  91. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/iconsizes-source.md +78 -0
  92. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/list-source.md +27 -0
  93. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/listitem-source.md +272 -0
  94. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/liststate-source.md +427 -0
  95. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/material-hct-source.md +217 -0
  96. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/projectedcontext-source.md +265 -0
  97. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/stack-source.md +368 -0
  98. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/stackitemscope-source.md +440 -0
  99. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/stackstate-source.md +325 -0
  100. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/surface-source.md +1211 -0
  101. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/titlechip-samples-source.md +101 -0
  102. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/titlechip-source.md +131 -0
  103. package/skills/android-edge-to-edge/SKILL.md +429 -0
  104. package/skills/android-engage-sdk-integration/SKILL.md +130 -0
  105. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/faq.md +439 -0
  106. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/food.md +1149 -0
  107. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/healthandfitness.md +1055 -0
  108. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/listen.md +1050 -0
  109. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/otherverticals.md +1301 -0
  110. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/read.md +910 -0
  111. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/shopping.md +1224 -0
  112. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/social.md +719 -0
  113. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/travel.md +1413 -0
  114. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/continue-watching/index.md +19 -0
  115. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/entitlements.md +353 -0
  116. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/getting-started.md +202 -0
  117. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/recommendations.md +471 -0
  118. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/watch.md +1081 -0
  119. package/skills/android-engage-sdk-integration/references/clusters.md +154 -0
  120. package/skills/android-engage-sdk-integration/references/common.md +519 -0
  121. package/skills/android-engage-sdk-integration/references/patterns.md +527 -0
  122. package/skills/android-engage-sdk-integration/references/requests.md +260 -0
  123. package/skills/android-engage-sdk-integration/references/schemas/food.md +509 -0
  124. package/skills/android-engage-sdk-integration/references/schemas/listen.md +842 -0
  125. package/skills/android-engage-sdk-integration/references/schemas/other.md +220 -0
  126. package/skills/android-engage-sdk-integration/references/schemas/read.md +412 -0
  127. package/skills/android-engage-sdk-integration/references/schemas/shopping.md +357 -0
  128. package/skills/android-engage-sdk-integration/references/schemas/social.md +228 -0
  129. package/skills/android-engage-sdk-integration/references/schemas/travel.md +923 -0
  130. package/skills/android-engage-sdk-integration/references/schemas/tv.md +1226 -0
  131. package/skills/android-engage-sdk-integration/references/schemas/watch.md +1137 -0
  132. package/skills/android-engage-sdk-integration/references/troubleshooting.md +39 -0
  133. package/skills/android-gradle-build-debug/SKILL.md +71 -0
  134. package/skills/android-intent-security/SKILL.md +550 -0
  135. package/skills/android-leanback-to-compose-tv-migration/SKILL.md +743 -0
  136. package/skills/android-leanback-to-compose-tv-migration/references/android/training/tv/get-started/navigation.md +372 -0
  137. package/skills/android-leanback-to-compose-tv-migration/references/android/training/tv/playback/compose/browse.md +243 -0
  138. package/skills/android-leanback-to-compose-tv-migration/references/android/training/tv/playback/compose/index.md +153 -0
  139. package/skills/android-media3-cast-integration/SKILL.md +236 -0
  140. package/skills/android-media3-cast-integration/references/android/media/media3/cast/create-castplayer.md +390 -0
  141. package/skills/android-media3-cast-integration/references/android/media/media3/cast/customize-castoptions.md +93 -0
  142. package/skills/android-media3-cast-integration/references/android/media/media3/cast/index.md +21 -0
  143. package/skills/android-migrate-xml-views-to-jetpack-compose/SKILL.md +127 -0
  144. package/skills/android-migrate-xml-views-to-jetpack-compose/references/analysis-of-the-project-and-layout.md +42 -0
  145. package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md +171 -0
  146. package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/compose-in-views.md +299 -0
  147. package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md +286 -0
  148. package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md +197 -0
  149. package/skills/android-migrate-xml-views-to-jetpack-compose/references/identify-optimal-xml-candidate.md +31 -0
  150. package/skills/android-migrate-xml-views-to-jetpack-compose/references/xml-layout-migration.md +86 -0
  151. package/skills/android-ml-kit-genai-prompt-api/SKILL.md +124 -0
  152. package/skills/android-ml-kit-genai-prompt-api/references/get-started.md +272 -0
  153. package/skills/android-ml-kit-genai-prompt-api/references/prefix-caching.md +146 -0
  154. package/skills/android-ml-kit-genai-prompt-api/references/prompt-design.md +55 -0
  155. package/skills/android-ml-kit-genai-prompt-api/references/prompt-optimization.md +74 -0
  156. package/skills/android-ml-kit-genai-prompt-api/references/structured-output.md +219 -0
  157. package/skills/android-ml-kit-genai-prompt-api/references/system-instructions.md +71 -0
  158. package/skills/android-navigation-3/SKILL.md +122 -0
  159. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/index.md +51 -0
  160. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/migration-guide.md +670 -0
  161. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/animations.md +147 -0
  162. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/basic.md +89 -0
  163. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/basicdsl.md +85 -0
  164. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/basicsaveable.md +90 -0
  165. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/bottomsheet.md +195 -0
  166. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/common-ui.md +200 -0
  167. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/conditional-transitions.md +182 -0
  168. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/conditional.md +230 -0
  169. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/deeplinks-custommatcher.md +176 -0
  170. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/deeplinks-staticuri.md +140 -0
  171. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/deeplinks-syntheticbackstack.md +184 -0
  172. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/dialog.md +107 -0
  173. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/lifecycle-owner.md +185 -0
  174. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/material-listdetail.md +141 -0
  175. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/material-supportingpane.md +145 -0
  176. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/modular-hilt.md +283 -0
  177. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/modular-koin.md +287 -0
  178. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/multiple-backstacks.md +468 -0
  179. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/passingarguments.md +371 -0
  180. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/results-event.md +273 -0
  181. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/results-state.md +266 -0
  182. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/scenes-listdetail.md +435 -0
  183. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/scenes-twopane.md +244 -0
  184. package/skills/android-navigation-3/references/android/guide/navigation/type-safe-destinations.md +129 -0
  185. package/skills/android-navigation-event/SKILL.md +312 -0
  186. package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/dispatcher.md +134 -0
  187. package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/handle-back.md +217 -0
  188. package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/index.md +83 -0
  189. package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/setup.md +32 -0
  190. package/skills/android-play-billing-library-version-upgrade/SKILL.md +96 -0
  191. package/skills/android-play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md +1240 -0
  192. package/skills/android-play-billing-library-version-upgrade/references/migration-logic.md +78 -0
  193. package/skills/android-play-billing-library-version-upgrade/references/version-checklist.md +54 -0
  194. package/skills/android-play-policy-insights/SKILL.md +172 -0
  195. package/skills/android-play-policy-insights/resources/common_mandates.md +100 -0
  196. package/skills/android-play-policy-insights/resources/compliance_report_template.md +59 -0
  197. package/skills/android-play-policy-insights/resources/critic.md +86 -0
  198. package/skills/android-play-policy-insights/resources/goal_data_safety.md +212 -0
  199. package/skills/android-play-policy-insights/resources/goal_permissions_and_apis.md +459 -0
  200. package/skills/android-play-policy-insights/resources/goal_user_account.md +106 -0
  201. package/skills/android-play-policy-insights/resources/policies.json +321 -0
  202. package/skills/android-play-policy-insights/resources/scanner_config.json +843 -0
  203. package/skills/android-play-policy-insights/scripts/generate_report.py +860 -0
  204. package/skills/android-play-policy-insights/scripts/orchestrator.py +1313 -0
  205. package/skills/android-play-policy-insights/scripts/play_store_scraper.py +410 -0
  206. package/skills/android-play-policy-insights/scripts/scanner.py +261 -0
  207. package/skills/android-play-policy-insights/scripts/template_engine.py +243 -0
  208. package/skills/android-profiler/SKILL.md +60 -0
  209. package/skills/android-profiler/analysis/analysis_orchestrator.md +32 -0
  210. package/skills/android-profiler/analysis/workflows/perfetto-sql/perfetto_sql.md +28 -0
  211. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/perfetto_trace_analysis.md +121 -0
  212. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/guiding_principles.md +51 -0
  213. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/domain_hints_template.md +7 -0
  214. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_cpu.md +56 -0
  215. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_graphics.md +51 -0
  216. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_io.md +46 -0
  217. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_ipc.md +41 -0
  218. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_memory.md +55 -0
  219. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_power.md +33 -0
  220. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/per_candidate_analysis.md +172 -0
  221. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/triage.md +94 -0
  222. package/skills/android-profiler/recording/recording_orchestrator.md +36 -0
  223. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/perfetto_trace_recording.md +148 -0
  224. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/app_jank.pftxt +43 -0
  225. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/cpu_profile.pftxt +35 -0
  226. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/java_heap_dump.pftxt +21 -0
  227. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/long_background.pftxt +37 -0
  228. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/memory_counters.pftxt +45 -0
  229. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/native_heap.pftxt +21 -0
  230. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/sched_cpu.pftxt +33 -0
  231. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/trace_config_reference.md +102 -0
  232. package/skills/android-profiler/references/env_setup.md +15 -0
  233. package/skills/android-profiler/references/perfetto/setup.md +29 -0
  234. package/skills/android-profiler/references/perfetto/sql.md +327 -0
  235. package/skills/android-r8-analyzer/SKILL.md +72 -0
  236. package/skills/android-r8-analyzer/references/CONFIGURATION-ANALYZER.md +287 -0
  237. package/skills/android-r8-analyzer/references/CONFIGURATION.md +44 -0
  238. package/skills/android-r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md +83 -0
  239. package/skills/android-r8-analyzer/references/REDUNDANT-RULES.md +222 -0
  240. package/skills/android-r8-analyzer/references/REFLECTION-GUIDE.md +139 -0
  241. package/skills/android-r8-analyzer/references/REPORT_FORMAT.md +51 -0
  242. package/skills/android-r8-analyzer/references/android/topic/performance/app-optimization/enable-app-optimization.md +252 -0
  243. package/skills/android-r8-analyzer/references/android/training/testing/other-components/ui-automator.md +312 -0
  244. package/skills/android-restore-credentials/SKILL.md +359 -0
  245. package/skills/android-restore-credentials/references/android/guide/topics/manifest/application-element.md +832 -0
  246. package/skills/android-restore-credentials/references/android/identity/passkeys/create-passkeys.md +327 -0
  247. package/skills/android-restore-credentials/references/android/identity/passkeys/sign-in-with-passkeys.md +219 -0
  248. package/skills/android-styles/SKILL.md +229 -0
  249. package/skills/android-styles/references/android/develop/ui/compose/designsystems/custom.md +459 -0
  250. package/skills/android-styles/references/android/develop/ui/compose/styles/fundamentals.md +421 -0
  251. package/skills/android-styles/references/android/develop/ui/compose/styles/state-animations.md +461 -0
  252. package/skills/android-styles/references/android/develop/ui/compose/styles/styles-vs-modifiers.md +48 -0
  253. package/skills/android-styles/references/android/develop/ui/compose/styles/theming.md +269 -0
  254. package/skills/android-verified-email/SKILL.md +432 -0
  255. package/skills/android-verified-email/references/android/identity/credential-manager/index.md +67 -0
  256. package/skills/android-verified-email/references/android/identity/digital-credentials/credential-verifier.md +166 -0
  257. package/skills/android-verified-email/references/android/identity/digital-credentials/email-verification-implementation.md +313 -0
  258. package/skills/android-verified-email/references/android/identity/digital-credentials/email-verification.md +158 -0
  259. package/skills/android-verified-email/references/android/identity/digital-credentials/index.md +112 -0
  260. package/skills/android-verified-email/references/android/identity/passkeys/create-passkeys.md +327 -0
  261. package/skills/android-verified-email/references/android/identity/sign-in/credential-manager-webview.md +84 -0
  262. package/skills/android-wear-compose-m3/SKILL.md +365 -0
  263. package/skills/android-wear-compose-m3/references/android/training/wearables/compose/migrate-to-material3.md +677 -0
  264. package/skills/android-wear-compose-m3/references/foundation/AmbientModeSample.kt.md +123 -0
  265. package/skills/android-wear-compose-m3/references/foundation/AmbientModeSample.kt.md.txt +123 -0
  266. package/skills/android-wear-compose-m3/references/foundation/CurvedWorldSample.kt.md +430 -0
  267. package/skills/android-wear-compose-m3/references/foundation/CurvedWorldSample.kt.md.txt +430 -0
  268. package/skills/android-wear-compose-m3/references/foundation/ExpandableSample.kt.md +99 -0
  269. package/skills/android-wear-compose-m3/references/foundation/ExpandableSample.kt.md.txt +99 -0
  270. package/skills/android-wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md +141 -0
  271. package/skills/android-wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md.txt +141 -0
  272. package/skills/android-wear-compose-m3/references/foundation/PagerSamples.kt.md +56 -0
  273. package/skills/android-wear-compose-m3/references/foundation/PagerSamples.kt.md.txt +56 -0
  274. package/skills/android-wear-compose-m3/references/foundation/RotarySamples.kt.md +153 -0
  275. package/skills/android-wear-compose-m3/references/foundation/RotarySamples.kt.md.txt +153 -0
  276. package/skills/android-wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md +131 -0
  277. package/skills/android-wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md.txt +131 -0
  278. package/skills/android-wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md +169 -0
  279. package/skills/android-wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md.txt +169 -0
  280. package/skills/android-wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md +282 -0
  281. package/skills/android-wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md.txt +282 -0
  282. package/skills/android-wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md +335 -0
  283. package/skills/android-wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md.txt +335 -0
  284. package/skills/android-wear-compose-m3/references/material3/AlertDialogSample.kt.md +377 -0
  285. package/skills/android-wear-compose-m3/references/material3/AlertDialogSample.kt.md.txt +377 -0
  286. package/skills/android-wear-compose-m3/references/material3/AnimatedTextSample.kt.md +165 -0
  287. package/skills/android-wear-compose-m3/references/material3/AnimatedTextSample.kt.md.txt +165 -0
  288. package/skills/android-wear-compose-m3/references/material3/ButtonGroupSample.kt.md +91 -0
  289. package/skills/android-wear-compose-m3/references/material3/ButtonGroupSample.kt.md.txt +91 -0
  290. package/skills/android-wear-compose-m3/references/material3/ButtonSample.kt.md +496 -0
  291. package/skills/android-wear-compose-m3/references/material3/ButtonSample.kt.md.txt +496 -0
  292. package/skills/android-wear-compose-m3/references/material3/CardSample.kt.md +470 -0
  293. package/skills/android-wear-compose-m3/references/material3/CardSample.kt.md.txt +470 -0
  294. package/skills/android-wear-compose-m3/references/material3/CheckboxButtonSample.kt.md +67 -0
  295. package/skills/android-wear-compose-m3/references/material3/CheckboxButtonSample.kt.md.txt +67 -0
  296. package/skills/android-wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md +162 -0
  297. package/skills/android-wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md.txt +162 -0
  298. package/skills/android-wear-compose-m3/references/material3/CurvedTextSamples.kt.md +78 -0
  299. package/skills/android-wear-compose-m3/references/material3/CurvedTextSamples.kt.md.txt +78 -0
  300. package/skills/android-wear-compose-m3/references/material3/DatePickerSample.kt.md +131 -0
  301. package/skills/android-wear-compose-m3/references/material3/DatePickerSample.kt.md.txt +131 -0
  302. package/skills/android-wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md +74 -0
  303. package/skills/android-wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md.txt +74 -0
  304. package/skills/android-wear-compose-m3/references/material3/EdgeButtonSample.kt.md +63 -0
  305. package/skills/android-wear-compose-m3/references/material3/EdgeButtonSample.kt.md.txt +63 -0
  306. package/skills/android-wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md +60 -0
  307. package/skills/android-wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md.txt +60 -0
  308. package/skills/android-wear-compose-m3/references/material3/IconButtonSample.kt.md +125 -0
  309. package/skills/android-wear-compose-m3/references/material3/IconButtonSample.kt.md.txt +125 -0
  310. package/skills/android-wear-compose-m3/references/material3/IconToggleButtonSample.kt.md +116 -0
  311. package/skills/android-wear-compose-m3/references/material3/IconToggleButtonSample.kt.md.txt +116 -0
  312. package/skills/android-wear-compose-m3/references/material3/LevelIndicatorSample.kt.md +68 -0
  313. package/skills/android-wear-compose-m3/references/material3/LevelIndicatorSample.kt.md.txt +68 -0
  314. package/skills/android-wear-compose-m3/references/material3/LinearProgressIndicatorSample.kt.md +50 -0
  315. package/skills/android-wear-compose-m3/references/material3/LinearProgressIndicatorSample.kt.md.txt +50 -0
  316. package/skills/android-wear-compose-m3/references/material3/ListHeaderSample.kt.md +177 -0
  317. package/skills/android-wear-compose-m3/references/material3/ListHeaderSample.kt.md.txt +177 -0
  318. package/skills/android-wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md +651 -0
  319. package/skills/android-wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md.txt +651 -0
  320. package/skills/android-wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md +57 -0
  321. package/skills/android-wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md.txt +57 -0
  322. package/skills/android-wear-compose-m3/references/material3/PageIndicatorSample.kt.md +110 -0
  323. package/skills/android-wear-compose-m3/references/material3/PageIndicatorSample.kt.md.txt +110 -0
  324. package/skills/android-wear-compose-m3/references/material3/PagerScaffoldSample.kt.md +204 -0
  325. package/skills/android-wear-compose-m3/references/material3/PagerScaffoldSample.kt.md.txt +204 -0
  326. package/skills/android-wear-compose-m3/references/material3/PickerGroupSample.kt.md +139 -0
  327. package/skills/android-wear-compose-m3/references/material3/PickerGroupSample.kt.md.txt +139 -0
  328. package/skills/android-wear-compose-m3/references/material3/PickerSample.kt.md +111 -0
  329. package/skills/android-wear-compose-m3/references/material3/PickerSample.kt.md.txt +111 -0
  330. package/skills/android-wear-compose-m3/references/material3/PlaceholderSample.kt.md +184 -0
  331. package/skills/android-wear-compose-m3/references/material3/PlaceholderSample.kt.md.txt +184 -0
  332. package/skills/android-wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md +263 -0
  333. package/skills/android-wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md.txt +263 -0
  334. package/skills/android-wear-compose-m3/references/material3/RadioButtonSample.kt.md +105 -0
  335. package/skills/android-wear-compose-m3/references/material3/RadioButtonSample.kt.md.txt +105 -0
  336. package/skills/android-wear-compose-m3/references/material3/ScaffoldSample.kt.md +146 -0
  337. package/skills/android-wear-compose-m3/references/material3/ScaffoldSample.kt.md.txt +146 -0
  338. package/skills/android-wear-compose-m3/references/material3/ScrollAwaySample.kt.md +97 -0
  339. package/skills/android-wear-compose-m3/references/material3/ScrollAwaySample.kt.md.txt +97 -0
  340. package/skills/android-wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md +66 -0
  341. package/skills/android-wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md.txt +66 -0
  342. package/skills/android-wear-compose-m3/references/material3/SliderSample.kt.md +86 -0
  343. package/skills/android-wear-compose-m3/references/material3/SliderSample.kt.md.txt +86 -0
  344. package/skills/android-wear-compose-m3/references/material3/StepperSample.kt.md +148 -0
  345. package/skills/android-wear-compose-m3/references/material3/StepperSample.kt.md.txt +148 -0
  346. package/skills/android-wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md +166 -0
  347. package/skills/android-wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md.txt +166 -0
  348. package/skills/android-wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md +172 -0
  349. package/skills/android-wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md.txt +172 -0
  350. package/skills/android-wear-compose-m3/references/material3/SwipeToRevealSample.kt.md +367 -0
  351. package/skills/android-wear-compose-m3/references/material3/SwipeToRevealSample.kt.md.txt +367 -0
  352. package/skills/android-wear-compose-m3/references/material3/SwitchButtonSample.kt.md +66 -0
  353. package/skills/android-wear-compose-m3/references/material3/SwitchButtonSample.kt.md.txt +66 -0
  354. package/skills/android-wear-compose-m3/references/material3/TextButtonSample.kt.md +115 -0
  355. package/skills/android-wear-compose-m3/references/material3/TextButtonSample.kt.md.txt +115 -0
  356. package/skills/android-wear-compose-m3/references/material3/TextToggleButtonSample.kt.md +71 -0
  357. package/skills/android-wear-compose-m3/references/material3/TextToggleButtonSample.kt.md.txt +71 -0
  358. package/skills/android-wear-compose-m3/references/material3/TimePickerSample.kt.md +151 -0
  359. package/skills/android-wear-compose-m3/references/material3/TimePickerSample.kt.md.txt +151 -0
  360. package/skills/android-wear-compose-m3/references/material3/TimeTextSample.kt.md +65 -0
  361. package/skills/android-wear-compose-m3/references/material3/TimeTextSample.kt.md.txt +65 -0
  362. package/skills/android-wear-compose-m3/references/material3/TransformationSpecSample.kt.md +246 -0
  363. package/skills/android-wear-compose-m3/references/material3/TransformationSpecSample.kt.md.txt +246 -0
  364. package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md +89 -0
  365. package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt +89 -0
  366. package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md +309 -0
  367. package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md.txt +309 -0
  368. package/skills/android-wear-compose-m3/references/navigation3/SwipeDismissableSceneStrategySample.kt.md +205 -0
  369. package/skills/android-wear-compose-m3/references/navigation3/SwipeDismissableSceneStrategySample.kt.md.txt +205 -0
  370. package/skills/{architecture-feature-first → flutter-architecture-feature-first}/SKILL.md +1 -1
  371. package/skills/{bloc → flutter-bloc}/SKILL.md +1 -1
  372. package/skills/flutter-build-debug/SKILL.md +66 -0
  373. package/skills/{code-review → flutter-code-review}/SKILL.md +1 -1
  374. package/skills/{dart-3-updates → flutter-dart-3-updates}/SKILL.md +1 -1
  375. package/skills/{dart-fix-runtime-errors → flutter-dart-fix-runtime-errors}/SKILL.md +1 -1
  376. package/skills/{dart-resolve-package-conflicts → flutter-dart-resolve-package-conflicts}/SKILL.md +1 -1
  377. package/skills/{dart-run-static-analysis → flutter-dart-run-static-analysis}/SKILL.md +1 -1
  378. package/skills/{dart-use-pattern-matching → flutter-dart-use-pattern-matching}/SKILL.md +1 -1
  379. package/skills/{effective-dart → flutter-effective-dart}/SKILL.md +1 -1
  380. package/skills/{provider → flutter-provider}/SKILL.md +1 -1
  381. package/skills/{riverpod → flutter-riverpod}/SKILL.md +1 -1
  382. package/skills/ios-cocoapods-debug/SKILL.md +65 -0
  383. package/skills/ios-code-review/SKILL.md +56 -0
  384. package/skills/ios-xcode-build-debug/SKILL.md +66 -0
  385. package/skills/{detect-mobile-stack → mobile-detect-stack}/SKILL.md +1 -1
  386. package/skills/mobile-firebase/SKILL.md +54 -0
  387. package/skills/mobile-firebase/references/android/ai-logic.md +31 -0
  388. package/skills/mobile-firebase/references/android/analytics.md +33 -0
  389. package/skills/mobile-firebase/references/android/app-check.md +36 -0
  390. package/skills/mobile-firebase/references/android/auth.md +44 -0
  391. package/skills/mobile-firebase/references/android/crashlytics.md +31 -0
  392. package/skills/mobile-firebase/references/android/firestore.md +44 -0
  393. package/skills/mobile-firebase/references/android/functions.md +33 -0
  394. package/skills/mobile-firebase/references/android/in-app-messaging.md +28 -0
  395. package/skills/mobile-firebase/references/android/messaging.md +41 -0
  396. package/skills/mobile-firebase/references/android/realtime-database.md +50 -0
  397. package/skills/mobile-firebase/references/android/remote-config.md +43 -0
  398. package/skills/mobile-firebase/references/android/sql-connect.md +31 -0
  399. package/skills/mobile-firebase/references/android/storage.md +39 -0
  400. package/skills/mobile-firebase/references/android.md +30 -0
  401. package/skills/{firebase-ai/SKILL.md → mobile-firebase/references/flutter/ai-logic.md} +2 -8
  402. package/skills/{firebase-analytics/SKILL.md → mobile-firebase/references/flutter/analytics.md} +2 -8
  403. package/skills/{firebase-app-check/SKILL.md → mobile-firebase/references/flutter/app-check.md} +2 -8
  404. package/skills/{firebase-auth/SKILL.md → mobile-firebase/references/flutter/auth.md} +2 -8
  405. package/skills/{firebase-crashlytics/SKILL.md → mobile-firebase/references/flutter/crashlytics.md} +2 -8
  406. package/skills/{firebase-cloud-firestore/SKILL.md → mobile-firebase/references/flutter/firestore.md} +2 -8
  407. package/skills/{firebase-cloud-functions/SKILL.md → mobile-firebase/references/flutter/functions.md} +2 -8
  408. package/skills/{firebase-in-app-messaging/SKILL.md → mobile-firebase/references/flutter/in-app-messaging.md} +2 -8
  409. package/skills/{firebase-messaging/SKILL.md → mobile-firebase/references/flutter/messaging.md} +2 -8
  410. package/skills/{firebase-database/SKILL.md → mobile-firebase/references/flutter/realtime-database.md} +2 -8
  411. package/skills/{firebase-remote-config/SKILL.md → mobile-firebase/references/flutter/remote-config.md} +2 -8
  412. package/skills/{flutterfire-configure/SKILL.md → mobile-firebase/references/flutter/setup-flutterfire.md} +1 -8
  413. package/skills/{firebase-data-connect/SKILL.md → mobile-firebase/references/flutter/sql-connect.md} +2 -8
  414. package/skills/{firebase-storage/SKILL.md → mobile-firebase/references/flutter/storage.md} +2 -8
  415. package/skills/mobile-firebase/references/flutter.md +36 -0
  416. package/skills/mobile-firebase/references/ios/ai-logic.md +30 -0
  417. package/skills/mobile-firebase/references/ios/analytics.md +33 -0
  418. package/skills/mobile-firebase/references/ios/app-check.md +36 -0
  419. package/skills/mobile-firebase/references/ios/auth.md +48 -0
  420. package/skills/mobile-firebase/references/ios/crashlytics.md +34 -0
  421. package/skills/mobile-firebase/references/ios/firestore.md +53 -0
  422. package/skills/mobile-firebase/references/ios/functions.md +35 -0
  423. package/skills/mobile-firebase/references/ios/in-app-messaging.md +26 -0
  424. package/skills/mobile-firebase/references/ios/messaging.md +45 -0
  425. package/skills/mobile-firebase/references/ios/realtime-database.md +49 -0
  426. package/skills/mobile-firebase/references/ios/remote-config.md +44 -0
  427. package/skills/mobile-firebase/references/ios/sql-connect.md +33 -0
  428. package/skills/mobile-firebase/references/ios/storage.md +36 -0
  429. package/skills/mobile-firebase/references/ios.md +28 -0
  430. package/skills/mobile-firebase/references/react-native/ai-logic.md +32 -0
  431. package/skills/mobile-firebase/references/react-native/analytics.md +43 -0
  432. package/skills/mobile-firebase/references/react-native/app-check.md +35 -0
  433. package/skills/mobile-firebase/references/react-native/auth.md +50 -0
  434. package/skills/mobile-firebase/references/react-native/crashlytics.md +36 -0
  435. package/skills/mobile-firebase/references/react-native/firestore.md +45 -0
  436. package/skills/mobile-firebase/references/react-native/functions.md +28 -0
  437. package/skills/mobile-firebase/references/react-native/in-app-messaging.md +27 -0
  438. package/skills/mobile-firebase/references/react-native/messaging.md +46 -0
  439. package/skills/mobile-firebase/references/react-native/realtime-database.md +46 -0
  440. package/skills/mobile-firebase/references/react-native/remote-config.md +32 -0
  441. package/skills/mobile-firebase/references/react-native/sql-connect.md +11 -0
  442. package/skills/mobile-firebase/references/react-native/storage.md +30 -0
  443. package/skills/mobile-firebase/references/react-native.md +29 -0
  444. package/skills/mobile-test/SKILL.md +63 -0
  445. package/skills/mobile-test/references/android/setup/GUIDE.md +183 -0
  446. package/skills/mobile-test/references/android/setup/references/android/develop/ui/compose/testing/common-patterns.md +181 -0
  447. package/skills/mobile-test/references/android/setup/references/android/studio/preview/compose-screenshot-testing.md +255 -0
  448. package/skills/mobile-test/references/android/setup/references/android/training/dependency-injection/hilt-testing.md +447 -0
  449. package/skills/mobile-test/references/android.md +83 -0
  450. package/skills/{dart-collect-coverage/SKILL.md → mobile-test/references/flutter/coverage.md} +2 -11
  451. package/skills/{dart-generate-test-mocks/SKILL.md → mobile-test/references/flutter/generate-mocks.md} +2 -11
  452. package/skills/{flutter-add-integration-test/SKILL.md → mobile-test/references/flutter/integration-tests.md} +2 -11
  453. package/skills/{dart-migrate-to-checks-package/SKILL.md → mobile-test/references/flutter/migrate-to-checks.md} +2 -13
  454. package/skills/{mockito/SKILL.md → mobile-test/references/flutter/mockito.md} +1 -8
  455. package/skills/{mocktail/SKILL.md → mobile-test/references/flutter/mocktail.md} +1 -8
  456. package/skills/{patrol-e2e-testing/SKILL.md → mobile-test/references/flutter/patrol.md} +1 -8
  457. package/skills/{testing/SKILL.md → mobile-test/references/flutter/testing-guidelines.md} +1 -8
  458. package/skills/{dart-add-unit-test/SKILL.md → mobile-test/references/flutter/unit-tests.md} +2 -11
  459. package/skills/{flutter-add-widget-test/SKILL.md → mobile-test/references/flutter/widget-tests.md} +2 -11
  460. package/skills/mobile-test/references/flutter.md +44 -0
  461. package/skills/mobile-test/references/ios.md +79 -0
  462. package/skills/mobile-test/references/react-native.md +62 -0
  463. /package/skills/{dart-use-pattern-matching → flutter-dart-use-pattern-matching}/examples/json_patterns.dart +0 -0
@@ -0,0 +1,1313 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright 2026 The Android Open Source Project
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ """Coordinates the end-to-end Play Policy audit flow.
18
+
19
+ This manages everything from triage to analysis aggregation.
20
+ """
21
+
22
+ import argparse
23
+ import concurrent.futures
24
+ import glob
25
+ import json
26
+ import os
27
+ import re
28
+ import sys
29
+ import uuid
30
+ import xml.etree.ElementTree as ET
31
+
32
+ import scanner
33
+ from template_engine import render_template
34
+
35
+ # Constants for Gradle parsing
36
+ GRADLE_APP_ID_PATTERN = (
37
+ r'applicationId\s*(?:[:=]|\.set\(?)\s*[\'"]?([a-zA-Z0-9._]+)[\'"]?\)?'
38
+ )
39
+ GRADLE_APP_ID_PROP_PATTERN = (
40
+ r'applicationId\s*[:=]?\s*project\.property\(\s*[\'"]([^\'"]+)[\'"]\s*\)'
41
+ )
42
+ GRADLE_NAMESPACE_PATTERN = (
43
+ r'namespace\s*(?:[:=]|\.set\(?)\s*[\'"]?([a-zA-Z0-9._]+)[\'"]?\)?'
44
+ )
45
+ GRADLE_NAMESPACE_PROP_PATTERN = (
46
+ r'namespace\s*[:=]?\s*project\.property\(\s*[\'"]([^\'"]+)[\'"]\s*\)'
47
+ )
48
+ GRADLE_TARGET_SDK_PATTERN = (
49
+ r"targetSdk(?:Version)?(?:\.set\(?)?\s*[:="
50
+ r" (]*\s*([\'\"]?[\w\d_\.]+[\'\"]?)\)?"
51
+ )
52
+
53
+ # Well-known framework defaults for unresolved dynamic variables (e.g. Flutter)
54
+ FRAMEWORK_DEFAULTS = {
55
+ "flutter.targetSdkVersion": 35,
56
+ "flutter.compileSdkVersion": 35,
57
+ "flutter.minSdkVersion": 21,
58
+ "flutter.ndkVersion": "25.1.8937393",
59
+ }
60
+
61
+ # .NET MAUI / .NET for Android express the target SDK through the target framework
62
+ # moniker rather than a manifest or Gradle file. An explicit platform level wins
63
+ # (net9.0-android35.0 -> 35); an unqualified moniker (net9.0-android) resolves to
64
+ # the highest API the installed workload supports, which is fixed per .NET release.
65
+ DOTNET_ANDROID_TARGET_SDK = {
66
+ "8.0": 34,
67
+ "9.0": 35,
68
+ "10.0": 36,
69
+ }
70
+
71
+ # net10.0-android, net10.0-android36.0, net8.0-android34.0 ...
72
+ DOTNET_TFM_PATTERN = re.compile(
73
+ r"net(\d+\.\d+)-android(\d+)?(?:\.\d+)?", re.IGNORECASE
74
+ )
75
+
76
+ # Patterns for diverse assignment styles (Groovy & Kotlin DSL)
77
+ # 1. Standard: targetSdkVersion = 35, ext.targetSdkVersion = 35, etc.
78
+ # 2. Delegated: val targetSdkVersion by extra(35)
79
+ # 3. Functional: set("targetSdkVersion", 35) or
80
+ # extra.set("targetSdkVersion", 35)
81
+ # 4. Indexer: extra["targetSdkVersion"] = 35
82
+ GRADLE_ASSIGNMENT_PATTERNS = [
83
+ re.compile(
84
+ r"(?:\b(?:val|var|def|const|static|internal|private|"
85
+ r"public|protected|ext|final)\s+)*"
86
+ r"([\w\d_\.]+)(?:\s*:\s*[\w\d_\.]+)?\s*=\s*['\"]?"
87
+ r"([^\x27\"\s\(\)<>\{\}]+)['\"]?"
88
+ ),
89
+ re.compile(
90
+ r"val\s+([\w\d_\.]+)(?:\s*:\s*[\w\d_\.]+)?\s+by\s+"
91
+ r"extra\s*\(\s*['\"]?([^\x27\"\s\(\)]+)['\"]?\s*\)"
92
+ ),
93
+ re.compile(
94
+ r"(?:extra\.)?set\s*\(\s*['\"]([\w\d_\.]+)['\"]\s*,\s*"
95
+ r"['\"]?([^\x27\"\s\(\)]+)['\"]?\s*\)"
96
+ ),
97
+ re.compile(
98
+ r"extra\s*\[\s*['\"]([\w\d_\.]+)['\"]\s*\]\s*=\s*"
99
+ r"['\"]?([^\x27\"\s]+)['\"]?"
100
+ ),
101
+ ]
102
+
103
+
104
+ def _strip_inline_comment(line, support_quotes=True):
105
+ """Strips inline comments starting with '#' while preserving quotes."""
106
+ in_double = False
107
+ in_single = False
108
+ for i, char in enumerate(line):
109
+ if support_quotes:
110
+ if char == '"' and not in_single:
111
+ in_double = not in_double
112
+ elif char == "'" and not in_double:
113
+ in_single = not in_single
114
+
115
+ if char == "#" and not in_double and not in_single:
116
+ return line[:i]
117
+ return line
118
+
119
+
120
+ def load_gradle_properties(target_dir):
121
+ """Loads properties from gradle.properties and local.properties."""
122
+ properties = {}
123
+ for filename in ["gradle.properties", "local.properties"]:
124
+ path = os.path.join(target_dir, filename)
125
+ if os.path.exists(path):
126
+ try:
127
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
128
+ for line in f:
129
+ line = _strip_inline_comment(line, support_quotes=False).strip()
130
+ if line and not line.startswith("#") and "=" in line:
131
+ key, val = line.split("=", 1)
132
+ properties[key.strip()] = val.strip()
133
+ except Exception as e: # pylint: disable=broad-exception-caught
134
+ print(
135
+ f"Warning: Failed to load properties from {path}: {e}",
136
+ file=sys.stderr,
137
+ )
138
+ return properties
139
+
140
+
141
+ def load_version_catalog(target_dir):
142
+ """Loads versions from gradle/libs.versions.toml if it exists."""
143
+ catalog = {}
144
+ toml_path = os.path.join(target_dir, "gradle", "libs.versions.toml")
145
+ if os.path.exists(toml_path):
146
+ try:
147
+ with open(toml_path, "r", encoding="utf-8", errors="ignore") as f:
148
+ current_section = None
149
+ for line in f:
150
+ line = _strip_inline_comment(line).strip()
151
+ if not line or line.startswith("#"):
152
+ continue
153
+ if line.startswith("[") and line.endswith("]"):
154
+ current_section = line[1:-1].strip()
155
+ continue
156
+ if current_section == "versions" and "=" in line:
157
+ key, val = line.split("=", 1)
158
+ val = val.strip().strip('"').strip("'")
159
+ catalog[key.strip()] = val.strip()
160
+ except Exception as e: # pylint: disable=broad-exception-caught
161
+ print(
162
+ f"Warning: Failed to load version catalog from {toml_path}: {e}",
163
+ file=sys.stderr,
164
+ )
165
+ return catalog
166
+
167
+
168
+ def load_custom_gradle_versions(target_dir, file_inventory=None):
169
+ """Loads versions from shared Gradle files and project configuration."""
170
+ versions = {}
171
+ if not target_dir or not os.path.isdir(target_dir):
172
+ return versions
173
+
174
+ # 1. Identify Candidate Files (Root files are the primary targets)
175
+ candidate_files = []
176
+
177
+ # Leaking Strategy: If target_dir is a sub-module, leak up to find the root
178
+ # e.g. /path/to/project/app -> check /path/to/project for root build files
179
+ search_roots = [target_dir]
180
+ parent = os.path.dirname(os.path.normpath(target_dir))
181
+ if parent and parent != target_dir:
182
+ search_roots.append(parent)
183
+
184
+ for s_root in search_roots:
185
+ for f in ["build.gradle", "build.gradle.kts"]:
186
+ root_file = os.path.normpath(os.path.join(s_root, f))
187
+ if os.path.exists(root_file) and root_file not in candidate_files:
188
+ candidate_files.append(root_file)
189
+
190
+ # Use inventory if provided to avoid redundant file walk
191
+ if file_inventory:
192
+ search_lists = [
193
+ file_inventory.get("gradles", []),
194
+ file_inventory.get("source_code", []),
195
+ ]
196
+ for file_list in search_lists:
197
+ for path in file_list:
198
+ norm_path = os.path.normpath(path)
199
+ if norm_path not in candidate_files:
200
+ file_lower = os.path.basename(norm_path).lower()
201
+ if any(
202
+ name in file_lower
203
+ for name in [
204
+ "versions.gradle",
205
+ "dependencies.gradle",
206
+ "variables.gradle",
207
+ ]
208
+ ):
209
+ candidate_files.append(norm_path)
210
+ elif any(
211
+ d in norm_path.lower() for d in ["build-logic", "buildsrc"]
212
+ ) and file_lower.endswith((".gradle", ".gradle.kts", ".kt")):
213
+ candidate_files.append(norm_path)
214
+ else:
215
+ # Fallback to walk if no inventory provided
216
+ config = scanner.get_scanner_config()
217
+ ignored_dirs = set(config.get("ignored_directories", []))
218
+ for root, dirs, files in os.walk(target_dir):
219
+ dirs[:] = [d for d in dirs if d not in ignored_dirs]
220
+ for f in files:
221
+ if f.lower().endswith((".gradle", ".gradle.kts", ".kt")):
222
+ candidate_files.append(os.path.join(root, f))
223
+
224
+ # 2. Parse candidate files for assignments
225
+ for f_path in candidate_files:
226
+ try:
227
+ with open(f_path, "r", encoding="utf-8", errors="ignore") as f:
228
+ content = f.read()
229
+ for pattern in GRADLE_ASSIGNMENT_PATTERNS:
230
+ for key, val in pattern.findall(content):
231
+ # Clean up the key (remove 'ext.' or 'project.' prefixes)
232
+ clean_key = key.split(".")[-1]
233
+ if clean_key not in versions:
234
+ versions[clean_key] = val.strip().strip('"').strip("'")
235
+ except Exception as e: # pylint: disable=broad-exception-caught
236
+ print(
237
+ f"Warning: Failed to parse Gradle file {f_path}: {e}", file=sys.stderr
238
+ )
239
+
240
+ return versions
241
+
242
+
243
+ def _load_json_file(path):
244
+ """Safely loads a JSON file."""
245
+ if os.path.exists(path):
246
+ try:
247
+ with open(path, "r", encoding="utf-8") as f:
248
+ return json.load(f)
249
+ except Exception as e: # pylint: disable=broad-exception-caught
250
+ print(f"Warning: Failed to load JSON from {path}: {e}", file=sys.stderr)
251
+ return {}
252
+
253
+
254
+ def _read_text_file(path):
255
+ """Safely reads a text file."""
256
+ if os.path.exists(path):
257
+ try:
258
+ with open(path, "r", encoding="utf-8") as f:
259
+ return f.read()
260
+ except Exception as e: # pylint: disable=broad-exception-caught
261
+ print(f"Warning: Failed to read file {path}: {e}", file=sys.stderr)
262
+ return ""
263
+
264
+
265
+ def _write_json_file(path, data):
266
+ """Safely writes a JSON file."""
267
+ try:
268
+ with open(path, "w", encoding="utf-8") as f:
269
+ json.dump(data, f, indent=4, sort_keys=True)
270
+ except Exception as e: # pylint: disable=broad-exception-caught
271
+ print(f"Warning: Failed to write JSON to {path}: {e}", file=sys.stderr)
272
+
273
+
274
+ def _write_text_file(path, content):
275
+ """Safely writes a text file."""
276
+ try:
277
+ with open(path, "w", encoding="utf-8") as f:
278
+ f.write(content)
279
+ except Exception as e: # pylint: disable=broad-exception-caught
280
+ print(f"Warning: Failed to write file {path}: {e}", file=sys.stderr)
281
+
282
+
283
+ def _filter_by_flavor(data, detected_flavors, prioritized_flavors):
284
+ """Recursively filters lists and dicts based on flavor path segments."""
285
+ if isinstance(data, dict):
286
+ filtered_dict = {}
287
+ for k, v in data.items():
288
+ filtered_val = _filter_by_flavor(v, detected_flavors, prioritized_flavors)
289
+ if isinstance(filtered_val, (dict, list)) and not filtered_val:
290
+ continue
291
+ filtered_dict[k] = filtered_val
292
+ return filtered_dict
293
+
294
+ elif isinstance(data, list):
295
+ filtered_list = []
296
+ for entry in data:
297
+ # Handle tuple entries (like in codebase_map.all_files)
298
+ entry_str = (
299
+ entry[1]
300
+ if isinstance(entry, tuple) and len(entry) > 1
301
+ else str(entry)
302
+ )
303
+
304
+ is_excluded = any(
305
+ f not in prioritized_flavors and f"src/{f}/" in entry_str
306
+ for f in detected_flavors
307
+ )
308
+ if not is_excluded:
309
+ filtered_list.append(entry)
310
+ return filtered_list
311
+
312
+ return data # Fallback for primitive types
313
+
314
+
315
+ def parse_application_modules(
316
+ gradle_files,
317
+ manifest_files,
318
+ gradle_properties,
319
+ version_catalog,
320
+ custom_gradle_versions=None,
321
+ ):
322
+ """Identifies distinct application modules in the project."""
323
+ modules = []
324
+ lookups = {
325
+ "properties": gradle_properties,
326
+ "catalog": version_catalog,
327
+ "custom": custom_gradle_versions or {},
328
+ }
329
+
330
+ # First, check Gradle files for the application plugin
331
+ for g_file in gradle_files:
332
+ try:
333
+ with open(g_file, "r", encoding="utf-8", errors="ignore") as f:
334
+ content = f.read()
335
+ if re.search(GRADLE_APP_PLUGIN_PATTERN, content):
336
+ # Found an app module!
337
+ mod_dir = os.path.dirname(g_file)
338
+ mod_name = os.path.basename(mod_dir)
339
+
340
+ # Try to extract details from this Gradle file
341
+ app_id = None
342
+ target_sdk = None
343
+ app_label = None
344
+
345
+ id_match = re.search(GRADLE_APP_ID_PATTERN, content)
346
+ if id_match:
347
+ app_id = id_match.group(1)
348
+ else:
349
+ prop_match = re.search(GRADLE_APP_ID_PROP_PATTERN, content)
350
+ if prop_match:
351
+ app_id = lookups["properties"].get(prop_match.group(1))
352
+
353
+ sdk_match = re.search(GRADLE_TARGET_SDK_PATTERN, content)
354
+ if sdk_match:
355
+ target_sdk = _resolve_to_int(sdk_match.group(1), lookups)
356
+
357
+ # Fallback to namespace if applicationId is missing
358
+ if not app_id:
359
+ ns_match = re.search(GRADLE_NAMESPACE_PATTERN, content)
360
+ if ns_match:
361
+ app_id = ns_match.group(1)
362
+ else:
363
+ ns_prop_match = re.search(GRADLE_NAMESPACE_PROP_PATTERN, content)
364
+ if ns_prop_match:
365
+ app_id = lookups["properties"].get(ns_prop_match.group(1))
366
+
367
+ modules.append({
368
+ "name": mod_name,
369
+ "path": mod_dir,
370
+ "application_id": app_id,
371
+ "target_sdk": target_sdk,
372
+ "app_label": app_label,
373
+ })
374
+ except Exception as e: # pylint: disable=broad-exception-caught
375
+ print(
376
+ f"Warning: Failed to parse Gradle file {g_file}: {e}", file=sys.stderr
377
+ )
378
+
379
+ return modules
380
+
381
+
382
+ def determine_primary_identity(
383
+ app_modules,
384
+ gradle_files,
385
+ manifest_files,
386
+ gradle_properties,
387
+ version_catalog,
388
+ custom_gradle_versions,
389
+ app_dir,
390
+ ):
391
+ """Heuristically determines the primary app ID and target SDK."""
392
+ primary_id = None
393
+ primary_sdk = None
394
+ primary_label = None
395
+
396
+ # 1. Prefer explicitly defined app modules
397
+ for mod in app_modules:
398
+ if mod.get("application_id"):
399
+ primary_id = mod["application_id"]
400
+ primary_sdk = mod["target_sdk"]
401
+ primary_label = mod["app_label"]
402
+ break
403
+
404
+ # 2. Fallback to global Gradle properties
405
+ if not primary_id:
406
+ primary_id = gradle_properties.get("applicationId") or (
407
+ gradle_properties.get("namespace")
408
+ )
409
+ if not primary_sdk:
410
+ primary_sdk = _resolve_to_int(
411
+ gradle_properties.get("targetSdkVersion")
412
+ or gradle_properties.get("targetSdk"),
413
+ {"properties": gradle_properties, "catalog": version_catalog},
414
+ )
415
+
416
+ # 3. Fallback to scanning manifests if still missing
417
+ if not primary_id or not primary_sdk:
418
+ for m_file in manifest_files:
419
+ try:
420
+ with open(m_file, "r", encoding="utf-8-sig") as f:
421
+ content = f.read()
422
+ details = extract_manifest_details(content)
423
+ if not primary_id:
424
+ primary_id = details.get("package_name")
425
+ if not primary_sdk:
426
+ primary_sdk = details.get("target_sdk")
427
+ if not primary_label:
428
+ primary_label = details.get("app_label")
429
+ except Exception: # pylint: disable=broad-exception-caught
430
+ pass
431
+
432
+ # 4. Cross-Platform Framework Detection
433
+ if not primary_id or not primary_sdk:
434
+ # Check for Flutter (pubspec.yaml)
435
+ pubspec_path = os.path.join(app_dir, "pubspec.yaml")
436
+ if os.path.exists(pubspec_path):
437
+ try:
438
+ with open(pubspec_path, "r", encoding="utf-8") as f:
439
+ for line in f:
440
+ if line.startswith("name:"):
441
+ primary_id = line.split(":")[1].strip()
442
+ break
443
+ primary_sdk = FRAMEWORK_DEFAULTS.get("flutter.targetSdkVersion")
444
+ except Exception: # pylint: disable=broad-exception-caught
445
+ pass
446
+
447
+ # Check for React Native (package.json)
448
+ pkg_json_path = os.path.join(app_dir, "package.json")
449
+ if os.path.exists(pkg_json_path):
450
+ try:
451
+ with open(pkg_json_path, "r", encoding="utf-8") as f:
452
+ data = json.load(f)
453
+ primary_id = data.get("name")
454
+ except Exception: # pylint: disable=broad-exception-caught
455
+ pass
456
+
457
+ # Check for .NET MAUI / .NET for Android (*.csproj)
458
+ if not primary_id or not primary_sdk:
459
+ dotnet_id, dotnet_sdk, dotnet_label = extract_dotnet_android_details(
460
+ app_dir
461
+ )
462
+ primary_id = primary_id or dotnet_id
463
+ primary_sdk = primary_sdk or dotnet_sdk
464
+ primary_label = primary_label or dotnet_label
465
+
466
+ return primary_id, primary_sdk, primary_label
467
+
468
+
469
+ def _msbuild_property(content, tag):
470
+ """Reads a single MSBuild property value out of a project file."""
471
+ match = re.search(
472
+ rf"<{tag}[^>]*>([^<]+)</{tag}>", content, re.IGNORECASE
473
+ )
474
+ return match.group(1).strip() if match else None
475
+
476
+
477
+ def extract_dotnet_android_details(app_dir):
478
+ """Finds the .NET MAUI / .NET for Android project and reads its identity.
479
+
480
+ A .NET for Android app has no Gradle files, and its AndroidManifest.xml carries
481
+ neither the package name nor uses-sdk -- both are MSBuild properties merged into
482
+ the manifest at build time. Without this the audit sees an app with no id and no
483
+ target SDK and reports nothing, which reads as a clean bill of health.
484
+
485
+ Args:
486
+ app_dir: Absolute path to the directory containing the app's code.
487
+
488
+ Returns:
489
+ A tuple of (application_id, target_sdk, application_title). Any element is
490
+ None when it could not be determined.
491
+ """
492
+ app_id = None
493
+ target_sdk = None
494
+ label = None
495
+
496
+ config = scanner.get_scanner_config()
497
+ ignored_dirs = set(config.get("ignored_directories", []))
498
+
499
+ candidates = []
500
+ for root, dirs, files in os.walk(app_dir):
501
+ dirs[:] = [d for d in dirs if d not in ignored_dirs]
502
+ for f in files:
503
+ if f.lower().endswith(".csproj"):
504
+ candidates.append(os.path.join(root, f))
505
+
506
+ for csproj in sorted(candidates):
507
+ try:
508
+ with open(csproj, "r", encoding="utf-8", errors="ignore") as f:
509
+ content = f.read()
510
+ except Exception: # pylint: disable=broad-exception-caught
511
+ continue
512
+
513
+ content = re.sub(r"<!--.*?-->", "", content, flags=re.DOTALL)
514
+
515
+ tfm_match = DOTNET_TFM_PATTERN.search(content)
516
+ if not tfm_match:
517
+ # Not an Android head project (class libraries, test projects).
518
+ continue
519
+
520
+ app_id = app_id or _msbuild_property(content, "ApplicationId")
521
+ label = label or _msbuild_property(content, "ApplicationTitle")
522
+
523
+ if target_sdk is None:
524
+ # Explicit override wins, then the moniker's platform level, then the
525
+ # workload default for that .NET release.
526
+ explicit = _msbuild_property(content, "AndroidTargetSdkVersion")
527
+ if explicit and explicit.isdigit():
528
+ target_sdk = int(explicit)
529
+ elif tfm_match.group(2):
530
+ target_sdk = int(tfm_match.group(2))
531
+ else:
532
+ target_sdk = DOTNET_ANDROID_TARGET_SDK.get(tfm_match.group(1))
533
+
534
+ if app_id and target_sdk and label:
535
+ break
536
+
537
+ return app_id, target_sdk, label
538
+
539
+
540
+ def extract_manifest_details(xml_content):
541
+ """Parses AndroidManifest.xml for key policy signals."""
542
+ details = {
543
+ "package_name": None,
544
+ "target_sdk": None,
545
+ "app_label": None,
546
+ "permissions": [],
547
+ "foreground_services": [],
548
+ }
549
+
550
+ try:
551
+ # Strip a UTF-8 BOM before parsing. Visual Studio writes one into
552
+ # AndroidManifest.xml by default, and ElementTree rejects it as an invalid
553
+ # token at line 1. The permission scan below is regex-based and survives, so
554
+ # the failure is silent: the manifest still yields permissions while quietly
555
+ # losing the package name, target SDK and app label.
556
+ root = ET.fromstring(xml_content.lstrip("\ufeff"))
557
+ ns = {"android": "http://schemas.android.com/apk/res/android"}
558
+
559
+ details["package_name"] = root.get("package")
560
+
561
+ # Target SDK
562
+ uses_sdk = root.find("uses-sdk", ns)
563
+ if uses_sdk is not None:
564
+ details["target_sdk"] = uses_sdk.get(
565
+ "{http://schemas.android.com/apk/res/android}targetSdkVersion"
566
+ )
567
+
568
+ # App Label
569
+ application = root.find("application", ns)
570
+ if application is not None:
571
+ label = application.get(
572
+ "{http://schemas.android.com/apk/res/android}label"
573
+ )
574
+ if label and label.startswith("@string/"):
575
+ details["app_label"] = label[8:]
576
+ else:
577
+ details["app_label"] = label
578
+
579
+ # Permissions
580
+ for tag in ["uses-permission", "uses-permission-sdk-23"]:
581
+ for perm in root.findall(tag, ns):
582
+ name = perm.get("{http://schemas.android.com/apk/res/android}name")
583
+ if name:
584
+ details["permissions"].append(name)
585
+
586
+ # Permissions from component declarations
587
+ for tag in [
588
+ "service",
589
+ "receiver",
590
+ "activity",
591
+ "provider",
592
+ "activity-alias",
593
+ ]:
594
+ for component in root.findall(f".//{tag}", ns):
595
+ perm = component.get(
596
+ "{http://schemas.android.com/apk/res/android}permission"
597
+ )
598
+ if perm:
599
+ details["permissions"].append(perm)
600
+
601
+ if tag == "provider":
602
+ read_perm = component.get(
603
+ "{http://schemas.android.com/apk/res/android}readPermission"
604
+ )
605
+ if read_perm:
606
+ details["permissions"].append(read_perm)
607
+ write_perm = component.get(
608
+ "{http://schemas.android.com/apk/res/android}writePermission"
609
+ )
610
+ if write_perm:
611
+ details["permissions"].append(write_perm)
612
+
613
+ # Foreground Services
614
+ for service in root.findall(".//service", ns):
615
+ # Check for foregroundServiceType
616
+ fgs_type = service.get(
617
+ "{http://schemas.android.com/apk/res/android}foregroundServiceType"
618
+ )
619
+ if fgs_type:
620
+ details["foreground_services"].append({
621
+ "name": service.get(
622
+ "{http://schemas.android.com/apk/res/android}name"
623
+ ),
624
+ "type": fgs_type,
625
+ })
626
+ except Exception as e: # pylint: disable=broad-exception-caught
627
+ print(f"Warning: Failed to parse XML: {e}", file=sys.stderr)
628
+
629
+ return details
630
+
631
+
632
+ def generate_triage_summary(manifest_details, data_safety_scan):
633
+ """Generates a high-level summary of the audit landscape."""
634
+ summary = []
635
+
636
+ package = manifest_details.get("package_name") or "Unknown"
637
+ summary.append(f"Audit Target: {package}")
638
+
639
+ sdk = manifest_details.get("target_sdk") or "Unknown"
640
+ summary.append(f"Target SDK: {sdk}")
641
+
642
+ perms_count = len(manifest_details.get("permissions", []))
643
+ summary.append(f"Permissions Requested: {perms_count}")
644
+
645
+ signals_count = 0
646
+ if isinstance(data_safety_scan, dict):
647
+ inner_scan = data_safety_scan.get("data_safety_scan", {})
648
+ if "data_sources" in inner_scan:
649
+ signals_count = len(inner_scan["data_sources"])
650
+ summary.append(f"Sensitive Data Signals Found: {signals_count}")
651
+
652
+ return "\n".join(summary)
653
+
654
+
655
+ def run_scanner_direct(target_dir, file_inventory, output_file):
656
+ """Invokes the scanner directly using the provided inventory."""
657
+ try:
658
+ # This calls the internal scan logic from scanner.py
659
+ results = scanner.perform_scan(target_dir, file_inventory)
660
+ _write_json_file(output_file, results)
661
+ return results
662
+ except Exception as e: # pylint: disable=broad-exception-caught
663
+ print(f"Error during scan: {e}", file=sys.stderr)
664
+ return {}
665
+
666
+
667
+ def write_agent_prompts(env_data, goal_map, output_data):
668
+ """Generates surgical prompt files for worker agents and infers activation."""
669
+ repo_root = env_data.get("repo_root")
670
+ temp_dir = env_data.get("temp_dir")
671
+ app_dir = env_data.get("app_dir")
672
+ activated_goals = []
673
+
674
+ # 1. Load Shared Resources
675
+ policies_path = os.path.join(repo_root, "resources", "policies.json")
676
+ policies_data = _load_json_file(policies_path)
677
+ taxonomy = policies_data.get("data_safety_section", {}).get("taxonomy", {})
678
+ taxonomy_keys = set(taxonomy.keys())
679
+
680
+ common_mandates_path = os.path.join(
681
+ repo_root, "resources", "common_mandates.md"
682
+ )
683
+ common_mandates = _read_text_file(common_mandates_path)
684
+
685
+ config = scanner.get_scanner_config()
686
+ permission_groups = config.get("permission_groups", {})
687
+
688
+ # 2. Write Slices
689
+ if output_data:
690
+ for slice_name in ["manifest_details", "codebase_map"]:
691
+ slice_path = os.path.join(temp_dir, f"{slice_name}.json")
692
+ _write_json_file(slice_path, output_data.get(slice_name, {}))
693
+
694
+ # 3. Build Base Context
695
+ scan_results = output_data.get("data_safety_scan", {})
696
+ base_context = scan_results.get("data_safety_scan", {}).copy()
697
+
698
+ # Also ensure other metadata is included
699
+ base_context["found_urls"] = scan_results.get("found_urls", [])
700
+ base_context["semantic_files"] = scan_results.get("semantic_files", {})
701
+
702
+ # Flatten anonymous categories (those with only a "-" key)
703
+ for cat in [
704
+ "exact_alarm",
705
+ "accessibility",
706
+ "foreground_service",
707
+ "disclosure",
708
+ ]:
709
+ if (
710
+ cat in base_context
711
+ and isinstance(base_context[cat], dict)
712
+ and "-" in base_context[cat]
713
+ ):
714
+ base_context[cat] = base_context[cat]["-"]
715
+
716
+ manifest_details = output_data.get("manifest_details", {})
717
+ base_context["TARGET_SDK"] = manifest_details.get("target_sdk")
718
+ base_context["APP_NAME"] = output_data.get("app_name")
719
+ base_context["PACKAGE_NAME"] = output_data.get("package_name")
720
+ manifest_perms = manifest_details.get("permissions", [])
721
+
722
+ # Populate requested_permissions dictionary based on groups
723
+ requested_perms_dict = {}
724
+ for group_name, perms_to_check in permission_groups.items():
725
+ matched = [p for p in perms_to_check if p in manifest_perms]
726
+ if matched:
727
+ requested_perms_dict[group_name] = matched
728
+
729
+ base_context["requested_permissions"] = requested_perms_dict
730
+ base_context["TEMP_DIR"] = temp_dir
731
+ base_context["APP_DIR"] = app_dir
732
+ base_context["REPO_ROOT"] = repo_root
733
+
734
+ # 4. Smart Filtering for data_sources (Noise Reduction)
735
+ if "data_sources" in base_context:
736
+ filtered_sources = {}
737
+ for data_type, findings in base_context["data_sources"].items():
738
+ # Deduplicate by File (max 2 per unique file)
739
+ file_counts = {}
740
+ diverse_findings = []
741
+ for f in findings:
742
+ file_path = f.split(":L")[0] if ":L" in f else f
743
+ if file_counts.get(file_path, 0) < 2:
744
+ diverse_findings.append(f)
745
+ file_counts[file_path] = file_counts.get(file_path, 0) + 1
746
+
747
+ # Global Cap (max 3 per data type)
748
+ filtered_sources[data_type] = diverse_findings[:3]
749
+ base_context["data_sources"] = filtered_sources
750
+
751
+ max_evidence_per_worker = 6
752
+
753
+ # Helper to evaluate and save a goal instance
754
+ def _save_goal_instance(instance_name, context, template_content):
755
+ full_template = template_content + "\n\n" + common_mandates
756
+ final_prompt = render_template(full_template, context)
757
+
758
+ if "{{ACTIVATE_GOAL}}" in final_prompt:
759
+ final_prompt = final_prompt.replace("{{ACTIVATE_GOAL}}", "")
760
+
761
+ goal_input_file = os.path.join(
762
+ temp_dir, f"input_worker_{instance_name}.json"
763
+ )
764
+ _write_json_file(goal_input_file, context)
765
+
766
+ output_path = os.path.join(temp_dir, f"prompt_worker_{instance_name}.md")
767
+ _write_text_file(output_path, final_prompt)
768
+ return True
769
+ return False
770
+
771
+ # 5. Process Goals Sequentially (Removes Concurrency GIL Overhead & State
772
+ # Copies)
773
+ for goal_name, goal_conf in goal_map.items():
774
+ template_path = os.path.join(
775
+ repo_root, "resources", goal_conf["prompt_file"]
776
+ )
777
+ template_content = _read_text_file(template_path)
778
+ if not template_content:
779
+ continue
780
+
781
+ # Identify chunks for this goal
782
+ chunks = []
783
+
784
+ if goal_name == "data_safety" and "data_sources" in base_context:
785
+ all_evidence = []
786
+ for data_type, findings in base_context["data_sources"].items():
787
+ if not taxonomy_keys or data_type in taxonomy_keys:
788
+ for finding in findings:
789
+ all_evidence.append((data_type, finding))
790
+
791
+ for i in range(0, len(all_evidence), max_evidence_per_worker):
792
+ chunk = all_evidence[i : i + max_evidence_per_worker]
793
+ chunk_dict = {}
794
+ for dt, finding in chunk:
795
+ if dt not in chunk_dict:
796
+ chunk_dict[dt] = {
797
+ "description": taxonomy.get(dt, {}).get(
798
+ "description", "User data type."
799
+ ),
800
+ "findings": [],
801
+ }
802
+ chunk_dict[dt]["findings"].append(finding)
803
+ chunks.append(chunk_dict)
804
+
805
+ if not chunks:
806
+ # Single pass for small goals
807
+ if _save_goal_instance(goal_name, base_context, template_content):
808
+ activated_goals.append(goal_name)
809
+ else:
810
+ # Multipass for large data safety sets
811
+ for idx, chunk_data in enumerate(chunks, 1):
812
+ instance_name = f"{goal_name}_part_{idx}"
813
+ chunk_context = base_context.copy()
814
+ chunk_context["data_sources"] = chunk_data
815
+ chunk_context["GOAL_NAME"] = instance_name
816
+ if _save_goal_instance(instance_name, chunk_context, template_content):
817
+ activated_goals.append(instance_name)
818
+
819
+ return activated_goals
820
+
821
+
822
+ def run_aggregation(temp_dir, repo_root):
823
+ """Aggregates and chunks findings for the Critic."""
824
+ worker_pattern = os.path.join(temp_dir, "worker_*.json")
825
+ worker_files = sorted(glob.glob(worker_pattern))
826
+
827
+ all_findings = []
828
+ finding_id_counter = 1
829
+
830
+ for w_file in worker_files:
831
+ basename = os.path.basename(w_file)
832
+ try:
833
+ with open(w_file, "r", encoding="utf-8") as f:
834
+ w_data = json.load(f)
835
+ findings_list = w_data.get("findings", [])
836
+ if isinstance(findings_list, list):
837
+ for finding in findings_list:
838
+ # Store the source filename so generate_report can associate it
839
+ # correctly
840
+ finding["worker_file"] = basename
841
+ # Assign sequential ID
842
+ finding["finding_id"] = str(finding_id_counter)
843
+ finding_id_counter += 1
844
+ all_findings.append(finding)
845
+ except Exception as e: # pylint: disable=broad-exception-caught
846
+ print(
847
+ f"Warning: Failed to load worker file {w_file}: {e}", file=sys.stderr
848
+ )
849
+
850
+ # Save the master list of all findings
851
+ aggregated_path = os.path.join(temp_dir, "aggregated_findings.json")
852
+ with open(aggregated_path, "w", encoding="utf-8") as f:
853
+ json.dump({"findings": all_findings}, f, indent=4)
854
+
855
+ # Filter findings that need Critic evaluation: skip only SUGGESTION findings
856
+ critic_findings = []
857
+ for f in all_findings:
858
+ severity = str(f.get("severity", "SUGGESTION")).upper().strip()
859
+ if severity == "SUGGESTION":
860
+ continue
861
+ critic_findings.append(f)
862
+
863
+ # Chunk the findings for the Critic
864
+ chunk_size = 3
865
+ chunks = [
866
+ critic_findings[i : i + chunk_size]
867
+ for i in range(0, len(critic_findings), chunk_size)
868
+ ]
869
+
870
+ # Render Critic template for each chunk
871
+ critic_template_path = os.path.join(repo_root, "resources", "critic.md")
872
+ critic_template = ""
873
+ if os.path.exists(critic_template_path):
874
+ with open(critic_template_path, "r", encoding="utf-8") as f:
875
+ critic_template = f.read()
876
+
877
+ common_mandates_path = os.path.join(
878
+ repo_root, "resources", "common_mandates.md"
879
+ )
880
+ common_mandates = ""
881
+ if os.path.exists(common_mandates_path):
882
+ with open(common_mandates_path, "r", encoding="utf-8") as f:
883
+ common_mandates = f.read()
884
+
885
+ full_template = critic_template + "\n\n" + common_mandates
886
+
887
+ # Load contextual values from manifest_details.json
888
+ manifest_path = os.path.join(temp_dir, "manifest_details.json")
889
+ base_context = {}
890
+ if os.path.exists(manifest_path):
891
+ try:
892
+ with open(manifest_path, "r", encoding="utf-8", errors="ignore") as f:
893
+ m_details = json.load(f)
894
+ base_context["APP_DIR"] = m_details.get("app_dir")
895
+ except Exception: # pylint: disable=broad-exception-caught
896
+ pass
897
+
898
+ if not repo_root:
899
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
900
+
901
+ base_context["TEMP_DIR"] = temp_dir
902
+ base_context["REPO_ROOT"] = repo_root
903
+
904
+ for idx, chunk in enumerate(chunks, 1):
905
+ # Create input_critic_<idx>.json
906
+ chunk_dict = {f["finding_id"]: f for f in chunk}
907
+ chunk_input_path = os.path.join(temp_dir, f"input_critic_{idx}.json")
908
+ with open(chunk_input_path, "w", encoding="utf-8") as f:
909
+ json.dump(chunk_dict, f, indent=4)
910
+
911
+ # Render prompt_critic_<idx>.md
912
+ chunk_context = base_context.copy()
913
+ chunk_context["CHUNK_INDEX"] = str(idx)
914
+
915
+ final_prompt = render_template(full_template, chunk_context)
916
+ chunk_prompt_path = os.path.join(temp_dir, f"prompt_critic_{idx}.md")
917
+ with open(chunk_prompt_path, "w", encoding="utf-8") as f:
918
+ f.write(final_prompt)
919
+
920
+ return len(chunks)
921
+
922
+
923
+ GRADLE_APP_PLUGIN_PATTERN = (
924
+ r"(?:id\s*\(?\s*[\x27\x22]com\.android\.application[\x27\x22]\s*\)?|"
925
+ r"apply\s*\(?\s*(?:plugin:\s*)?[\x27\x22]com\.android\.application"
926
+ r"[\x27\x22]\s*\)?)(?!\s+apply\s+false)"
927
+ )
928
+
929
+
930
+ def _resolve_to_int(val, lookups, visited=None):
931
+ """Recursively resolves a variable name to an integer value."""
932
+ if visited is None:
933
+ visited = set()
934
+
935
+ if not val:
936
+ return None
937
+
938
+ # Clean and normalize the value
939
+ val_str = str(val).strip().strip('"').strip("'")
940
+
941
+ # Support project.property("key") syntax
942
+ prop_match = re.search(
943
+ r'project\.property\s*\(\s*[\'"](.*?)[\'"]\s*\)', val_str
944
+ )
945
+ if prop_match:
946
+ val_str = prop_match.group(1).strip()
947
+
948
+ # Robust resolution for Gradle Version Catalogs and dynamic getters
949
+ # Strip common accessors and indirection prefixes
950
+ val_str = re.sub(
951
+ r"\.(?:get|toInt|getOrElse|provider)(?:\s*\([^)]*\))?", "", val_str
952
+ )
953
+ if val_str.startswith("libs.versions."):
954
+ val_str = val_str[14:]
955
+ elif val_str.startswith("libs."):
956
+ val_str = val_str[5:]
957
+ elif val_str.startswith("versions."):
958
+ val_str = val_str[9:]
959
+
960
+ if val_str.isdigit():
961
+ return int(val_str)
962
+
963
+ if val_str in visited:
964
+ return None # Circular reference protection
965
+ visited.add(val_str)
966
+
967
+ # 1. Direct and Namespaced Lookups
968
+ search_keys = [val_str]
969
+ if "." in val_str:
970
+ # If A.B.C fails, try B.C, then C
971
+ parts = val_str.split(".")
972
+ for i in range(1, len(parts)):
973
+ search_keys.append(".".join(parts[i:]))
974
+
975
+ for key in search_keys:
976
+ # Try custom versions first (usually has highest signal)
977
+ if key in lookups.get("custom", {}):
978
+ res = _resolve_to_int(lookups["custom"][key], lookups, visited)
979
+ if res is not None:
980
+ return res
981
+
982
+ # Try gradle properties
983
+ if key in lookups.get("properties", {}):
984
+ res = _resolve_to_int(lookups["properties"][key], lookups, visited)
985
+ if res is not None:
986
+ return res
987
+
988
+ # Try version catalog
989
+ if key in lookups.get("catalog", {}):
990
+ res = _resolve_to_int(lookups["catalog"][key], lookups, visited)
991
+ if res is not None:
992
+ return res
993
+
994
+ return None
995
+
996
+
997
+ def main():
998
+ """Deterministic context-gathering tool for the Reviewer skill."""
999
+ parser = argparse.ArgumentParser(
1000
+ description="Deterministic context-gathering and state-query tool."
1001
+ )
1002
+ subparsers = parser.add_subparsers(dest="command", required=True)
1003
+
1004
+ # Init subcommand
1005
+ init_parser = subparsers.add_parser(
1006
+ "init", help="Initialize scratch environment."
1007
+ )
1008
+ init_parser.add_argument("app_dir", help="Path to the Android application.")
1009
+
1010
+ # Aggregate subcommand
1011
+ agg_parser = subparsers.add_parser("aggregate", help="Aggregate findings.")
1012
+ agg_parser.add_argument(
1013
+ "temp_dir", help="Path to the temporary scratch directory."
1014
+ )
1015
+
1016
+ args = parser.parse_args()
1017
+
1018
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
1019
+
1020
+ if args.command == "aggregate":
1021
+ temp_dir = os.path.abspath(args.temp_dir)
1022
+ if not os.path.isdir(temp_dir):
1023
+ print(
1024
+ f"Error: Specified temporary directory does not exist: {temp_dir}",
1025
+ file=sys.stderr,
1026
+ )
1027
+ sys.exit(1)
1028
+
1029
+ chunk_count = run_aggregation(temp_dir, repo_root)
1030
+ print(json.dumps({"temp_dir": temp_dir, "critic_chunks": chunk_count}))
1031
+ sys.exit(0)
1032
+
1033
+ # Initialization Mode
1034
+ app_dir = os.path.abspath(args.app_dir)
1035
+ if not os.path.isdir(app_dir):
1036
+ print(f"Error: {app_dir} is not a valid directory.", file=sys.stderr)
1037
+ sys.exit(1)
1038
+
1039
+ scratch_id = str(uuid.uuid4())
1040
+ workspace_root = os.getcwd()
1041
+ temp_dir = os.path.join(
1042
+ workspace_root, ".scratch", f"play_policy_insights_{scratch_id}"
1043
+ )
1044
+ os.makedirs(temp_dir, exist_ok=True)
1045
+
1046
+ # Perform a single master filesystem walk using the scanner
1047
+ file_inventory = scanner.collect_target_files(app_dir)
1048
+
1049
+ gradle_properties = load_gradle_properties(app_dir)
1050
+ version_catalog = load_version_catalog(app_dir)
1051
+ # Pass inventory to avoid redundant walk
1052
+ custom_gradle_versions = load_custom_gradle_versions(app_dir, file_inventory)
1053
+
1054
+ codebase_map = {
1055
+ "key_files": {
1056
+ "manifests": file_inventory["manifests"],
1057
+ "gradles": [
1058
+ g
1059
+ for g in file_inventory["gradles"]
1060
+ if g.endswith((".gradle", ".gradle.kts"))
1061
+ ],
1062
+ }
1063
+ }
1064
+
1065
+ # 1. Detect and Filter Flavors (Play Store Prioritization)
1066
+ detected_flavors = set()
1067
+ src_dirs = []
1068
+
1069
+ config = scanner.get_scanner_config()
1070
+ ignored_dirs = set(config.get("ignored_directories", []))
1071
+
1072
+ # Find all 'src' directories
1073
+ for root, dirs, _ in os.walk(app_dir):
1074
+ dirs[:] = [d for d in dirs if d not in ignored_dirs]
1075
+ if "src" in dirs:
1076
+ src_dirs.append(os.path.join(root, "src"))
1077
+ dirs.remove("src") # Don't recurse deeper for 'src' search
1078
+
1079
+ for s_dir in src_dirs:
1080
+ for flavor in os.listdir(s_dir):
1081
+ if (
1082
+ os.path.isdir(os.path.join(s_dir, flavor))
1083
+ and flavor not in ignored_dirs
1084
+ ):
1085
+ detected_flavors.add(flavor)
1086
+
1087
+ prioritized_flavors = []
1088
+ if "play" in detected_flavors:
1089
+ prioritized_flavors = ["main", "play"]
1090
+
1091
+ if prioritized_flavors:
1092
+ # Filter codebase_map to only include prioritized flavors
1093
+ print(
1094
+ f"Prioritizing flavors: {', '.join(prioritized_flavors)}",
1095
+ file=sys.stderr,
1096
+ )
1097
+
1098
+ filtered_files = {}
1099
+ for key, paths in codebase_map.get("key_files", {}).items():
1100
+ filtered_paths = []
1101
+ for p in paths:
1102
+ # Check if path contains any non-prioritized flavor
1103
+ is_excluded = False
1104
+ for f in detected_flavors:
1105
+ if f not in prioritized_flavors and f"src/{f}/" in p:
1106
+ is_excluded = True
1107
+ break
1108
+ if not is_excluded:
1109
+ filtered_paths.append(p)
1110
+ filtered_files[key] = filtered_paths
1111
+ codebase_map["key_files"] = filtered_files
1112
+
1113
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
1114
+ # 1. Start the Data Safety Scanner immediately (independent task)
1115
+ data_safety_scan_file = os.path.join(temp_dir, "data_safety_scan.json")
1116
+ scanner_future = executor.submit(
1117
+ run_scanner_direct, app_dir, file_inventory, data_safety_scan_file
1118
+ )
1119
+
1120
+ # 2. Extract Application Modules and Primary Identity
1121
+ app_modules = parse_application_modules(
1122
+ codebase_map.get("key_files", {}).get("gradles", []),
1123
+ codebase_map.get("key_files", {}).get("manifests", []),
1124
+ gradle_properties,
1125
+ version_catalog,
1126
+ custom_gradle_versions=custom_gradle_versions,
1127
+ )
1128
+ primary_app_id, primary_target_sdk, primary_app_label = (
1129
+ determine_primary_identity(
1130
+ app_modules,
1131
+ codebase_map.get("key_files", {}).get("gradles", []),
1132
+ codebase_map.get("key_files", {}).get("manifests", []),
1133
+ gradle_properties,
1134
+ version_catalog,
1135
+ custom_gradle_versions,
1136
+ app_dir,
1137
+ )
1138
+ )
1139
+
1140
+ manifest_content = ""
1141
+ manifest_details = {
1142
+ "package_name": primary_app_id,
1143
+ "app_label": primary_app_label,
1144
+ "target_sdk": primary_target_sdk,
1145
+ "app_dir": app_dir,
1146
+ "permissions": [],
1147
+ "foreground_services": [],
1148
+ }
1149
+
1150
+ if codebase_map.get("key_files", {}).get("manifests"):
1151
+ for _, manifest_path in enumerate(codebase_map["key_files"]["manifests"]):
1152
+ # Filter Test-Bleed based on relative path
1153
+ rel_path = os.path.relpath(manifest_path, app_dir)
1154
+ normalized_rel_path = "/" + rel_path.replace("\\", "/")
1155
+ if any(
1156
+ x in normalized_rel_path
1157
+ for x in [
1158
+ "/debug/",
1159
+ "/test/",
1160
+ "/androidTest/",
1161
+ "/testFixtures/",
1162
+ ]
1163
+ ):
1164
+ continue
1165
+
1166
+ if os.path.exists(manifest_path):
1167
+ try:
1168
+ # Manifests are UTF-8. Without an explicit encoding this falls back
1169
+ # to the platform default, which on Windows is the locale codepage:
1170
+ # the file arrives mojibake and every downstream parse fails.
1171
+ with open(manifest_path, "r", encoding="utf-8-sig") as f:
1172
+ content = f.read()
1173
+ if not manifest_content:
1174
+ manifest_content = content
1175
+
1176
+ details = extract_manifest_details(content)
1177
+
1178
+ # Identity Fallbacks
1179
+ if details.get("package_name") and not manifest_details.get(
1180
+ "package_name"
1181
+ ):
1182
+ manifest_details["package_name"] = details["package_name"]
1183
+
1184
+ if details.get("target_sdk") and not manifest_details.get(
1185
+ "target_sdk"
1186
+ ):
1187
+ manifest_details["target_sdk"] = details["target_sdk"]
1188
+
1189
+ if details.get("app_label") and not manifest_details.get(
1190
+ "app_label"
1191
+ ):
1192
+ manifest_details["app_label"] = details["app_label"]
1193
+
1194
+ manifest_details["permissions"].extend(
1195
+ details.get("permissions", [])
1196
+ )
1197
+ manifest_details["foreground_services"].extend(
1198
+ details.get("foreground_services", [])
1199
+ )
1200
+ except Exception as e: # pylint: disable=broad-exception-caught
1201
+ print(f"Warning: Failed to read manifest: {e}", file=sys.stderr)
1202
+
1203
+ manifest_details["permissions"] = sorted(
1204
+ list(set(manifest_details["permissions"]))
1205
+ )
1206
+
1207
+ # Deduplicate and sort foreground services
1208
+ unique_services = []
1209
+ seen_services = set()
1210
+ for svc in manifest_details["foreground_services"]:
1211
+ svc_key = (svc.get("name"), svc.get("type"))
1212
+ if svc_key not in seen_services:
1213
+ seen_services.add(svc_key)
1214
+ unique_services.append(svc)
1215
+
1216
+ # Final stable sort
1217
+ manifest_details["foreground_services"] = sorted(
1218
+ unique_services,
1219
+ key=lambda x: (x.get("name") or "", x.get("type") or ""),
1220
+ )
1221
+
1222
+ # 3. Gather results
1223
+ data_safety_scan = scanner_future.result()
1224
+
1225
+ # 5. Post-Scan Flavor Filtering
1226
+ if prioritized_flavors:
1227
+ data_safety_scan = _filter_by_flavor(
1228
+ data_safety_scan, detected_flavors, prioritized_flavors
1229
+ )
1230
+
1231
+ # 6. Ingest local Play Store info
1232
+ local_info_path = os.path.join(
1233
+ app_dir, "play_store_assets", "play_store_info.json"
1234
+ )
1235
+ if os.path.exists(local_info_path):
1236
+ try:
1237
+ with open(local_info_path, "r", encoding="utf-8") as f:
1238
+ play_store_info = json.load(f)
1239
+ if "is_published" not in play_store_info:
1240
+ play_store_info["is_published"] = True
1241
+ if "store_url" not in play_store_info:
1242
+ play_store_info["store_url"] = f"file://{local_info_path}"
1243
+
1244
+ with open(
1245
+ os.path.join(temp_dir, "play_store_info.json"),
1246
+ "w",
1247
+ encoding="utf-8",
1248
+ ) as out_f:
1249
+ json.dump(play_store_info, out_f, indent=4)
1250
+ print(
1251
+ f"Ingested local Play Store info from {local_info_path}",
1252
+ file=sys.stderr,
1253
+ )
1254
+ except Exception as e: # pylint: disable=broad-exception-caught
1255
+ print(
1256
+ f"Warning: Failed to ingest local Play Store info: {e}",
1257
+ file=sys.stderr,
1258
+ )
1259
+
1260
+ reasoning = []
1261
+ goal_map = {}
1262
+ resources_dir = os.path.join(repo_root, "resources")
1263
+ if os.path.exists(resources_dir):
1264
+ for f in os.listdir(resources_dir):
1265
+ if f.startswith("goal_") and f.endswith(".md"):
1266
+ goal_name = f[5:-3]
1267
+ goal_map[goal_name] = {"prompt_file": f}
1268
+
1269
+ if prioritized_flavors:
1270
+ reasoning.append(
1271
+ f"Prioritizing Play Store flavors: {', '.join(prioritized_flavors)}"
1272
+ )
1273
+ reasoning.append("Activated via template evaluation")
1274
+
1275
+ output_data = {
1276
+ "app_name": (
1277
+ manifest_details.get("app_label")
1278
+ or os.path.basename(os.path.normpath(app_dir))
1279
+ ),
1280
+ "package_name": manifest_details.get("package_name"),
1281
+ "target_dir": app_dir,
1282
+ "codebase_map": codebase_map,
1283
+ "manifest_content": manifest_content,
1284
+ "manifest_details": manifest_details,
1285
+ "application_modules": app_modules,
1286
+ "data_safety_scan": data_safety_scan,
1287
+ "triage_summary": generate_triage_summary(
1288
+ manifest_details, data_safety_scan
1289
+ ),
1290
+ "triage_reasoning": "\n".join(reasoning),
1291
+ "detected_flavors": sorted(list(detected_flavors)),
1292
+ "prioritized_flavors": prioritized_flavors,
1293
+ }
1294
+
1295
+ try:
1296
+ activated_goals = write_agent_prompts(
1297
+ {"repo_root": repo_root, "temp_dir": temp_dir, "app_dir": app_dir},
1298
+ goal_map,
1299
+ output_data,
1300
+ )
1301
+ print(f"Prompts generated in {temp_dir}", file=sys.stderr)
1302
+ except Exception as e: # pylint: disable=broad-exception-caught
1303
+ print(f"Warning: Failed to generate prompts: {e}", file=sys.stderr)
1304
+
1305
+ print(
1306
+ json.dumps(
1307
+ {"temp_dir": temp_dir, "activated_goals": activated_goals}, indent=4
1308
+ )
1309
+ )
1310
+
1311
+
1312
+ if __name__ == "__main__":
1313
+ main()