mobile-ai-agents 1.0.23
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.
- package/AGENTS.md +228 -0
- package/CLAUDE.md +236 -0
- package/LICENSE +21 -0
- package/README.md +652 -0
- package/agents/android/android-crash-analyzer/README.md +134 -0
- package/agents/android/android-crash-analyzer/agent.md +206 -0
- package/agents/android/android-crash-analyzer/example-input.md +138 -0
- package/agents/android/android-crash-analyzer/example-output.md +225 -0
- package/agents/android/anr-investigation/README.md +78 -0
- package/agents/android/anr-investigation/agent.md +117 -0
- package/agents/android/anr-investigation/example-input.md +62 -0
- package/agents/android/anr-investigation/example-output.md +103 -0
- package/agents/android/axiom/agent.md +197 -0
- package/agents/android/code-reviewer/README.md +108 -0
- package/agents/android/code-reviewer/agent.md +210 -0
- package/agents/android/code-reviewer/example-input.md +116 -0
- package/agents/android/code-reviewer/example-output.md +115 -0
- package/agents/android/compose-navigation/README.md +76 -0
- package/agents/android/compose-navigation/agent.md +121 -0
- package/agents/android/compose-navigation/example-input.md +49 -0
- package/agents/android/compose-navigation/example-output.md +325 -0
- package/agents/android/compose-screen-builder/README.md +95 -0
- package/agents/android/compose-screen-builder/agent.md +170 -0
- package/agents/android/compose-screen-builder/example-input.md +40 -0
- package/agents/android/compose-screen-builder/example-output.md +568 -0
- package/agents/android/compose-ui-reviewer/README.md +73 -0
- package/agents/android/compose-ui-reviewer/agent.md +253 -0
- package/agents/android/compose-ui-reviewer/example-input.md +84 -0
- package/agents/android/compose-ui-reviewer/example-output.md +102 -0
- package/agents/android/crash-analyzer/README.md +53 -0
- package/agents/android/crash-analyzer/agent.md +295 -0
- package/agents/android/memory-leak-analyzer/README.md +79 -0
- package/agents/android/memory-leak-analyzer/agent.md +116 -0
- package/agents/android/memory-leak-analyzer/example-input.md +78 -0
- package/agents/android/memory-leak-analyzer/example-output.md +128 -0
- package/agents/cross-platform/accessibility-auditor/README.md +47 -0
- package/agents/cross-platform/accessibility-auditor/agent.md +268 -0
- package/agents/cross-platform/accessibility-auditor/example-input.md +84 -0
- package/agents/cross-platform/accessibility-auditor/example-output.md +132 -0
- package/agents/cross-platform/appforge/agent.md +303 -0
- package/agents/cross-platform/ci-cd-generator/README.md +65 -0
- package/agents/cross-platform/ci-cd-generator/agent.md +422 -0
- package/agents/cross-platform/ci-cd-generator/example-input.md +69 -0
- package/agents/cross-platform/ci-cd-generator/example-output.md +261 -0
- package/agents/cross-platform/crasher/agent.md +221 -0
- package/agents/cross-platform/figma/agent.md +247 -0
- package/agents/cross-platform/launchpad/agent.md +330 -0
- package/agents/cross-platform/mobile-harness/agent.md +422 -0
- package/agents/cross-platform/mobile-memory/agent.md +429 -0
- package/agents/cross-platform/perf/agent.md +261 -0
- package/agents/cross-platform/pipeline/agent.md +288 -0
- package/agents/cross-platform/release-notes-generator/README.md +63 -0
- package/agents/cross-platform/release-notes-generator/agent.md +218 -0
- package/agents/cross-platform/release-notes-generator/example-input.md +71 -0
- package/agents/cross-platform/release-notes-generator/example-output.md +96 -0
- package/agents/cross-platform/scribe/agent.md +180 -0
- package/agents/cross-platform/security-scanner/README.md +61 -0
- package/agents/cross-platform/security-scanner/agent.md +287 -0
- package/agents/cross-platform/security-scanner/example-input.md +77 -0
- package/agents/cross-platform/security-scanner/example-output.md +141 -0
- package/agents/cross-platform/sentinel/agent.md +217 -0
- package/agents/cross-platform/store-listing-writer/README.md +58 -0
- package/agents/cross-platform/store-listing-writer/agent.md +380 -0
- package/agents/cross-platform/store-listing-writer/example-input.md +67 -0
- package/agents/cross-platform/store-listing-writer/example-output.md +131 -0
- package/agents/flutter/bloc-feature-builder/README.md +101 -0
- package/agents/flutter/bloc-feature-builder/agent.md +225 -0
- package/agents/flutter/bloc-feature-builder/example-input.md +47 -0
- package/agents/flutter/bloc-feature-builder/example-output.md +838 -0
- package/agents/flutter/dart/agent.md +223 -0
- package/agents/flutter/widget-generator/README.md +66 -0
- package/agents/flutter/widget-generator/agent.md +264 -0
- package/agents/flutter/widget-generator/example-input.md +71 -0
- package/agents/flutter/widget-generator/example-output.md +163 -0
- package/agents/gaming/forge/agent.md +197 -0
- package/agents/gaming/unreal/agent.md +200 -0
- package/agents/ios/crash-analyzer/README.md +69 -0
- package/agents/ios/crash-analyzer/agent.md +300 -0
- package/agents/ios/crash-analyzer/example-input.md +83 -0
- package/agents/ios/crash-analyzer/example-output.md +126 -0
- package/agents/ios/swift/agent.md +206 -0
- package/agents/ios/swift-reviewer/README.md +68 -0
- package/agents/ios/swift-reviewer/agent.md +231 -0
- package/agents/ios/swift-reviewer/example-input.md +100 -0
- package/agents/ios/swift-reviewer/example-output.md +127 -0
- package/agents/react-native/bridge/agent.md +212 -0
- package/agents/react-native/performance-optimizer/README.md +81 -0
- package/agents/react-native/performance-optimizer/agent.md +284 -0
- package/agents/react-native/performance-optimizer/example-input.md +104 -0
- package/agents/react-native/performance-optimizer/example-output.md +132 -0
- package/agents/unity/shader-generator/README.md +77 -0
- package/agents/unity/shader-generator/agent.md +270 -0
- package/agents/unity/shader-generator/example-input.md +70 -0
- package/agents/unity/shader-generator/example-output.md +160 -0
- package/agents/unreal/blueprint-advisor/README.md +71 -0
- package/agents/unreal/blueprint-advisor/agent.md +311 -0
- package/agents/unreal/blueprint-advisor/example-input.md +73 -0
- package/agents/unreal/blueprint-advisor/example-output.md +217 -0
- package/cli/index.js +1091 -0
- package/docs/agent-guide.md +138 -0
- package/docs/chatgpt-integration.md +219 -0
- package/docs/core-loops.md +143 -0
- package/docs/cursor-integration.md +143 -0
- package/docs/getting-started.md +132 -0
- package/docs/index.html +711 -0
- package/docs/installation.md +271 -0
- package/docs/mobile-harness.md +160 -0
- package/docs/mobile-mcp.md +172 -0
- package/docs/mobile-memory.md +349 -0
- package/docs/release-process.md +85 -0
- package/docs/releases/release-v1.0.0.md +165 -0
- package/docs/roadmap/github-issues.md +239 -0
- package/docs/skill-categories.md +125 -0
- package/docs/vscode-copilot-integration.md +104 -0
- package/docs/wiki.md +615 -0
- package/install.sh +507 -0
- package/mobile-karpathy.md +79 -0
- package/package.json +57 -0
- package/skills/android/README.md +31 -0
- package/skills/android/android-tdd.md +89 -0
- package/skills/android/anr-investigation.md +251 -0
- package/skills/android/code-review.md +98 -0
- package/skills/android/compose-migration.md +48 -0
- package/skills/android/compose-review.md +78 -0
- package/skills/android/kotlin-modernize.md +51 -0
- package/skills/android/memory-leak-investigation.md +256 -0
- package/skills/android/proguard-rules.md +48 -0
- package/skills/cross-platform/accessibility-audit.md +63 -0
- package/skills/cross-platform/api-versioning.md +59 -0
- package/skills/cross-platform/clean-code-audit.md +239 -0
- package/skills/cross-platform/crash-triage.md +53 -0
- package/skills/cross-platform/deeplink-debug.md +57 -0
- package/skills/cross-platform/feature-slice.md +48 -0
- package/skills/cross-platform/grill-mobile.md +64 -0
- package/skills/cross-platform/mobile-mcp-qa.md +193 -0
- package/skills/cross-platform/mobile-memory-graph.md +179 -0
- package/skills/cross-platform/mobile-memory-save.md +210 -0
- package/skills/cross-platform/mobile-memory-search.md +154 -0
- package/skills/cross-platform/perf-audit.md +61 -0
- package/skills/cross-platform/prd-verification.md +324 -0
- package/skills/cross-platform/release-prep.md +64 -0
- package/skills/cross-platform/security-audit.md +281 -0
- package/skills/cross-platform/store-listing.md +75 -0
- package/skills/flutter/README.md +32 -0
- package/skills/flutter/dart-modernize.md +47 -0
- package/skills/flutter/flutter-review.md +59 -0
- package/skills/flutter/flutter-tdd.md +73 -0
- package/skills/flutter/widget-extract.md +41 -0
- package/skills/flutter/widget-gen.md +131 -0
- package/skills/gaming/blueprint-to-cpp.md +51 -0
- package/skills/gaming/game-perf.md +54 -0
- package/skills/gaming/shader-gen.md +44 -0
- package/skills/gaming/unity-tdd.md +85 -0
- package/skills/ios/README.md +45 -0
- package/skills/ios/data-persistence.md +151 -0
- package/skills/ios/ios-tdd.md +90 -0
- package/skills/ios/networking.md +139 -0
- package/skills/ios/performance.md +122 -0
- package/skills/ios/swift-concurrency.md +57 -0
- package/skills/ios/swift-review.md +124 -0
- package/skills/ios/swiftui-review.md +49 -0
- package/skills/ios/swiftui-state.md +122 -0
- package/skills/ios/unit-testing.md +144 -0
- package/skills/ios/xcode-warnings.md +37 -0
- package/skills/react-native/README.md +42 -0
- package/skills/react-native/bridge-audit.md +104 -0
- package/skills/react-native/expo-optimize.md +55 -0
- package/skills/react-native/new-arch-migrate.md +53 -0
- package/skills/react-native/performance.md +124 -0
- package/skills/react-native/rn-review.md +53 -0
- package/skills/react-native/rn-tdd.md +76 -0
- package/skills/shared/README.md +46 -0
- package/skills/shared/accessibility-audit.md +132 -0
- package/skills/shared/crash-analysis.md +110 -0
- package/skills/shared/security-scan.md +149 -0
- package/skills/unity/README.md +31 -0
- package/skills/unity/shader-review.md +110 -0
- package/templates/agent-template.md +88 -0
- package/templates/prompt-template.md +50 -0
- package/templates/skill-template.md +64 -0
- package/templates/workflow-template.md +48 -0
- package/workflows/app-launch.md +66 -0
- package/workflows/appforge-workflow.md +232 -0
- package/workflows/ci-setup.md +77 -0
- package/workflows/crash-to-fix.md +71 -0
- package/workflows/feature-ship.md +61 -0
- package/workflows/game-level.md +72 -0
- package/workflows/mobile-harness.md +319 -0
- package/workflows/mobile-mcp-qa.md +228 -0
- package/workflows/mobile-memory-workflow.md +243 -0
- package/workflows/new-project-setup.md +93 -0
- package/workflows/new-screen.md +68 -0
- package/workflows/perf-sprint.md +78 -0
- package/workflows/release-cycle.md +71 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Example Output — Android Code Reviewer
|
|
2
|
+
|
|
3
|
+
Full agent response to the input in [`example-input.md`](example-input.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
REVIEW SUMMARY
|
|
9
|
+
==============
|
|
10
|
+
File: app/src/main/java/com/example/socialapp/ui/profile/ProfileViewModel.kt
|
|
11
|
+
Layer: ViewModel
|
|
12
|
+
Issues Found: 7
|
|
13
|
+
Critical: 4 Warning: 2 Info: 1
|
|
14
|
+
|
|
15
|
+
FINDINGS
|
|
16
|
+
--------
|
|
17
|
+
|
|
18
|
+
[CRITICAL] Line 19 — GlobalScope used instead of viewModelScope
|
|
19
|
+
Problem : GlobalScope is not tied to the ViewModel's lifecycle. When the ViewModel
|
|
20
|
+
is cleared (screen navigated away, process death), the coroutine continues
|
|
21
|
+
running, holding references to the ViewModel's LiveData and potentially the
|
|
22
|
+
Fragment. This is a guaranteed scope leak.
|
|
23
|
+
Fix : Replace both GlobalScope.launch calls with viewModelScope.launch:
|
|
24
|
+
fun loadUser(userId: String) {
|
|
25
|
+
viewModelScope.launch {
|
|
26
|
+
...
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
[CRITICAL] Line 20 — LiveData.value assigned from a background coroutine thread
|
|
31
|
+
Problem : GlobalScope.launch uses Dispatchers.Default by default. Calling
|
|
32
|
+
isLoading.value = true on a non-Main thread throws CalledFromWrongThreadException
|
|
33
|
+
in debug builds and causes silent data races in release builds.
|
|
34
|
+
LiveData.value can only be set from the Main thread; LiveData.postValue
|
|
35
|
+
is the background-safe alternative, but it introduces a delivery delay.
|
|
36
|
+
Fix : Either switch the coroutine to the Main dispatcher:
|
|
37
|
+
viewModelScope.launch(Dispatchers.Main) { isLoading.value = true; ... }
|
|
38
|
+
Or migrate isLoading to MutableStateFlow and update it from any thread via .value.
|
|
39
|
+
The cleanest solution is viewModelScope.launch, which defaults to Main.
|
|
40
|
+
|
|
41
|
+
[CRITICAL] Lines 22 & 42 — UserRepository instantiated directly inside ViewModel
|
|
42
|
+
Problem : new UserRepository() (or UserRepository() in Kotlin) constructs a concrete
|
|
43
|
+
dependency inside the ViewModel. This:
|
|
44
|
+
(1) makes the class impossible to unit-test without the real network/database,
|
|
45
|
+
(2) violates the Dependency Inversion Principle,
|
|
46
|
+
(3) creates a new repository instance on every function call, bypassing any
|
|
47
|
+
caching or connection pooling in the repository.
|
|
48
|
+
Fix : Inject UserRepository via the constructor. With Hilt:
|
|
49
|
+
@HiltViewModel
|
|
50
|
+
class ProfileViewModel @Inject constructor(
|
|
51
|
+
private val userRepository: UserRepository
|
|
52
|
+
) : ViewModel() { ... }
|
|
53
|
+
Then use userRepository.getUser(userId) instead of UserRepository().getUser(userId).
|
|
54
|
+
|
|
55
|
+
[CRITICAL] Lines 36 & 38 — !! force-unwrap on user.value
|
|
56
|
+
Problem : user.value!! will throw NullPointerException if saveProfile() is called before
|
|
57
|
+
loadUser() completes, or if loadUser() failed with an error. The !! operator
|
|
58
|
+
should be treated as a code smell in production ViewModel code — it converts a
|
|
59
|
+
compile-time nullable into a runtime crash.
|
|
60
|
+
Fix : Use safe navigation and an early return:
|
|
61
|
+
val currentUser = user.value ?: run {
|
|
62
|
+
errorMessage.value = "No user loaded"
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
val updated = User(id = currentUser.id, name = name, bio = bio,
|
|
66
|
+
avatarUrl = currentUser.avatarUrl)
|
|
67
|
+
|
|
68
|
+
[WARNING] Line 34 — null check on non-nullable String (always false)
|
|
69
|
+
Problem : name == null is always false because name: String is non-nullable in Kotlin.
|
|
70
|
+
The compiler treats this as a warning. The intent was to guard against an empty
|
|
71
|
+
string, but the null check is dead code that misleads future readers.
|
|
72
|
+
Fix : Replace with:
|
|
73
|
+
if (name.isBlank()) return
|
|
74
|
+
isBlank() checks for both empty and whitespace-only strings, which is the
|
|
75
|
+
correct guard for a user-facing name field.
|
|
76
|
+
|
|
77
|
+
[WARNING] Lines 13–15 — MutableLiveData exposed directly as public properties
|
|
78
|
+
Problem : Exposing MutableLiveData publicly allows any observer to call .value = ...
|
|
79
|
+
from outside the ViewModel, breaking the single-source-of-truth pattern and
|
|
80
|
+
making state mutations untraceable.
|
|
81
|
+
Fix : Follow the backing property pattern:
|
|
82
|
+
private val _user = MutableLiveData<User>()
|
|
83
|
+
val user: LiveData<User> = _user
|
|
84
|
+
|
|
85
|
+
private val _isLoading = MutableLiveData<Boolean>()
|
|
86
|
+
val isLoading: LiveData<Boolean> = _isLoading
|
|
87
|
+
|
|
88
|
+
[INFO] Lines 13–15 — Consider migrating from LiveData to StateFlow
|
|
89
|
+
Problem : LiveData is lifecycle-aware but less composable than StateFlow and requires
|
|
90
|
+
Android framework classes (LifecycleOwner) to observe. For new ViewModels,
|
|
91
|
+
StateFlow + collectAsStateWithLifecycle() is the idiomatic Kotlin choice and
|
|
92
|
+
integrates directly with Jetpack Compose.
|
|
93
|
+
Fix : private val _user = MutableStateFlow<User?>(null)
|
|
94
|
+
val user: StateFlow<User?> = _user.asStateFlow()
|
|
95
|
+
In the Fragment: collect with repeatOnLifecycle(STARTED).
|
|
96
|
+
|
|
97
|
+
CLEAN ARCHITECTURE ASSESSMENT
|
|
98
|
+
------------------------------
|
|
99
|
+
Layer boundary respected: No
|
|
100
|
+
→ UserRepository() is instantiated directly inside ViewModel. The ViewModel should
|
|
101
|
+
depend on an abstraction (interface), not a concrete repository implementation.
|
|
102
|
+
Inject via Hilt constructor injection.
|
|
103
|
+
|
|
104
|
+
COROUTINE SAFETY
|
|
105
|
+
----------------
|
|
106
|
+
Scope leaks detected: Yes
|
|
107
|
+
→ Two GlobalScope.launch calls (lines 19 and 41). Both must be replaced with
|
|
108
|
+
viewModelScope.launch. viewModelScope is automatically cancelled in onCleared().
|
|
109
|
+
|
|
110
|
+
TESTABILITY SCORE: 1/10
|
|
111
|
+
→ Direct UserRepository instantiation and GlobalScope make this class effectively
|
|
112
|
+
untestable. After applying the fixes, testability rises to ~8/10.
|
|
113
|
+
|
|
114
|
+
OVERALL VERDICT: NEEDS WORK
|
|
115
|
+
```
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Android Compose Navigation Architect
|
|
2
|
+
|
|
3
|
+
> Generate or review scalable Jetpack Compose navigation with type-safe routes, nested graphs, bottom navigation, deep links, and tests.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What It Does
|
|
8
|
+
|
|
9
|
+
- Generates Navigation Compose 2.8+ type-safe routes using `@Serializable`
|
|
10
|
+
- Creates root and feature-level graph-builder extensions
|
|
11
|
+
- Designs nested auth, onboarding, checkout, or other subflows
|
|
12
|
+
- Implements bottom navigation with state restoration
|
|
13
|
+
- Handles typed arguments through `toRoute<T>()`
|
|
14
|
+
- Shows `SavedStateHandle.toRoute<T>()` for ViewModels
|
|
15
|
+
- Defines deep-link and authentication behavior
|
|
16
|
+
- Reviews duplicate routes, unsafe arguments, incorrect `popUpTo`, and back-stack bugs
|
|
17
|
+
- Generates navigation tests
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Files
|
|
22
|
+
|
|
23
|
+
| File | Purpose |
|
|
24
|
+
|---|---|
|
|
25
|
+
| [`agent.md`](agent.md) | Input/output contract and system prompt |
|
|
26
|
+
| [`example-input.md`](example-input.md) | Real app navigation requirements |
|
|
27
|
+
| [`example-output.md`](example-output.md) | Complete generated navigation architecture |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
Use the agent at agents/android/compose-navigation/agent.md.
|
|
35
|
+
|
|
36
|
+
MODE: GENERATE
|
|
37
|
+
PACKAGE: com.example.shop
|
|
38
|
+
NAVIGATION_VERSION: 2.8.9
|
|
39
|
+
KOTLIN_VERSION: 2.0
|
|
40
|
+
...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Install only this agent:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx mobile-ai-agents add agent compose-navigation
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Design Principles
|
|
52
|
+
|
|
53
|
+
- Routes are types, not strings.
|
|
54
|
+
- Screens expose callbacks, not `NavController`.
|
|
55
|
+
- Feature modules own their internal destinations.
|
|
56
|
+
- Route arguments contain IDs, not full objects.
|
|
57
|
+
- Bottom tabs preserve independent state.
|
|
58
|
+
- Back-stack changes are deliberate and documented.
|
|
59
|
+
- Deep links are validated before privileged navigation.
|
|
60
|
+
- Navigation behavior is covered by tests.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Related Agents
|
|
65
|
+
|
|
66
|
+
- [`compose-screen-builder`](../compose-screen-builder/) for screen implementation
|
|
67
|
+
- [`compose-ui-reviewer`](../compose-ui-reviewer/) for UI quality
|
|
68
|
+
- [`axiom`](../axiom/) for Android architecture review
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Official References
|
|
73
|
+
|
|
74
|
+
- [Navigation with Compose](https://developer.android.com/develop/ui/compose/navigation)
|
|
75
|
+
- [Type safety in Kotlin DSL and Navigation Compose](https://developer.android.com/guide/navigation/design/type-safety)
|
|
76
|
+
- [Test Navigation](https://developer.android.com/guide/navigation/testing)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# NAVIGATOR — Android Compose Navigation Architect
|
|
2
|
+
|
|
3
|
+
**Platform:** Android (Kotlin / Jetpack Compose / Navigation Compose)
|
|
4
|
+
**Personality:** Back-stack cartographer. Every destination has a type and every pop has a reason.
|
|
5
|
+
**Category:** Architecture / Code Generation / Code Review
|
|
6
|
+
**Complexity:** High
|
|
7
|
+
**Tested With:** GPT-5.4
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
Designs or reviews scalable Jetpack Compose navigation using type-safe routes, nested feature graphs, bottom navigation, deep links, authentication gates, predictable back-stack behavior, and testable screen callbacks.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Input Format
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
MODE: <GENERATE | REVIEW>
|
|
21
|
+
PACKAGE: <base package>
|
|
22
|
+
NAVIGATION_VERSION: <version, e.g. 2.8.9>
|
|
23
|
+
KOTLIN_VERSION: <version>
|
|
24
|
+
MODULES: <single app module or module list>
|
|
25
|
+
SCREENS:
|
|
26
|
+
- <screen and arguments>
|
|
27
|
+
FLOWS:
|
|
28
|
+
- <navigation flow>
|
|
29
|
+
BOTTOM_NAVIGATION:
|
|
30
|
+
- <tab destinations or "none">
|
|
31
|
+
DEEP_LINKS:
|
|
32
|
+
- <URI and destination or "none">
|
|
33
|
+
AUTH_RULES:
|
|
34
|
+
- <authentication/onboarding conditions or "none">
|
|
35
|
+
EXISTING_CODE:
|
|
36
|
+
<required in REVIEW mode; optional in GENERATE mode>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Output Format
|
|
42
|
+
|
|
43
|
+
````
|
|
44
|
+
NAVIGATION ARCHITECTURE
|
|
45
|
+
=======================
|
|
46
|
+
Mode:
|
|
47
|
+
Verdict: PASS | NEEDS WORK | GENERATED
|
|
48
|
+
|
|
49
|
+
GRAPH
|
|
50
|
+
-----
|
|
51
|
+
<compact graph description>
|
|
52
|
+
|
|
53
|
+
FILES
|
|
54
|
+
-----
|
|
55
|
+
## File: <path>
|
|
56
|
+
```kotlin
|
|
57
|
+
<complete code>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
BACK-STACK RULES
|
|
61
|
+
----------------
|
|
62
|
+
<rules>
|
|
63
|
+
|
|
64
|
+
DEEP-LINK RULES
|
|
65
|
+
---------------
|
|
66
|
+
<rules>
|
|
67
|
+
|
|
68
|
+
TEST PLAN
|
|
69
|
+
---------
|
|
70
|
+
<tests>
|
|
71
|
+
|
|
72
|
+
REVIEW FINDINGS
|
|
73
|
+
---------------
|
|
74
|
+
<findings or "Not applicable">
|
|
75
|
+
|
|
76
|
+
DEPENDENCIES
|
|
77
|
+
------------
|
|
78
|
+
<Gradle dependencies>
|
|
79
|
+
````
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## System Prompt
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
You are NAVIGATOR, a senior Android navigation architect specializing in Kotlin, Jetpack Compose, Navigation Compose, modularization, deep links, and back-stack correctness.
|
|
87
|
+
|
|
88
|
+
Support GENERATE and REVIEW modes.
|
|
89
|
+
|
|
90
|
+
Rules:
|
|
91
|
+
1. For Navigation 2.8+, use @Serializable route objects/classes, composable<T>(), navigation<T>(), navigate(route), NavBackStackEntry.toRoute<T>(), and SavedStateHandle.toRoute<T>(). Do not generate hardcoded route strings.
|
|
92
|
+
2. Pass stable identifiers only. Never pass repositories, ViewModels, mutable objects, or large domain objects as arguments.
|
|
93
|
+
3. Screens receive typed callbacks such as onProductClick(id); do not pass NavController into leaf screens.
|
|
94
|
+
4. Put routes and graph-builder extensions outside UI screen files. Use one feature navigation contract per module.
|
|
95
|
+
5. Use nested graphs for self-contained flows such as auth, onboarding, checkout, and games.
|
|
96
|
+
6. Bottom navigation must use launchSingleTop, restoreState, and popUpTo with saveState. Define reselection behavior explicitly.
|
|
97
|
+
7. Authentication redirects must preserve the intended destination without putting authorization decisions in Composables.
|
|
98
|
+
8. Deep links must validate hosts, paths, arguments, and authentication before privileged navigation.
|
|
99
|
+
9. Explain popUpTo and inclusive choices. Prevent duplicate destinations and accidental stack clearing.
|
|
100
|
+
10. Include navigation tests using TestNavHostController or an equivalent testable navigation surface.
|
|
101
|
+
11. In REVIEW mode, assign CRITICAL, WARNING, or INFO and provide corrected code.
|
|
102
|
+
12. If the project uses Navigation below 2.8, state the limitation and provide a migration plan before type-safe code.
|
|
103
|
+
|
|
104
|
+
Return complete compilable Kotlin for the requested scope. Do not invent screens or flows.
|
|
105
|
+
|
|
106
|
+
Output MUST follow the exact format specified. Do not add extra sections or omit any section.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Example
|
|
112
|
+
|
|
113
|
+
See [`example-input.md`](example-input.md) and [`example-output.md`](example-output.md).
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Notes
|
|
118
|
+
|
|
119
|
+
- Type-safe Navigation Compose APIs require Navigation 2.8.0 or newer and Kotlin Serialization.
|
|
120
|
+
- The agent targets Navigation Compose 2.x. Navigation 3 should be requested explicitly because it uses a different architecture.
|
|
121
|
+
- Complex payloads belong in repositories or saved state; routes carry IDs and small primitives.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Example Input
|
|
2
|
+
|
|
3
|
+
```text
|
|
4
|
+
MODE: GENERATE
|
|
5
|
+
PACKAGE: com.example.catalog
|
|
6
|
+
NAVIGATION_VERSION: 2.8.9
|
|
7
|
+
KOTLIN_VERSION: 2.0
|
|
8
|
+
MODULES:
|
|
9
|
+
- :app
|
|
10
|
+
- :feature:home
|
|
11
|
+
- :feature:search
|
|
12
|
+
- :feature:favorites
|
|
13
|
+
- :feature:profile
|
|
14
|
+
- :feature:product
|
|
15
|
+
|
|
16
|
+
SCREENS:
|
|
17
|
+
- Home — no arguments
|
|
18
|
+
- Search — optional initialQuery: String
|
|
19
|
+
- Favorites — no arguments
|
|
20
|
+
- Profile — no arguments
|
|
21
|
+
- Settings — no arguments
|
|
22
|
+
- ProductDetail — productId: String
|
|
23
|
+
- SignIn — optional redirectProductId: String
|
|
24
|
+
|
|
25
|
+
FLOWS:
|
|
26
|
+
- Home, Search, Favorites, and Profile are top-level destinations.
|
|
27
|
+
- Product cards open ProductDetail.
|
|
28
|
+
- Settings opens from Profile.
|
|
29
|
+
- Unauthenticated users opening ProductDetail go to SignIn, then continue to ProductDetail.
|
|
30
|
+
- Back from ProductDetail returns to the previous tab.
|
|
31
|
+
|
|
32
|
+
BOTTOM_NAVIGATION:
|
|
33
|
+
- Home
|
|
34
|
+
- Search
|
|
35
|
+
- Favorites
|
|
36
|
+
- Profile
|
|
37
|
+
|
|
38
|
+
DEEP_LINKS:
|
|
39
|
+
- https://catalog.example.com/products/{productId} -> ProductDetail
|
|
40
|
+
|
|
41
|
+
AUTH_RULES:
|
|
42
|
+
- Home, Search, Favorites, and Profile are public.
|
|
43
|
+
- ProductDetail requires authentication.
|
|
44
|
+
- Preserve productId through sign-in.
|
|
45
|
+
|
|
46
|
+
EXISTING_CODE:
|
|
47
|
+
None.
|
|
48
|
+
```
|
|
49
|
+
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Example Output
|
|
2
|
+
|
|
3
|
+
## Navigation Architecture
|
|
4
|
+
|
|
5
|
+
Mode: `GENERATE`
|
|
6
|
+
Verdict: `GENERATED`
|
|
7
|
+
|
|
8
|
+
### Graph
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
AppGraph
|
|
12
|
+
├── Home
|
|
13
|
+
├── Search(initialQuery?)
|
|
14
|
+
├── Favorites
|
|
15
|
+
├── Profile
|
|
16
|
+
├── Settings
|
|
17
|
+
├── AuthGraph
|
|
18
|
+
│ └── SignIn(redirectProductId?)
|
|
19
|
+
└── ProductDetail(productId)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Top-level tabs are direct destinations so each tab can save and restore its state. Authentication is a nested graph because sign-in is a self-contained flow. Product navigation goes through an authentication-aware coordinator callback in the app layer.
|
|
23
|
+
|
|
24
|
+
## Files
|
|
25
|
+
|
|
26
|
+
### File: `app/navigation/AppRoute.kt`
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
package com.example.catalog.navigation
|
|
30
|
+
|
|
31
|
+
import kotlinx.serialization.Serializable
|
|
32
|
+
|
|
33
|
+
sealed interface AppRoute {
|
|
34
|
+
@Serializable
|
|
35
|
+
data object Home : AppRoute
|
|
36
|
+
|
|
37
|
+
@Serializable
|
|
38
|
+
data class Search(val initialQuery: String? = null) : AppRoute
|
|
39
|
+
|
|
40
|
+
@Serializable
|
|
41
|
+
data object Favorites : AppRoute
|
|
42
|
+
|
|
43
|
+
@Serializable
|
|
44
|
+
data object Profile : AppRoute
|
|
45
|
+
|
|
46
|
+
@Serializable
|
|
47
|
+
data object Settings : AppRoute
|
|
48
|
+
|
|
49
|
+
@Serializable
|
|
50
|
+
data class ProductDetail(val productId: String) : AppRoute
|
|
51
|
+
|
|
52
|
+
@Serializable
|
|
53
|
+
data object AuthGraph : AppRoute
|
|
54
|
+
|
|
55
|
+
@Serializable
|
|
56
|
+
data class SignIn(val redirectProductId: String? = null) : AppRoute
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### File: `app/navigation/TopLevelDestination.kt`
|
|
61
|
+
|
|
62
|
+
```kotlin
|
|
63
|
+
package com.example.catalog.navigation
|
|
64
|
+
|
|
65
|
+
import androidx.compose.material.icons.Icons
|
|
66
|
+
import androidx.compose.material.icons.outlined.FavoriteBorder
|
|
67
|
+
import androidx.compose.material.icons.outlined.Home
|
|
68
|
+
import androidx.compose.material.icons.outlined.Person
|
|
69
|
+
import androidx.compose.material.icons.outlined.Search
|
|
70
|
+
import androidx.compose.ui.graphics.vector.ImageVector
|
|
71
|
+
|
|
72
|
+
enum class TopLevelDestination(
|
|
73
|
+
val route: AppRoute,
|
|
74
|
+
val label: String,
|
|
75
|
+
val icon: ImageVector,
|
|
76
|
+
) {
|
|
77
|
+
HOME(AppRoute.Home, "Home", Icons.Outlined.Home),
|
|
78
|
+
SEARCH(AppRoute.Search(), "Search", Icons.Outlined.Search),
|
|
79
|
+
FAVORITES(AppRoute.Favorites, "Favorites", Icons.Outlined.FavoriteBorder),
|
|
80
|
+
PROFILE(AppRoute.Profile, "Profile", Icons.Outlined.Person),
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### File: `app/navigation/NavControllerExtensions.kt`
|
|
85
|
+
|
|
86
|
+
```kotlin
|
|
87
|
+
package com.example.catalog.navigation
|
|
88
|
+
|
|
89
|
+
import androidx.navigation.NavGraph.Companion.findStartDestination
|
|
90
|
+
import androidx.navigation.NavHostController
|
|
91
|
+
|
|
92
|
+
fun NavHostController.navigateToTopLevel(
|
|
93
|
+
destination: TopLevelDestination,
|
|
94
|
+
) {
|
|
95
|
+
navigate(destination.route) {
|
|
96
|
+
popUpTo(graph.findStartDestination().id) {
|
|
97
|
+
saveState = true
|
|
98
|
+
}
|
|
99
|
+
launchSingleTop = true
|
|
100
|
+
restoreState = true
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### File: `app/navigation/AppNavHost.kt`
|
|
106
|
+
|
|
107
|
+
```kotlin
|
|
108
|
+
package com.example.catalog.navigation
|
|
109
|
+
|
|
110
|
+
import androidx.compose.runtime.Composable
|
|
111
|
+
import androidx.navigation.NavHostController
|
|
112
|
+
import androidx.navigation.compose.NavHost
|
|
113
|
+
import androidx.navigation.compose.composable
|
|
114
|
+
import androidx.navigation.navigation
|
|
115
|
+
import androidx.navigation.toRoute
|
|
116
|
+
import com.example.catalog.feature.favorites.FavoritesScreen
|
|
117
|
+
import com.example.catalog.feature.home.HomeScreen
|
|
118
|
+
import com.example.catalog.feature.product.ProductDetailScreen
|
|
119
|
+
import com.example.catalog.feature.profile.ProfileScreen
|
|
120
|
+
import com.example.catalog.feature.profile.SettingsScreen
|
|
121
|
+
import com.example.catalog.feature.search.SearchScreen
|
|
122
|
+
import com.example.catalog.feature.signin.SignInScreen
|
|
123
|
+
|
|
124
|
+
@Composable
|
|
125
|
+
fun AppNavHost(
|
|
126
|
+
navController: NavHostController,
|
|
127
|
+
isAuthenticated: () -> Boolean,
|
|
128
|
+
) {
|
|
129
|
+
fun openProduct(productId: String) {
|
|
130
|
+
val route = if (isAuthenticated()) {
|
|
131
|
+
AppRoute.ProductDetail(productId)
|
|
132
|
+
} else {
|
|
133
|
+
AppRoute.SignIn(redirectProductId = productId)
|
|
134
|
+
}
|
|
135
|
+
navController.navigate(route)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
NavHost(
|
|
139
|
+
navController = navController,
|
|
140
|
+
startDestination = AppRoute.Home,
|
|
141
|
+
) {
|
|
142
|
+
composable<AppRoute.Home> {
|
|
143
|
+
HomeScreen(onProductClick = ::openProduct)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
composable<AppRoute.Search> { entry ->
|
|
147
|
+
val route = entry.toRoute<AppRoute.Search>()
|
|
148
|
+
SearchScreen(
|
|
149
|
+
initialQuery = route.initialQuery,
|
|
150
|
+
onProductClick = ::openProduct,
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
composable<AppRoute.Favorites> {
|
|
155
|
+
FavoritesScreen(onProductClick = ::openProduct)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
composable<AppRoute.Profile> {
|
|
159
|
+
ProfileScreen(
|
|
160
|
+
onSettingsClick = {
|
|
161
|
+
navController.navigate(AppRoute.Settings)
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
composable<AppRoute.Settings> {
|
|
167
|
+
SettingsScreen(onBack = navController::navigateUp)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
composable<AppRoute.ProductDetail>(
|
|
171
|
+
deepLinks = listOf(
|
|
172
|
+
androidx.navigation.navDeepLink<AppRoute.ProductDetail>(
|
|
173
|
+
basePath = "https://catalog.example.com/products",
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
) { entry ->
|
|
177
|
+
val route = entry.toRoute<AppRoute.ProductDetail>()
|
|
178
|
+
ProductDetailScreen(
|
|
179
|
+
productId = route.productId,
|
|
180
|
+
onBack = navController::navigateUp,
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
navigation<AppRoute.AuthGraph>(
|
|
185
|
+
startDestination = AppRoute.SignIn(),
|
|
186
|
+
) {
|
|
187
|
+
composable<AppRoute.SignIn> { entry ->
|
|
188
|
+
val route = entry.toRoute<AppRoute.SignIn>()
|
|
189
|
+
SignInScreen(
|
|
190
|
+
onSignedIn = {
|
|
191
|
+
val redirect = route.redirectProductId
|
|
192
|
+
if (redirect == null) {
|
|
193
|
+
navController.popBackStack()
|
|
194
|
+
} else {
|
|
195
|
+
navController.navigate(
|
|
196
|
+
AppRoute.ProductDetail(redirect),
|
|
197
|
+
) {
|
|
198
|
+
popUpTo<AppRoute.SignIn> {
|
|
199
|
+
inclusive = true
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
onCancel = navController::navigateUp,
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### File: `feature/product/ProductDetailViewModel.kt`
|
|
213
|
+
|
|
214
|
+
```kotlin
|
|
215
|
+
package com.example.catalog.feature.product
|
|
216
|
+
|
|
217
|
+
import androidx.lifecycle.SavedStateHandle
|
|
218
|
+
import androidx.lifecycle.ViewModel
|
|
219
|
+
import androidx.navigation.toRoute
|
|
220
|
+
import com.example.catalog.navigation.AppRoute
|
|
221
|
+
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
222
|
+
import javax.inject.Inject
|
|
223
|
+
|
|
224
|
+
@HiltViewModel
|
|
225
|
+
class ProductDetailViewModel @Inject constructor(
|
|
226
|
+
savedStateHandle: SavedStateHandle,
|
|
227
|
+
) : ViewModel() {
|
|
228
|
+
private val route = savedStateHandle.toRoute<AppRoute.ProductDetail>()
|
|
229
|
+
|
|
230
|
+
val productId: String = route.productId
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### File: `app/navigation/AppNavigationTest.kt`
|
|
235
|
+
|
|
236
|
+
```kotlin
|
|
237
|
+
package com.example.catalog.navigation
|
|
238
|
+
|
|
239
|
+
import androidx.compose.ui.test.junit4.createComposeTestRule
|
|
240
|
+
import androidx.navigation.compose.ComposeNavigator
|
|
241
|
+
import androidx.navigation.hasRoute
|
|
242
|
+
import androidx.navigation.testing.TestNavHostController
|
|
243
|
+
import androidx.test.core.app.ApplicationProvider
|
|
244
|
+
import org.junit.Assert.assertTrue
|
|
245
|
+
import org.junit.Before
|
|
246
|
+
import org.junit.Rule
|
|
247
|
+
import org.junit.Test
|
|
248
|
+
|
|
249
|
+
class AppNavigationTest {
|
|
250
|
+
@get:Rule
|
|
251
|
+
val composeRule = createComposeTestRule()
|
|
252
|
+
|
|
253
|
+
private lateinit var navController: TestNavHostController
|
|
254
|
+
|
|
255
|
+
@Before
|
|
256
|
+
fun setUp() {
|
|
257
|
+
navController = TestNavHostController(
|
|
258
|
+
ApplicationProvider.getApplicationContext(),
|
|
259
|
+
).apply {
|
|
260
|
+
navigatorProvider.addNavigator(ComposeNavigator())
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@Test
|
|
265
|
+
fun startsOnHome() {
|
|
266
|
+
composeRule.setContent {
|
|
267
|
+
AppNavHost(
|
|
268
|
+
navController = navController,
|
|
269
|
+
isAuthenticated = { true },
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
assertTrue(
|
|
274
|
+
navController.currentBackStackEntry
|
|
275
|
+
?.destination
|
|
276
|
+
?.hasRoute<AppRoute.Home>() == true,
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Back-Stack Rules
|
|
283
|
+
|
|
284
|
+
1. Top-level tab navigation saves and restores state.
|
|
285
|
+
2. `launchSingleTop` prevents duplicate tab destinations.
|
|
286
|
+
3. Product detail is pushed above the current tab, so back returns to that tab.
|
|
287
|
+
4. Successful sign-in removes only `SignIn`, then opens the preserved product.
|
|
288
|
+
5. Settings is pushed above Profile and uses `navigateUp()`.
|
|
289
|
+
|
|
290
|
+
## Deep-Link Rules
|
|
291
|
+
|
|
292
|
+
1. Accept only the HTTPS host `catalog.example.com`.
|
|
293
|
+
2. Parse `productId` through the typed route.
|
|
294
|
+
3. Validate that `productId` is non-blank before loading data.
|
|
295
|
+
4. If unauthenticated, preserve the ID and enter `AuthGraph`.
|
|
296
|
+
5. Do not trust client navigation as authorization; the backend must enforce access.
|
|
297
|
+
|
|
298
|
+
## Test Plan
|
|
299
|
+
|
|
300
|
+
- Verify the graph starts on Home.
|
|
301
|
+
- Verify tapping a product opens typed `ProductDetail`.
|
|
302
|
+
- Verify unauthenticated product navigation opens SignIn.
|
|
303
|
+
- Verify successful sign-in removes SignIn and opens the intended product.
|
|
304
|
+
- Verify switching tabs restores scroll/state and does not duplicate tabs.
|
|
305
|
+
- Verify back from ProductDetail returns to the originating tab.
|
|
306
|
+
- Verify the HTTPS product deep link opens the correct product.
|
|
307
|
+
- Verify malformed or blank product IDs show a safe error state.
|
|
308
|
+
|
|
309
|
+
## Review Findings
|
|
310
|
+
|
|
311
|
+
Not applicable.
|
|
312
|
+
|
|
313
|
+
## Dependencies
|
|
314
|
+
|
|
315
|
+
```kotlin
|
|
316
|
+
plugins {
|
|
317
|
+
kotlin("plugin.serialization")
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
dependencies {
|
|
321
|
+
implementation("androidx.navigation:navigation-compose:2.8.9")
|
|
322
|
+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
|
323
|
+
androidTestImplementation("androidx.navigation:navigation-testing:2.8.9")
|
|
324
|
+
}
|
|
325
|
+
```
|