android-app — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited android-app (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an autonomous Android app scaffolding agent. You generate a complete, production-ready native Android project with modern Jetpack architecture, clean layer separation, and best practices. Do NOT ask the user questions unless the requirements are fundamentally ambiguous.
INPUT: $ARGUMENTS The user will describe the app they want to build. If no arguments are provided, scaffold a starter template with all architecture layers wired up.
============================================================ PHASE 1: REQUIREMENTS ANALYSIS ============================================================
============================================================ PHASE 2: PROJECT STRUCTURE ============================================================
Generate the Android project with this package structure:
app/
src/
main/
java/com/example/appname/
AppNameApplication.kt # Application class with Hilt
MainActivity.kt # Single Activity host
di/
AppModule.kt # Hilt @Module for app-scoped deps
NetworkModule.kt # Hilt @Module for Retrofit, OkHttp
DatabaseModule.kt # Hilt @Module for Room
data/
local/
AppDatabase.kt # Room database
dao/
[Entity]Dao.kt # Room DAOs per entity
entity/
[Entity]Entity.kt # Room entities
remote/
api/
[Feature]Api.kt # Retrofit interface per feature
dto/
[Model]Dto.kt # Network DTOs with serialization
interceptor/
AuthInterceptor.kt # Token attachment
LoggingInterceptor.kt # Request/response logging
repository/
[Feature]Repository.kt # Repository implementations
[Feature]RepositoryImpl.kt
domain/
model/
[DomainModel].kt # Domain models (clean, no annotations)
usecase/
[Action]UseCase.kt # Use cases for business logic
repository/
[Feature]Repository.kt # Repository interfaces
presentation/
navigation/
AppNavigation.kt # NavHost with route definitions
Screen.kt # Sealed class of screen routes
theme/
Theme.kt # Material 3 theme
Color.kt # Color tokens
Type.kt # Typography tokens
Spacing.kt # Spacing dimension tokens
common/
LoadingScreen.kt # Reusable loading composable
ErrorScreen.kt # Reusable error with retry
EmptyStateScreen.kt # Reusable empty state
[feature]/
[Feature]Screen.kt # Composable screen
[Feature]ViewModel.kt # ViewModel with StateFlow
[Feature]UiState.kt # UI state sealed class
components/
[Widget].kt # Feature-specific composables
service/
push/
PushNotificationService.kt # FirebaseMessagingService
NotificationChannels.kt # Channel definitions
auth/
AuthManager.kt # Token storage, biometric auth
EncryptedPrefs.kt # EncryptedSharedPreferences wrapper
util/
Extensions.kt # Kotlin extension functions
NetworkMonitor.kt # ConnectivityManager observer
Constants.kt # App-wide string constants
res/
values/
strings.xml
themes.xml
dimens.xml
values-night/
themes.xml
debug/
AndroidManifest.xml # Debug-specific permissions
staging/
res/values/strings.xml # Staging app name override
release/
res/values/strings.xml # Release app name
test/
java/com/example/appname/
[Feature]ViewModelTest.kt
[Feature]RepositoryTest.kt
[UseCase]Test.kt
androidTest/
java/com/example/appname/
[Feature]ScreenTest.kt
build.gradle.kts # App-level build config
proguard-rules.pro # ProGuard/R8 rules
build.gradle.kts # Project-level build config
settings.gradle.kts # Project settings
gradle/
libs.versions.toml # Version catalog============================================================ PHASE 3: CORE INFRASTRUCTURE ============================================================
DEPENDENCY INJECTION (Hilt):
@HiltAndroidApp
class AppNameApplication : Application()
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides @Singleton
fun provideOkHttpClient(authInterceptor: AuthInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides @Singleton
fun provideRetrofit(okHttpClient: OkHttpClient, @BaseUrl baseUrl: String): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
}
}Every ViewModel is @HiltViewModel with @Inject constructor. Every Repository binds interface to implementation via @Binds in a Hilt module.
NETWORKING LAYER (Retrofit + OkHttp):
interface ItemsApi {
@GET("api/v1/items")
suspend fun getItems(@Query("page") page: Int, @Query("limit") limit: Int): ItemsResponse
@GET("api/v1/items/{id}")
suspend fun getItem(@Path("id") id: String): ItemResponse
@POST("api/v1/items")
suspend fun createItem(@Body dto: CreateItemDto): ItemResponse
}PERSISTENCE LAYER (Room):
@Database(entities = [ItemEntity::class], version = 1, exportSchema = true)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
}
@Dao
interface ItemDao {
@Query("SELECT * FROM items ORDER BY created_at DESC")
fun observeItems(): Flow<List<ItemEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(items: List<ItemEntity>)
@Delete
suspend fun delete(item: ItemEntity)
}Repository implements offline-first pattern:
ENCRYPTED STORAGE:
class EncryptedPrefs @Inject constructor(@ApplicationContext context: Context) {
private val prefs = EncryptedSharedPreferences.create(
context, "secure_prefs",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
fun saveToken(token: String) { prefs.edit().putString("auth_token", token).apply() }
fun getToken(): String? = prefs.getString("auth_token", null)
fun clearToken() { prefs.edit().remove("auth_token").apply() }
}============================================================ PHASE 4: UI ARCHITECTURE (JETPACK COMPOSE) ============================================================
NAVIGATION:
sealed class Screen(val route: String) {
data object Home : Screen("home")
data object Settings : Screen("settings")
data class ItemDetail(val id: String) : Screen("items/{id}") {
companion object { const val ROUTE = "items/{id}" }
}
}
@Composable
fun AppNavigation(navController: NavHostController = rememberNavController()) {
NavHost(navController = navController, startDestination = Screen.Home.route) {
composable(Screen.Home.route) { HomeScreen(navController) }
composable(
Screen.ItemDetail.ROUTE,
arguments = listOf(navArgument("id") { type = NavType.StringType })
) { ItemDetailScreen(navController) }
}
}THEMING (Material 3):
@Composable
fun AppNameTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) dynamicDarkColorScheme(LocalContext.current)
else dynamicLightColorScheme(LocalContext.current)
}
darkTheme -> darkColorScheme(/* custom colors */)
else -> lightColorScheme(/* custom colors */)
}
MaterialTheme(colorScheme = colorScheme, typography = AppTypography, content = content)
}Define spacing tokens as CompositionLocal:
data class Spacing(val xs: Dp = 4.dp, val sm: Dp = 8.dp, val md: Dp = 16.dp, val lg: Dp = 24.dp, val xl: Dp = 32.dp)
val LocalSpacing = staticCompositionLocalOf { Spacing() }STATE MANAGEMENT:
Every screen uses a sealed UiState:
sealed interface ItemsUiState {
data object Loading : ItemsUiState
data class Success(val items: List<Item>) : ItemsUiState
data class Error(val message: String) : ItemsUiState
data object Empty : ItemsUiState
}ViewModel exposes StateFlow<UiState> collected by the Composable.
============================================================ PHASE 5: PUSH NOTIFICATIONS ============================================================
NotificationChannels.kt:
PushNotificationService extends FirebaseMessagingService:
============================================================ PHASE 6: BUILD VARIANTS & GRADLE ============================================================
VERSION CATALOG (gradle/libs.versions.toml):
[versions]
kotlin = "2.0.0"
compose-bom = "2024.06.00"
hilt = "2.51"
room = "2.6.1"
retrofit = "2.11.0"
# ... all versions centralized
[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
# ... all dependencies
[plugins]
android-application = { id = "com.android.application", version = "8.5.0" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
ksp = { id = "com.google.devtools.ksp", version = "2.0.0-1.0.22" }BUILD VARIANTS (app/build.gradle.kts):
android {
buildTypes {
debug {
applicationIdSuffix = ".debug"
isDebuggable = true
buildConfigField("String", "API_BASE_URL", "\"https://api-dev.example.com\"")
}
create("staging") {
initWith(getByName("debug"))
applicationIdSuffix = ".staging"
buildConfigField("String", "API_BASE_URL", "\"https://api-staging.example.com\"")
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
buildConfigField("String", "API_BASE_URL", "\"https://api.example.com\"")
}
}
}PROGUARD / R8 RULES:
============================================================ PHASE 7: TESTING SETUP ============================================================
Unit tests (test/):
Instrumented tests (androidTest/):
Generate at least 3 unit tests and 1 UI test per major feature.
============================================================ SELF-HEALING VALIDATION (max 3 iterations) ============================================================
After completing the main phases, validate your work:
IF STILL FAILING after 3 iterations:
============================================================ OUTPUT ============================================================
{Tree listing of all generated files}
| Variant | App ID Suffix | API URL | Minify | Features |
|---|---|---|---|---|
| debug | .debug | api-dev.example.com | No | Verbose logging |
| staging | .staging | api-staging.example.com | No | Standard logging |
| release | (none) | api.example.com | Yes | Minimal logging, R8 |
| Library | Version | Purpose |
|---|---|---|
| {name} | {version} | {why} |
| Test File | Tests | Coverage Area |
|---|---|---|
| {file} | {count} | {what it tests} |
DO NOT:
NEXT STEPS:
/mobile-test to generate comprehensive unit, UI, and integration tests."/mobile-security-review to audit encrypted storage, certificate pinning, and obfuscation."/play-store-publish to set up signing, AAB, and Play Console publishing."/mobile-ci-cd to configure CI/CD with Gradle builds and Play Store distribution."============================================================ SELF-EVOLUTION TELEMETRY ============================================================
After producing output, record execution metadata for the /evolve pipeline.
Check if a project memory directory exists:
~/.claude/projects/skill-telemetry.md in that memory directoryEntry format:
### /android-app — {{YYYY-MM-DD}}
- Outcome: {{SUCCESS | PARTIAL | FAILED}}
- Self-healed: {{yes — what was healed | no}}
- Iterations used: {{N}} / {{N max}}
- Bottleneck: {{phase that struggled or "none"}}
- Suggestion: {{one-line improvement idea for /evolve, or "none"}}Only log if the memory directory exists. Skip silently if not found. Keep entries concise — /evolve will parse these for skill improvement signals.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.