oh-my-ag 1.2.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.
- package/.agent/skills/_shared/api-contracts/README.md +56 -0
- package/.agent/skills/_shared/api-contracts/template.md +88 -0
- package/.agent/skills/_shared/clarification-protocol.md +217 -0
- package/.agent/skills/_shared/common-checklist.md +31 -0
- package/.agent/skills/_shared/context-budget.md +118 -0
- package/.agent/skills/_shared/context-loading.md +105 -0
- package/.agent/skills/_shared/difficulty-guide.md +55 -0
- package/.agent/skills/_shared/lessons-learned.md +113 -0
- package/.agent/skills/_shared/memory-protocol.md +79 -0
- package/.agent/skills/_shared/reasoning-templates.md +161 -0
- package/.agent/skills/_shared/skill-routing.md +80 -0
- package/.agent/skills/_shared/verify.sh +252 -0
- package/.agent/skills/backend-agent/SKILL.md +47 -0
- package/.agent/skills/backend-agent/resources/api-template.py +326 -0
- package/.agent/skills/backend-agent/resources/checklist.md +36 -0
- package/.agent/skills/backend-agent/resources/error-playbook.md +98 -0
- package/.agent/skills/backend-agent/resources/examples.md +85 -0
- package/.agent/skills/backend-agent/resources/execution-protocol.md +45 -0
- package/.agent/skills/backend-agent/resources/snippets.md +197 -0
- package/.agent/skills/backend-agent/resources/tech-stack.md +39 -0
- package/.agent/skills/commit/SKILL.md +121 -0
- package/.agent/skills/commit/config/commit-config.yaml +55 -0
- package/.agent/skills/commit/resources/conventional-commits.md +166 -0
- package/.agent/skills/debug-agent/SKILL.md +51 -0
- package/.agent/skills/debug-agent/resources/bug-report-template.md +332 -0
- package/.agent/skills/debug-agent/resources/checklist.md +30 -0
- package/.agent/skills/debug-agent/resources/common-patterns.md +734 -0
- package/.agent/skills/debug-agent/resources/debugging-checklist.md +362 -0
- package/.agent/skills/debug-agent/resources/error-playbook.md +94 -0
- package/.agent/skills/debug-agent/resources/examples.md +87 -0
- package/.agent/skills/debug-agent/resources/execution-protocol.md +51 -0
- package/.agent/skills/frontend-agent/SKILL.md +48 -0
- package/.agent/skills/frontend-agent/resources/checklist.md +38 -0
- package/.agent/skills/frontend-agent/resources/component-template.tsx +92 -0
- package/.agent/skills/frontend-agent/resources/error-playbook.md +108 -0
- package/.agent/skills/frontend-agent/resources/examples.md +77 -0
- package/.agent/skills/frontend-agent/resources/execution-protocol.md +49 -0
- package/.agent/skills/frontend-agent/resources/snippets.md +205 -0
- package/.agent/skills/frontend-agent/resources/tailwind-rules.md +343 -0
- package/.agent/skills/frontend-agent/resources/tech-stack.md +36 -0
- package/.agent/skills/mobile-agent/SKILL.md +46 -0
- package/.agent/skills/mobile-agent/resources/checklist.md +35 -0
- package/.agent/skills/mobile-agent/resources/error-playbook.md +106 -0
- package/.agent/skills/mobile-agent/resources/examples.md +79 -0
- package/.agent/skills/mobile-agent/resources/execution-protocol.md +49 -0
- package/.agent/skills/mobile-agent/resources/screen-template.dart +298 -0
- package/.agent/skills/mobile-agent/resources/snippets.md +235 -0
- package/.agent/skills/mobile-agent/resources/tech-stack.md +45 -0
- package/.agent/skills/orchestrator/SKILL.md +99 -0
- package/.agent/skills/orchestrator/config/cli-config.yaml +78 -0
- package/.agent/skills/orchestrator/resources/memory-schema.md +212 -0
- package/.agent/skills/orchestrator/resources/subagent-prompt-template.md +153 -0
- package/.agent/skills/orchestrator/scripts/parallel-run.sh +330 -0
- package/.agent/skills/orchestrator/scripts/spawn-agent.sh +263 -0
- package/.agent/skills/orchestrator/templates/backend-task.md +18 -0
- package/.agent/skills/orchestrator/templates/debug-task.md +16 -0
- package/.agent/skills/orchestrator/templates/frontend-task.md +17 -0
- package/.agent/skills/orchestrator/templates/mobile-task.md +17 -0
- package/.agent/skills/orchestrator/templates/qa-task.md +16 -0
- package/.agent/skills/orchestrator/templates/tasks-example.yaml +15 -0
- package/.agent/skills/pm-agent/SKILL.md +47 -0
- package/.agent/skills/pm-agent/resources/error-playbook.md +75 -0
- package/.agent/skills/pm-agent/resources/examples.md +121 -0
- package/.agent/skills/pm-agent/resources/execution-protocol.md +46 -0
- package/.agent/skills/pm-agent/resources/task-template.json +57 -0
- package/.agent/skills/qa-agent/SKILL.md +43 -0
- package/.agent/skills/qa-agent/resources/checklist.md +294 -0
- package/.agent/skills/qa-agent/resources/error-playbook.md +95 -0
- package/.agent/skills/qa-agent/resources/examples.md +100 -0
- package/.agent/skills/qa-agent/resources/execution-protocol.md +50 -0
- package/.agent/skills/qa-agent/resources/self-check.md +27 -0
- package/.agent/skills/workflow-guide/SKILL.md +57 -0
- package/.agent/skills/workflow-guide/resources/examples.md +68 -0
- package/README.ko.md +459 -0
- package/README.md +563 -0
- package/bin/cli.js +205 -0
- package/package.json +75 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screen Template for Mobile Agent (Flutter)
|
|
3
|
+
*
|
|
4
|
+
* This template demonstrates best practices for Flutter screens.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import 'package:flutter/material.dart';
|
|
8
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
9
|
+
|
|
10
|
+
// Provider definition (in separate file: providers/example_providers.dart)
|
|
11
|
+
/*
|
|
12
|
+
final exampleProvider = FutureProvider<List<ExampleModel>>((ref) async {
|
|
13
|
+
final repository = ref.watch(exampleRepositoryProvider);
|
|
14
|
+
return repository.fetchData();
|
|
15
|
+
});
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/// Example screen demonstrating common patterns
|
|
19
|
+
class ExampleScreen extends ConsumerStatefulWidget {
|
|
20
|
+
/// Route name for navigation
|
|
21
|
+
static const routeName = '/example';
|
|
22
|
+
|
|
23
|
+
/// Constructor
|
|
24
|
+
const ExampleScreen({super.key});
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
ConsumerState<ExampleScreen> createState() => _ExampleScreenState();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class _ExampleScreenState extends ConsumerState<ExampleScreen> {
|
|
31
|
+
// Local state (if needed)
|
|
32
|
+
final _scrollController = ScrollController();
|
|
33
|
+
bool _showScrollToTop = false;
|
|
34
|
+
|
|
35
|
+
@override
|
|
36
|
+
void initState() {
|
|
37
|
+
super.initState();
|
|
38
|
+
_setupScrollListener();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
void dispose() {
|
|
43
|
+
_scrollController.dispose();
|
|
44
|
+
super.dispose();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void _setupScrollListener() {
|
|
48
|
+
_scrollController.addListener(() {
|
|
49
|
+
final shouldShow = _scrollController.offset > 200;
|
|
50
|
+
if (shouldShow != _showScrollToTop) {
|
|
51
|
+
setState(() => _showScrollToTop = shouldShow);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@override
|
|
57
|
+
Widget build(BuildContext context) {
|
|
58
|
+
// Watch providers
|
|
59
|
+
final dataAsync = ref.watch(exampleProvider);
|
|
60
|
+
|
|
61
|
+
return Scaffold(
|
|
62
|
+
// App bar
|
|
63
|
+
appBar: AppBar(
|
|
64
|
+
title: const Text('Example Screen'),
|
|
65
|
+
actions: [
|
|
66
|
+
IconButton(
|
|
67
|
+
icon: const Icon(Icons.refresh),
|
|
68
|
+
onPressed: () => ref.invalidate(exampleProvider),
|
|
69
|
+
tooltip: 'Refresh',
|
|
70
|
+
),
|
|
71
|
+
IconButton(
|
|
72
|
+
icon: const Icon(Icons.settings),
|
|
73
|
+
onPressed: _navigateToSettings,
|
|
74
|
+
tooltip: 'Settings',
|
|
75
|
+
),
|
|
76
|
+
],
|
|
77
|
+
),
|
|
78
|
+
|
|
79
|
+
// Body with async handling
|
|
80
|
+
body: dataAsync.when(
|
|
81
|
+
// Success state
|
|
82
|
+
data: (items) => _buildContent(items),
|
|
83
|
+
|
|
84
|
+
// Loading state
|
|
85
|
+
loading: () => const Center(
|
|
86
|
+
child: CircularProgressIndicator(),
|
|
87
|
+
),
|
|
88
|
+
|
|
89
|
+
// Error state
|
|
90
|
+
error: (error, stackTrace) => _buildErrorState(error),
|
|
91
|
+
),
|
|
92
|
+
|
|
93
|
+
// Floating action button
|
|
94
|
+
floatingActionButton: _showScrollToTop
|
|
95
|
+
? FloatingActionButton(
|
|
96
|
+
onPressed: _scrollToTop,
|
|
97
|
+
child: const Icon(Icons.arrow_upward),
|
|
98
|
+
)
|
|
99
|
+
: null,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Builds main content
|
|
104
|
+
Widget _buildContent(List<dynamic> items) {
|
|
105
|
+
if (items.isEmpty) {
|
|
106
|
+
return _buildEmptyState();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return RefreshIndicator(
|
|
110
|
+
onRefresh: _handleRefresh,
|
|
111
|
+
child: CustomScrollView(
|
|
112
|
+
controller: _scrollController,
|
|
113
|
+
slivers: [
|
|
114
|
+
// Header
|
|
115
|
+
SliverToBoxAdapter(
|
|
116
|
+
child: Padding(
|
|
117
|
+
padding: const EdgeInsets.all(16),
|
|
118
|
+
child: Text(
|
|
119
|
+
'${items.length} Items',
|
|
120
|
+
style: Theme.of(context).textTheme.titleMedium,
|
|
121
|
+
),
|
|
122
|
+
),
|
|
123
|
+
),
|
|
124
|
+
|
|
125
|
+
// List
|
|
126
|
+
SliverList(
|
|
127
|
+
delegate: SliverChildBuilderDelegate(
|
|
128
|
+
(context, index) {
|
|
129
|
+
final item = items[index];
|
|
130
|
+
return _buildListItem(item, index);
|
|
131
|
+
},
|
|
132
|
+
childCount: items.length,
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
],
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Builds individual list item
|
|
141
|
+
Widget _buildListItem(dynamic item, int index) {
|
|
142
|
+
return Card(
|
|
143
|
+
margin: const EdgeInsets.symmetric(
|
|
144
|
+
horizontal: 16,
|
|
145
|
+
vertical: 8,
|
|
146
|
+
),
|
|
147
|
+
child: ListTile(
|
|
148
|
+
leading: CircleAvatar(
|
|
149
|
+
child: Text('${index + 1}'),
|
|
150
|
+
),
|
|
151
|
+
title: Text(item.title ?? 'Untitled'),
|
|
152
|
+
subtitle: Text(item.description ?? ''),
|
|
153
|
+
trailing: IconButton(
|
|
154
|
+
icon: const Icon(Icons.chevron_right),
|
|
155
|
+
onPressed: () => _navigateToDetail(item),
|
|
156
|
+
),
|
|
157
|
+
onTap: () => _navigateToDetail(item),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Builds empty state
|
|
163
|
+
Widget _buildEmptyState() {
|
|
164
|
+
return Center(
|
|
165
|
+
child: Column(
|
|
166
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
167
|
+
children: [
|
|
168
|
+
Icon(
|
|
169
|
+
Icons.inbox,
|
|
170
|
+
size: 64,
|
|
171
|
+
color: Theme.of(context).colorScheme.secondary,
|
|
172
|
+
),
|
|
173
|
+
const SizedBox(height: 16),
|
|
174
|
+
Text(
|
|
175
|
+
'No items yet',
|
|
176
|
+
style: Theme.of(context).textTheme.titleLarge,
|
|
177
|
+
),
|
|
178
|
+
const SizedBox(height: 8),
|
|
179
|
+
Text(
|
|
180
|
+
'Add your first item to get started',
|
|
181
|
+
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
182
|
+
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
|
183
|
+
),
|
|
184
|
+
),
|
|
185
|
+
const SizedBox(height: 24),
|
|
186
|
+
ElevatedButton.icon(
|
|
187
|
+
onPressed: _showAddDialog,
|
|
188
|
+
icon: const Icon(Icons.add),
|
|
189
|
+
label: const Text('Add Item'),
|
|
190
|
+
),
|
|
191
|
+
],
|
|
192
|
+
),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// Builds error state
|
|
197
|
+
Widget _buildErrorState(Object error) {
|
|
198
|
+
return Center(
|
|
199
|
+
child: Padding(
|
|
200
|
+
padding: const EdgeInsets.all(24),
|
|
201
|
+
child: Column(
|
|
202
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
203
|
+
children: [
|
|
204
|
+
Icon(
|
|
205
|
+
Icons.error_outline,
|
|
206
|
+
size: 64,
|
|
207
|
+
color: Theme.of(context).colorScheme.error,
|
|
208
|
+
),
|
|
209
|
+
const SizedBox(height: 16),
|
|
210
|
+
Text(
|
|
211
|
+
'Oops! Something went wrong',
|
|
212
|
+
style: Theme.of(context).textTheme.titleLarge,
|
|
213
|
+
textAlign: TextAlign.center,
|
|
214
|
+
),
|
|
215
|
+
const SizedBox(height: 8),
|
|
216
|
+
Text(
|
|
217
|
+
error.toString(),
|
|
218
|
+
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
219
|
+
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
|
220
|
+
),
|
|
221
|
+
textAlign: TextAlign.center,
|
|
222
|
+
),
|
|
223
|
+
const SizedBox(height: 24),
|
|
224
|
+
ElevatedButton.icon(
|
|
225
|
+
onPressed: () => ref.invalidate(exampleProvider),
|
|
226
|
+
icon: const Icon(Icons.refresh),
|
|
227
|
+
label: const Text('Try Again'),
|
|
228
|
+
),
|
|
229
|
+
],
|
|
230
|
+
),
|
|
231
|
+
),
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Event handlers
|
|
236
|
+
|
|
237
|
+
Future<void> _handleRefresh() async {
|
|
238
|
+
await ref.refresh(exampleProvider.future);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
void _scrollToTop() {
|
|
242
|
+
_scrollController.animateTo(
|
|
243
|
+
0,
|
|
244
|
+
duration: const Duration(milliseconds: 500),
|
|
245
|
+
curve: Curves.easeOut,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
void _navigateToDetail(dynamic item) {
|
|
250
|
+
Navigator.of(context).pushNamed(
|
|
251
|
+
'/detail',
|
|
252
|
+
arguments: item,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
void _navigateToSettings() {
|
|
257
|
+
Navigator.of(context).pushNamed('/settings');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
void _showAddDialog() {
|
|
261
|
+
showDialog(
|
|
262
|
+
context: context,
|
|
263
|
+
builder: (context) => AlertDialog(
|
|
264
|
+
title: const Text('Add Item'),
|
|
265
|
+
content: TextField(
|
|
266
|
+
decoration: const InputDecoration(
|
|
267
|
+
labelText: 'Title',
|
|
268
|
+
hintText: 'Enter title',
|
|
269
|
+
),
|
|
270
|
+
onSubmitted: (value) {
|
|
271
|
+
// Handle submission
|
|
272
|
+
Navigator.of(context).pop();
|
|
273
|
+
},
|
|
274
|
+
),
|
|
275
|
+
actions: [
|
|
276
|
+
TextButton(
|
|
277
|
+
onPressed: () => Navigator.of(context).pop(),
|
|
278
|
+
child: const Text('Cancel'),
|
|
279
|
+
),
|
|
280
|
+
ElevatedButton(
|
|
281
|
+
onPressed: () {
|
|
282
|
+
// Handle add
|
|
283
|
+
Navigator.of(context).pop();
|
|
284
|
+
},
|
|
285
|
+
child: const Text('Add'),
|
|
286
|
+
),
|
|
287
|
+
],
|
|
288
|
+
),
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Placeholder provider (move to separate file)
|
|
294
|
+
final exampleProvider = FutureProvider<List<dynamic>>((ref) async {
|
|
295
|
+
// Simulate API call
|
|
296
|
+
await Future.delayed(const Duration(seconds: 1));
|
|
297
|
+
return [];
|
|
298
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Mobile Agent - Code Snippets
|
|
2
|
+
|
|
3
|
+
Copy-paste ready patterns. Use these as starting points, adapt to the specific task.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Riverpod AsyncNotifier
|
|
8
|
+
|
|
9
|
+
```dart
|
|
10
|
+
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
11
|
+
|
|
12
|
+
part 'todo_provider.g.dart';
|
|
13
|
+
|
|
14
|
+
@riverpod
|
|
15
|
+
class TodoList extends _$TodoList {
|
|
16
|
+
@override
|
|
17
|
+
Future<List<Todo>> build() async {
|
|
18
|
+
final repository = ref.watch(todoRepositoryProvider);
|
|
19
|
+
return repository.fetchAll();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Future<void> add(String title) async {
|
|
23
|
+
final repository = ref.read(todoRepositoryProvider);
|
|
24
|
+
await repository.create(title);
|
|
25
|
+
ref.invalidateSelf();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Future<void> toggle(String id) async {
|
|
29
|
+
final repository = ref.read(todoRepositoryProvider);
|
|
30
|
+
await repository.toggle(id);
|
|
31
|
+
ref.invalidateSelf();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Future<void> delete(String id) async {
|
|
35
|
+
final repository = ref.read(todoRepositoryProvider);
|
|
36
|
+
await repository.delete(id);
|
|
37
|
+
ref.invalidateSelf();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Screen with AsyncValue
|
|
45
|
+
|
|
46
|
+
```dart
|
|
47
|
+
import 'package:flutter/material.dart';
|
|
48
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
49
|
+
|
|
50
|
+
class TodoListScreen extends ConsumerWidget {
|
|
51
|
+
const TodoListScreen({super.key});
|
|
52
|
+
|
|
53
|
+
@override
|
|
54
|
+
Widget build(BuildContext context, WidgetRef ref) {
|
|
55
|
+
final todosAsync = ref.watch(todoListProvider);
|
|
56
|
+
|
|
57
|
+
return Scaffold(
|
|
58
|
+
appBar: AppBar(title: const Text('Todos')),
|
|
59
|
+
body: todosAsync.when(
|
|
60
|
+
loading: () => const Center(child: CircularProgressIndicator()),
|
|
61
|
+
error: (error, stack) => Center(
|
|
62
|
+
child: Column(
|
|
63
|
+
mainAxisSize: MainAxisSize.min,
|
|
64
|
+
children: [
|
|
65
|
+
Text('Error: $error'),
|
|
66
|
+
const SizedBox(height: 8),
|
|
67
|
+
ElevatedButton(
|
|
68
|
+
onPressed: () => ref.invalidate(todoListProvider),
|
|
69
|
+
child: const Text('Retry'),
|
|
70
|
+
),
|
|
71
|
+
],
|
|
72
|
+
),
|
|
73
|
+
),
|
|
74
|
+
data: (todos) => todos.isEmpty
|
|
75
|
+
? const Center(child: Text('No todos yet'))
|
|
76
|
+
: ListView.builder(
|
|
77
|
+
itemCount: todos.length,
|
|
78
|
+
itemBuilder: (context, index) => TodoItem(todo: todos[index]),
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
floatingActionButton: FloatingActionButton(
|
|
82
|
+
onPressed: () => _showAddDialog(context, ref),
|
|
83
|
+
child: const Icon(Icons.add),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Repository Pattern
|
|
93
|
+
|
|
94
|
+
```dart
|
|
95
|
+
abstract class TodoRepository {
|
|
96
|
+
Future<List<Todo>> fetchAll();
|
|
97
|
+
Future<Todo> create(String title);
|
|
98
|
+
Future<void> toggle(String id);
|
|
99
|
+
Future<void> delete(String id);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class TodoRepositoryImpl implements TodoRepository {
|
|
103
|
+
final Dio _dio;
|
|
104
|
+
|
|
105
|
+
TodoRepositoryImpl(this._dio);
|
|
106
|
+
|
|
107
|
+
@override
|
|
108
|
+
Future<List<Todo>> fetchAll() async {
|
|
109
|
+
final response = await _dio.get('/api/todos');
|
|
110
|
+
return (response.data as List).map((e) => Todo.fromJson(e)).toList();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@override
|
|
114
|
+
Future<Todo> create(String title) async {
|
|
115
|
+
final response = await _dio.post('/api/todos', data: {'title': title});
|
|
116
|
+
return Todo.fromJson(response.data);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@override
|
|
120
|
+
Future<void> toggle(String id) async {
|
|
121
|
+
await _dio.patch('/api/todos/$id/toggle');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@override
|
|
125
|
+
Future<void> delete(String id) async {
|
|
126
|
+
await _dio.delete('/api/todos/$id');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Dio with Auth Interceptor
|
|
134
|
+
|
|
135
|
+
```dart
|
|
136
|
+
class AuthInterceptor extends Interceptor {
|
|
137
|
+
final Ref _ref;
|
|
138
|
+
|
|
139
|
+
AuthInterceptor(this._ref);
|
|
140
|
+
|
|
141
|
+
@override
|
|
142
|
+
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
|
143
|
+
final token = _ref.read(authProvider).accessToken;
|
|
144
|
+
if (token != null) {
|
|
145
|
+
options.headers['Authorization'] = 'Bearer $token';
|
|
146
|
+
}
|
|
147
|
+
handler.next(options);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@override
|
|
151
|
+
void onError(DioException err, ErrorInterceptorHandler handler) {
|
|
152
|
+
if (err.response?.statusCode == 401) {
|
|
153
|
+
_ref.read(authProvider.notifier).logout();
|
|
154
|
+
}
|
|
155
|
+
handler.next(err);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## GoRouter Config
|
|
163
|
+
|
|
164
|
+
```dart
|
|
165
|
+
final routerProvider = Provider<GoRouter>((ref) {
|
|
166
|
+
final auth = ref.watch(authProvider);
|
|
167
|
+
|
|
168
|
+
return GoRouter(
|
|
169
|
+
redirect: (context, state) {
|
|
170
|
+
final isLoggedIn = auth.isAuthenticated;
|
|
171
|
+
final isAuthRoute = state.matchedLocation.startsWith('/auth');
|
|
172
|
+
|
|
173
|
+
if (!isLoggedIn && !isAuthRoute) return '/auth/login';
|
|
174
|
+
if (isLoggedIn && isAuthRoute) return '/';
|
|
175
|
+
return null;
|
|
176
|
+
},
|
|
177
|
+
routes: [
|
|
178
|
+
GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
|
|
179
|
+
GoRoute(path: '/auth/login', builder: (_, __) => const LoginScreen()),
|
|
180
|
+
GoRoute(path: '/todos', builder: (_, __) => const TodoListScreen()),
|
|
181
|
+
],
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Entity (freezed)
|
|
189
|
+
|
|
190
|
+
```dart
|
|
191
|
+
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
192
|
+
|
|
193
|
+
part 'todo.freezed.dart';
|
|
194
|
+
part 'todo.g.dart';
|
|
195
|
+
|
|
196
|
+
@freezed
|
|
197
|
+
class Todo with _$Todo {
|
|
198
|
+
const factory Todo({
|
|
199
|
+
required String id,
|
|
200
|
+
required String title,
|
|
201
|
+
@Default(false) bool completed,
|
|
202
|
+
required DateTime createdAt,
|
|
203
|
+
}) = _Todo;
|
|
204
|
+
|
|
205
|
+
factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Widget Test
|
|
212
|
+
|
|
213
|
+
```dart
|
|
214
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
215
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
216
|
+
|
|
217
|
+
void main() {
|
|
218
|
+
testWidgets('TodoListScreen shows loading then data', (tester) async {
|
|
219
|
+
await tester.pumpWidget(
|
|
220
|
+
ProviderScope(
|
|
221
|
+
overrides: [
|
|
222
|
+
todoListProvider.overrideWith(() => MockTodoList()),
|
|
223
|
+
],
|
|
224
|
+
child: const MaterialApp(home: TodoListScreen()),
|
|
225
|
+
),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
|
229
|
+
|
|
230
|
+
await tester.pumpAndSettle();
|
|
231
|
+
|
|
232
|
+
expect(find.text('Test Todo'), findsOneWidget);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Mobile Agent - Tech Stack Reference
|
|
2
|
+
|
|
3
|
+
## Flutter (Recommended)
|
|
4
|
+
- **Framework**: Flutter 3.19+
|
|
5
|
+
- **Language**: Dart 3.3+
|
|
6
|
+
- **State**: Riverpod 2.4+, Bloc, Provider
|
|
7
|
+
- **Navigation**: GoRouter 13+
|
|
8
|
+
- **API Client**: Dio
|
|
9
|
+
- **Local Storage**: Drift, Hive
|
|
10
|
+
- **Testing**: flutter_test, mockito
|
|
11
|
+
|
|
12
|
+
## React Native (Alternative)
|
|
13
|
+
- **Framework**: React Native 0.73+
|
|
14
|
+
- **Language**: TypeScript
|
|
15
|
+
- **State**: Redux Toolkit, Zustand
|
|
16
|
+
- **Navigation**: React Navigation 6+
|
|
17
|
+
- **Testing**: Jest, React Native Testing Library
|
|
18
|
+
|
|
19
|
+
## Project Structure (Flutter)
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
lib/
|
|
23
|
+
main.dart
|
|
24
|
+
core/ # Theme, router, utils
|
|
25
|
+
features/
|
|
26
|
+
[feature]/
|
|
27
|
+
data/ # Models, repositories
|
|
28
|
+
domain/ # Entities, use cases
|
|
29
|
+
presentation/ # Screens, widgets, providers
|
|
30
|
+
shared/ # Shared widgets
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Architecture Pattern
|
|
34
|
+
|
|
35
|
+
Clean Architecture with Riverpod:
|
|
36
|
+
1. Entity (Domain) - Pure business objects
|
|
37
|
+
2. Repository Interface (Domain) - Abstract data access
|
|
38
|
+
3. Repository Implementation (Data) - Dio, database
|
|
39
|
+
4. Providers (Presentation) - State management
|
|
40
|
+
5. Screens/Widgets (Presentation) - UI
|
|
41
|
+
|
|
42
|
+
## Platform Guidelines
|
|
43
|
+
- Material Design 3 for Android
|
|
44
|
+
- iOS Human Interface Guidelines for iOS
|
|
45
|
+
- Use `Platform.isIOS` for platform-specific code
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orchestrator
|
|
3
|
+
description: Automated multi-agent orchestrator that spawns CLI subagents in parallel, coordinates via MCP Memory, and monitors progress
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Orchestrator - Automated Multi-Agent Coordinator
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
- Complex feature requires multiple specialized agents working in parallel
|
|
10
|
+
- User wants automated execution without manually spawning agents
|
|
11
|
+
- Full-stack implementation spanning backend, frontend, mobile, and QA
|
|
12
|
+
- User says "자동으로 실행해줘", "병렬로 돌려줘", or similar automation requests
|
|
13
|
+
|
|
14
|
+
## When NOT to use
|
|
15
|
+
- Simple single-domain task -> use the specific agent directly
|
|
16
|
+
- User wants step-by-step manual control -> use workflow-guide
|
|
17
|
+
- Quick bug fixes or minor changes
|
|
18
|
+
|
|
19
|
+
## Important
|
|
20
|
+
This skill orchestrates CLI subagents via `gemini -p "..." --approval-mode=yolo`. It uses MCP Memory tools as a shared state bus. Each subagent runs as an independent process.
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
| Setting | Default | Description |
|
|
25
|
+
|---------|---------|-------------|
|
|
26
|
+
| MAX_PARALLEL | 3 | Max concurrent subagents |
|
|
27
|
+
| MAX_RETRIES | 2 | Retry attempts per failed task |
|
|
28
|
+
| POLL_INTERVAL | 30s | Status check interval |
|
|
29
|
+
| MAX_TURNS (impl) | 20 | Turn limit for backend/frontend/mobile |
|
|
30
|
+
| MAX_TURNS (review) | 15 | Turn limit for qa/debug |
|
|
31
|
+
| MAX_TURNS (plan) | 10 | Turn limit for pm |
|
|
32
|
+
|
|
33
|
+
## Memory Configuration
|
|
34
|
+
|
|
35
|
+
Memory provider and tool names are configurable via `mcp.json`:
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"memoryConfig": {
|
|
39
|
+
"provider": "serena",
|
|
40
|
+
"basePath": ".serena/memories",
|
|
41
|
+
"tools": {
|
|
42
|
+
"read": "read_memory",
|
|
43
|
+
"write": "write_memory",
|
|
44
|
+
"edit": "edit_memory"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Workflow Phases
|
|
51
|
+
|
|
52
|
+
**PHASE 1 - Plan**: Analyze request -> decompose tasks -> generate session ID
|
|
53
|
+
**PHASE 2 - Setup**: Use memory write tool to create `orchestrator-session.md` + `task-board.md`
|
|
54
|
+
**PHASE 3 - Execute**: Spawn agents by priority tier (never exceed MAX_PARALLEL)
|
|
55
|
+
**PHASE 4 - Monitor**: Poll every POLL_INTERVAL; handle completed/failed/crashed agents
|
|
56
|
+
**PHASE 4.5 - Verify**: Run `../_shared/verify.sh {agent-type} {workspace}` per completed agent
|
|
57
|
+
**PHASE 5 - Collect**: Read all `result-{agent}.md`, compile summary, cleanup progress files
|
|
58
|
+
|
|
59
|
+
See `resources/subagent-prompt-template.md` for prompt construction.
|
|
60
|
+
See `resources/memory-schema.md` for memory file formats.
|
|
61
|
+
|
|
62
|
+
## Memory File Ownership
|
|
63
|
+
|
|
64
|
+
| File | Owner | Others |
|
|
65
|
+
|------|-------|--------|
|
|
66
|
+
| `orchestrator-session.md` | orchestrator | read-only |
|
|
67
|
+
| `task-board.md` | orchestrator | read-only |
|
|
68
|
+
| `progress-{agent}.md` | that agent | orchestrator reads |
|
|
69
|
+
| `result-{agent}.md` | that agent | orchestrator reads |
|
|
70
|
+
|
|
71
|
+
## Verification Gate (PHASE 4.5)
|
|
72
|
+
After each agent completes, run automated verification before accepting the result:
|
|
73
|
+
```bash
|
|
74
|
+
bash .agent/skills/_shared/verify.sh {agent-type} {workspace}
|
|
75
|
+
```
|
|
76
|
+
- **PASS (exit 0)**: Accept result, advance to next task
|
|
77
|
+
- **FAIL (exit 1)**: Treat as failure → enter Retry Logic with verify output as error context
|
|
78
|
+
- This is mandatory. Never skip verification even if the agent reports success.
|
|
79
|
+
|
|
80
|
+
## Retry Logic
|
|
81
|
+
- 1st retry: Wait 30s, re-spawn with error context (include verify.sh output)
|
|
82
|
+
- 2nd retry: Wait 60s, add "Try a different approach"
|
|
83
|
+
- Final failure: Report to user, ask whether to continue or abort
|
|
84
|
+
|
|
85
|
+
## References
|
|
86
|
+
- Prompt template: `resources/subagent-prompt-template.md`
|
|
87
|
+
- Memory schema: `resources/memory-schema.md`
|
|
88
|
+
- Config: `config/cli-config.yaml`
|
|
89
|
+
- Scripts: `scripts/spawn-agent.sh`, `scripts/parallel-run.sh`
|
|
90
|
+
- Task templates: `templates/`
|
|
91
|
+
- Skill routing: `../_shared/skill-routing.md`
|
|
92
|
+
- Verification: `../_shared/verify.sh`
|
|
93
|
+
- API contracts: `../_shared/api-contracts/`
|
|
94
|
+
- Context loading: `../_shared/context-loading.md`
|
|
95
|
+
- Difficulty guide: `../_shared/difficulty-guide.md`
|
|
96
|
+
- Reasoning templates: `../_shared/reasoning-templates.md`
|
|
97
|
+
- Clarification protocol: `../_shared/clarification-protocol.md`
|
|
98
|
+
- Context budget: `../_shared/context-budget.md`
|
|
99
|
+
- Lessons learned: `../_shared/lessons-learned.md`
|