ai-flow-dev 2.6.0 → 2.8.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 (33) hide show
  1. package/README.md +24 -21
  2. package/package.json +6 -6
  3. package/prompts/backend/flow-check-review.md +648 -12
  4. package/prompts/backend/flow-check-test.md +520 -8
  5. package/prompts/backend/flow-check.md +687 -29
  6. package/prompts/backend/flow-commit.md +18 -49
  7. package/prompts/backend/flow-finish.md +919 -0
  8. package/prompts/backend/flow-release.md +949 -0
  9. package/prompts/backend/flow-work.md +296 -221
  10. package/prompts/desktop/flow-check-review.md +648 -12
  11. package/prompts/desktop/flow-check-test.md +520 -8
  12. package/prompts/desktop/flow-check.md +687 -29
  13. package/prompts/desktop/flow-commit.md +18 -49
  14. package/prompts/desktop/flow-finish.md +910 -0
  15. package/prompts/desktop/flow-release.md +662 -0
  16. package/prompts/desktop/flow-work.md +398 -219
  17. package/prompts/frontend/flow-check-review.md +648 -12
  18. package/prompts/frontend/flow-check-test.md +520 -8
  19. package/prompts/frontend/flow-check.md +687 -29
  20. package/prompts/frontend/flow-commit.md +18 -49
  21. package/prompts/frontend/flow-finish.md +910 -0
  22. package/prompts/frontend/flow-release.md +519 -0
  23. package/prompts/frontend/flow-work-api.md +1540 -0
  24. package/prompts/frontend/flow-work.md +774 -218
  25. package/prompts/mobile/flow-check-review.md +648 -12
  26. package/prompts/mobile/flow-check-test.md +520 -8
  27. package/prompts/mobile/flow-check.md +687 -29
  28. package/prompts/mobile/flow-commit.md +18 -49
  29. package/prompts/mobile/flow-finish.md +910 -0
  30. package/prompts/mobile/flow-release.md +751 -0
  31. package/prompts/mobile/flow-work-api.md +1493 -0
  32. package/prompts/mobile/flow-work.md +792 -222
  33. package/templates/AGENT.template.md +1 -1
@@ -0,0 +1,1493 @@
1
+ ---
2
+ description: Analyze OpenAPI specification to extract complete module metadata for mobile CRUD generation (React Native)
3
+ ---
4
+
5
+ # API Module Analyzer (Sub-Prompt - Mobile)
6
+
7
+ **YOU ARE AN EXPERT API ANALYZER specialized in extracting comprehensive metadata from OpenAPI specifications for React Native mobile code generation.**
8
+
9
+ ## ⚠️ IMPORTANT: Internal Sub-Prompt
10
+
11
+ **DO NOT invoke this prompt directly.** This is an internal sub-prompt called by `/flow-work`.
12
+
13
+ **To use API Module Analysis (mobile), run:**
14
+
15
+ ```bash
16
+ /flow-work api <module-name>
17
+ # Example: /flow-work api users
18
+ ```
19
+
20
+ **Why not call directly?**
21
+
22
+ - `/flow-work` manages URL cache (`.ai-flow/cache/api-config.json`)
23
+ - `/flow-work` handles connection errors with interactive retry
24
+ - `/flow-work` validates URL before analysis
25
+ - This sub-prompt expects a **pre-validated URL** as input
26
+
27
+ **Architecture:**
28
+
29
+ - `flow-work` = Orchestrator (stateful, manages cache)
30
+ - `flow-work-api` = Analyzer (stateless, pure function)
31
+
32
+ ---
33
+
34
+ ## Invocation Context
35
+
36
+ This sub-prompt is automatically invoked by `/flow-work` when the pattern `api [module-name]` is detected.
37
+
38
+ ## Purpose
39
+
40
+ Parse OpenAPI backend specification and return structured analysis data that `flow-work` will use to:
41
+
42
+ 1. Generate detailed `work.md` with field specifications
43
+ 2. Execute CRUD implementation with mobile-specific patterns (React Native)
44
+ 3. Ensure type-safety between mobile app and backend
45
+
46
+ ---
47
+
48
+ ## Input Parameters
49
+
50
+ Received from parent prompt (flow-work):
51
+
52
+ ```typescript
53
+ interface ApiModuleInput {
54
+ module: string; // 'users', 'organizations', 'audit-logs'
55
+ apiUrl?: string; // Override default OpenAPI endpoint
56
+ }
57
+ ```
58
+
59
+ **Default API URL**: `http://localhost:3001/api/docs-json`
60
+ **Override**: User can specify `--api-url=http://other-host:3000/api/docs`
61
+
62
+ ---
63
+
64
+ ## Phase 0: API Analysis (Automatic)
65
+
66
+ ### 0.1. Fetch OpenAPI Specification (Robust)
67
+
68
+ **CRITICAL: Handle connection errors, timeouts, and network issues.**
69
+
70
+ ```typescript
71
+ async function fetchOpenAPISpec(url: string): Promise<OpenAPISpec> {
72
+ try {
73
+ const controller = new AbortController();
74
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
75
+
76
+ const response = await fetch(url, {
77
+ signal: controller.signal,
78
+ headers: { Accept: 'application/json' },
79
+ });
80
+
81
+ clearTimeout(timeoutId);
82
+
83
+ if (!response.ok) {
84
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
85
+ }
86
+
87
+ const spec = await response.json();
88
+
89
+ // Validate OpenAPI version
90
+ if (!spec.openapi && !spec.swagger) {
91
+ throw new Error('Invalid OpenAPI/Swagger specification');
92
+ }
93
+
94
+ return spec;
95
+ } catch (error) {
96
+ if (error.name === 'AbortError') {
97
+ throw new Error('API documentation server timeout. Ensure backend is running.');
98
+ }
99
+
100
+ if (error.message.includes('Network request failed')) {
101
+ throw new Error('Network error. Check device connectivity or backend URL.');
102
+ }
103
+
104
+ throw error;
105
+ }
106
+ }
107
+ ```
108
+
109
+ **IF fetch fails:**
110
+
111
+ ```
112
+ ❌ Failed to fetch OpenAPI spec from http://localhost:3001/api/docs-json
113
+
114
+ Error: API documentation server timeout. Ensure backend is running.
115
+
116
+ Options:
117
+ A) Retry with different URL
118
+ B) Use cached spec (if available)
119
+ C) Proceed with manual type definitions
120
+ D) Cancel
121
+
122
+ Your choice: _
123
+ ```
124
+
125
+ **IF successful, show spec version:**
126
+
127
+ ```
128
+ ✅ OpenAPI Specification Loaded
129
+
130
+ Version: OpenAPI 3.0.3
131
+ Title: CROSS Mobile API
132
+ Paths: 45 endpoints detected
133
+ Schemas: 32 types available
134
+
135
+ Proceeding with analysis...
136
+ ```
137
+
138
+ ### 0.2. Extract Module Endpoints (Filtered)
139
+
140
+ **CRITICAL: Extract ONLY the target module endpoints. Do NOT extract all API modules.**
141
+
142
+ Identify all endpoints for the specified module:
143
+
144
+ ```typescript
145
+ // Example for "users" module
146
+ const targetModule = 'users'; // From user input
147
+
148
+ const moduleEndpoints = filterEndpoints(spec.paths, {
149
+ tags: [capitalizeFirst(targetModule)], // ['Users']
150
+ pathPrefix: `/api/${targetModule}`, // '/api/users'
151
+ });
152
+
153
+ // Result:
154
+ {
155
+ list: 'GET /api/users',
156
+ create: 'POST /api/users',
157
+ get: 'GET /api/users/{id}',
158
+ update: 'PUT /api/users/{id}',
159
+ delete: 'DELETE /api/users/{id}',
160
+ // Additional endpoints:
161
+ getMe: 'GET /api/users/me',
162
+ updateMe: 'PUT /api/users/me',
163
+ changePassword: 'PUT /api/users/me/password',
164
+ }
165
+ ```
166
+
167
+ **⚠️ IMPORTANT**: Do NOT include endpoints from other modules like `/api/organizations`, `/api/audit-logs`, etc. Only the target module.
168
+
169
+ ### 0.3. Detect Pagination Response Format
170
+
171
+ **Analyze the response schema for list endpoints:**
172
+
173
+ ```typescript
174
+ function detectPaginationFormat(endpoint: OpenAPIEndpoint): PaginationFormat {
175
+ const responseSchema = endpoint.responses['200'].schema;
176
+
177
+ // Check if response is object with data/items property
178
+ if (responseSchema.type === 'object') {
179
+ if (responseSchema.properties?.items && responseSchema.properties?.total) {
180
+ return {
181
+ type: 'object',
182
+ dataKey: 'items',
183
+ totalKey: 'total',
184
+ pageKey: responseSchema.properties.page ? 'page' : null,
185
+ };
186
+ }
187
+
188
+ if (responseSchema.properties?.data && responseSchema.properties?.meta) {
189
+ return {
190
+ type: 'object',
191
+ dataKey: 'data',
192
+ totalKey: 'meta.total',
193
+ pageKey: 'meta.page',
194
+ };
195
+ }
196
+ }
197
+
198
+ // Response is array directly
199
+ if (responseSchema.type === 'array') {
200
+ return {
201
+ type: 'array',
202
+ dataKey: null,
203
+ totalKey: null, // Client-side pagination only
204
+ };
205
+ }
206
+
207
+ throw new Error('Unable to detect pagination format from OpenAPI schema');
208
+ }
209
+ ```
210
+
211
+ ### 0.4. Extract Complete Field Specifications (Detailed)
212
+
213
+ **For EACH endpoint, extract FULL field specifications:**
214
+
215
+ ```typescript
216
+ interface FieldSpec {
217
+ name: string;
218
+ type: 'string' | 'number' | 'boolean' | 'date' | 'enum' | 'array' | 'object';
219
+ required: boolean;
220
+ nullable: boolean;
221
+ validation?: {
222
+ min?: number;
223
+ max?: number;
224
+ pattern?: string;
225
+ format?: 'email' | 'url' | 'uuid' | 'date-time';
226
+ enum?: string[];
227
+ };
228
+ relation?: {
229
+ entity: string;
230
+ type: 'one-to-one' | 'many-to-one' | 'one-to-many' | 'many-to-many';
231
+ populated: boolean; // Si el backend devuelve el objeto completo
232
+ displayField: string; // Campo a mostrar (e.g., "name", "email")
233
+ };
234
+ default?: any;
235
+ description?: string;
236
+ }
237
+ ```
238
+
239
+ **Extract from OpenAPI schema:**
240
+
241
+ ```typescript
242
+ // Example extraction
243
+ const userSchema = spec.components.schemas.UserResponseDto;
244
+
245
+ const fields: FieldSpec[] = Object.entries(userSchema.properties).map(([name, prop]) => ({
246
+ name,
247
+ type: mapOpenAPIType(prop.type, prop.format),
248
+ required: userSchema.required?.includes(name) ?? false,
249
+ nullable: prop.nullable ?? false,
250
+ validation: extractValidation(prop),
251
+ relation: detectRelation(name, prop, spec),
252
+ default: prop.default,
253
+ description: prop.description,
254
+ }));
255
+
256
+ // Helper: Detect relations
257
+ function detectRelation(fieldName: string, prop: any, spec: OpenAPISpec) {
258
+ // Pattern 1: Field ends with "Id" → Foreign Key
259
+ if (fieldName.endsWith('Id') && prop.type === 'string') {
260
+ const entityName = fieldName.slice(0, -2); // "roleId" → "role"
261
+ return {
262
+ entity: entityName,
263
+ type: 'many-to-one',
264
+ populated: false,
265
+ displayField: 'name', // Default assumption
266
+ };
267
+ }
268
+
269
+ // Pattern 2: Field is object with $ref → Populated relation
270
+ if (prop.$ref) {
271
+ const refSchema = resolveRef(prop.$ref, spec);
272
+ return {
273
+ entity: extractEntityName(prop.$ref),
274
+ type: 'many-to-one',
275
+ populated: true,
276
+ displayField: detectDisplayField(refSchema), // Smart detection
277
+ };
278
+ }
279
+
280
+ // Pattern 3: Array of objects → One-to-many or Many-to-many
281
+ if (prop.type === 'array' && prop.items?.$ref) {
282
+ return {
283
+ entity: extractEntityName(prop.items.$ref),
284
+ type: 'one-to-many', // or 'many-to-many' based on naming
285
+ populated: true,
286
+ displayField: 'name',
287
+ };
288
+ }
289
+
290
+ return null;
291
+ }
292
+
293
+ // Helper: Detect which field to display (smart heuristic)
294
+ function detectDisplayField(schema: any): string {
295
+ const commonDisplayFields = ['name', 'title', 'label', 'email', 'username'];
296
+ for (const field of commonDisplayFields) {
297
+ if (schema.properties?.[field]) return field;
298
+ }
299
+ return 'id'; // Fallback
300
+ }
301
+ ```
302
+
303
+ ### 0.5. Categorize Fields by Usage
304
+
305
+ **Auto-categorize fields based on patterns:**
306
+
307
+ ```typescript
308
+ function categorizeField(field: FieldSpec, dto: 'response' | 'create' | 'update'): FieldCategory {
309
+ const autoGeneratedPatterns = ['id', 'createdAt', 'updatedAt', 'deletedAt', 'version'];
310
+ const readOnlyPatterns = ['lastLoginAt', 'emailVerifiedAt', '_count', 'computed'];
311
+ const metadataPatterns = ['metadata', 'config', 'settings'];
312
+
313
+ // Auto-generated (never editable)
314
+ if (autoGeneratedPatterns.includes(field.name)) {
315
+ return {
316
+ category: 'auto-generated',
317
+ showInList: field.name === 'id' ? false : true, // Mobile uses "List" not "Table"
318
+ showInForm: false,
319
+ showInDetails: true,
320
+ editable: false,
321
+ };
322
+ }
323
+
324
+ // Read-only (show but not editable)
325
+ if (readOnlyPatterns.some((p) => field.name.includes(p))) {
326
+ return {
327
+ category: 'read-only',
328
+ showInList: true,
329
+ showInForm: false,
330
+ showInDetails: true,
331
+ editable: false,
332
+ };
333
+ }
334
+
335
+ // Metadata (advanced users only)
336
+ if (metadataPatterns.includes(field.name)) {
337
+ return {
338
+ category: 'metadata',
339
+ showInList: false,
340
+ showInForm: false, // Or in advanced section
341
+ showInDetails: true,
342
+ editable: true,
343
+ };
344
+ }
345
+
346
+ // Editable (normal fields)
347
+ return {
348
+ category: 'editable',
349
+ showInList: true,
350
+ showInForm: dto === 'response' ? false : true,
351
+ showInDetails: true,
352
+ editable: dto === 'update' ? true : false,
353
+ };
354
+ }
355
+ ```
356
+
357
+ ### 0.6. Extract DTOs and Schemas
358
+
359
+ Analyze `components.schemas` to extract:
360
+
361
+ ```typescript
362
+ interface ModuleSchemas {
363
+ response: Schema; // UserResponseDto
364
+ create: Schema; // CreateUserDto
365
+ update: Schema; // UpdateUserDto
366
+ filters: QueryParams; // page, limit, search, etc.
367
+ }
368
+ ```
369
+
370
+ ### 0.7. Detect Features
371
+
372
+ Auto-detect capabilities:
373
+
374
+ ```yaml
375
+ Features_Detected:
376
+ pagination:
377
+ enabled: true/false
378
+ params: [page, limit]
379
+ response_format: "array" | "{ data, meta }"
380
+
381
+ search:
382
+ enabled: true/false
383
+ params: [search, filter_field_1, filter_field_2]
384
+
385
+ sorting:
386
+ enabled: true/false
387
+ params: [sortBy, order]
388
+
389
+ authentication:
390
+ type: bearer
391
+ required: true
392
+
393
+ authorization:
394
+ roles: [ROOT, OWNER, ADMIN]
395
+ permissions: ['user:read', 'user:write']
396
+
397
+ soft_delete:
398
+ enabled: true/false
399
+ status_field: "status" | "deletedAt"
400
+ ```
401
+
402
+ ### 0.8. Analyze Relationships (Smart Depth)
403
+
404
+ **Extract ONLY direct relationships (depth 1) to avoid analyzing unnecessary modules.**
405
+
406
+ Detect foreign keys and relations:
407
+
408
+ ```typescript
409
+ function extractRelevantRelationships(
410
+ targetModule: string,
411
+ spec: OpenAPISpec,
412
+ maxDepth: number = 1
413
+ ): Relationships[] {
414
+ const targetSchemas = getModuleSchemas(spec, targetModule);
415
+ const relationships: Relationships[] = [];
416
+
417
+ // Only analyze target module schemas
418
+ targetSchemas.forEach(schema => {
419
+ Object.entries(schema.properties).forEach(([fieldName, prop]) => {
420
+ // Detect foreign key (e.g., roleId)
421
+ if (fieldName.endsWith('Id') && prop.type === 'string') {
422
+ const relatedEntity = fieldName.slice(0, -2); // 'roleId' → 'role'
423
+
424
+ relationships.push({
425
+ field: fieldName,
426
+ relatedEntity: capitalizeFirst(relatedEntity),
427
+ type: 'many-to-one',
428
+ foreignKey: fieldName,
429
+ endpoint: `/api/${relatedEntity}s`, // Pluralize
430
+ displayField: 'name', // Default assumption
431
+ populated: false,
432
+ });
433
+ }
434
+
435
+ // Detect populated relation (e.g., role: { $ref: '#/components/schemas/Role' })
436
+ if (prop.$ref) {
437
+ const relatedEntity = extractEntityName(prop.$ref);
438
+
439
+ relationships.push({
440
+ field: fieldName,
441
+ relatedEntity,
442
+ type: 'many-to-one',
443
+ foreignKey: `${fieldName}Id`,
444
+ endpoint: `/api/${fieldName}s`,
445
+ displayField: detectDisplayField(resolveRef(prop.$ref, spec)),
446
+ populated: true,
447
+ });
448
+ }
449
+
450
+ // Detect array relations (e.g., organizations: [{ $ref: '...' }])
451
+ if (prop.type === 'array' && prop.items?.$ref) {
452
+ const relatedEntity = extractEntityName(prop.items.$ref);
453
+
454
+ relationships.push({
455
+ field: fieldName,
456
+ relatedEntity,
457
+ type: 'one-to-many', // or 'many-to-many'
458
+ foreignKey: `${targetModule}Id`,
459
+ endpoint: `/api/${fieldName}`,
460
+ displayField: 'name',
461
+ populated: true,
462
+ });
463
+ }
464
+ });
465
+ });
466
+
467
+ return relationships;
468
+ }
469
+
470
+ // Result for "users" module:
471
+ {
472
+ role: {
473
+ field: 'roleId',
474
+ relatedEntity: 'Role',
475
+ type: "many-to-one",
476
+ foreignKey: "roleId",
477
+ endpoint: "/api/roles",
478
+ displayField: "name",
479
+ populated: false,
480
+ },
481
+ organization: {
482
+ field: 'organizationId',
483
+ relatedEntity: 'Organization',
484
+ type: "many-to-one",
485
+ foreignKey: "organizationId",
486
+ endpoint: "/api/organizations",
487
+ displayField: "name",
488
+ populated: false,
489
+ }
490
+ }
491
+ ```
492
+
493
+ **✅ Only extract schemas for related entities (Role, Organization)**
494
+ **❌ Do NOT extract full CRUD endpoints for related modules**
495
+ **❌ Do NOT analyze deep nested relations (maxDepth = 1)**
496
+
497
+ ---
498
+
499
+ ## Phase 0.9: Current Implementation Audit (CRITICAL)
500
+
501
+ **Compare OpenAPI specification with existing mobile code to detect gaps and errors.**
502
+
503
+ ### Step 1: Check if Feature Exists
504
+
505
+ ```typescript
506
+ const featurePath = `src/features/${targetModule}`;
507
+ const featureExists = await fileExists(featurePath);
508
+
509
+ if (!featureExists) {
510
+ return {
511
+ status: 'NOT_IMPLEMENTED',
512
+ action: 'FULL_IMPLEMENTATION',
513
+ message: `Feature directory does not exist. Full implementation required.`,
514
+ };
515
+ }
516
+ ```
517
+
518
+ ### Step 2: Scan Existing Files
519
+
520
+ ```typescript
521
+ const existingFiles = {
522
+ types: await glob(`${featurePath}/types/**/*.ts`),
523
+ schemas: await glob(`${featurePath}/schemas/**/*.ts`),
524
+ services: await glob(`${featurePath}/services/**/*.ts`),
525
+ hooks: await glob(`${featurePath}/hooks/**/*.ts`),
526
+ components: await glob(`${featurePath}/components/**/*.tsx`),
527
+ screens: await glob(`${featurePath}/screens/**/*.tsx`), // Mobile uses "screens" not "pages"
528
+ tests: await glob(`${featurePath}/**/*.test.{ts,tsx}`),
529
+ };
530
+ ```
531
+
532
+ ### Step 3: Compare Types with OpenAPI Schemas
533
+
534
+ ```typescript
535
+ async function auditTypes(
536
+ openapiSchemas: Schema[],
537
+ existingTypeFiles: string[]
538
+ ): Promise<TypeAuditResult> {
539
+ const audit = {
540
+ matching: [],
541
+ missing: [],
542
+ incorrect: [],
543
+ };
544
+
545
+ for (const schema of openapiSchemas) {
546
+ const typeFile = existingTypeFiles.find(
547
+ (f) => f.includes(schema.name) || f.includes('entities.ts')
548
+ );
549
+
550
+ if (!typeFile) {
551
+ audit.missing.push({
552
+ schema: schema.name,
553
+ action: 'CREATE',
554
+ reason: 'Type definition not found',
555
+ });
556
+ continue;
557
+ }
558
+
559
+ // Parse existing type
560
+ const fileContent = await readFile(typeFile);
561
+ const existingType = parseTypeScriptInterface(fileContent, schema.name);
562
+
563
+ if (!existingType) {
564
+ audit.missing.push({
565
+ schema: schema.name,
566
+ action: 'CREATE',
567
+ reason: `Type not found in ${typeFile}`,
568
+ });
569
+ continue;
570
+ }
571
+
572
+ // Compare fields
573
+ const fieldComparison = compareFields(existingType.fields, schema.properties);
574
+
575
+ if (fieldComparison.hasDifferences) {
576
+ audit.incorrect.push({
577
+ schema: schema.name,
578
+ file: typeFile,
579
+ issues: [
580
+ ...fieldComparison.missingFields.map((f) => `Missing field: ${f}`),
581
+ ...fieldComparison.extraFields.map((f) => `Extra field: ${f}`),
582
+ ...fieldComparison.typeMismatches.map(
583
+ (m) => `Type mismatch for ${m.field}: expected ${m.expected}, got ${m.actual}`
584
+ ),
585
+ ],
586
+ action: 'UPDATE',
587
+ });
588
+ } else {
589
+ audit.matching.push(schema.name);
590
+ }
591
+ }
592
+
593
+ return audit;
594
+ }
595
+ ```
596
+
597
+ ### Step 4: Compare Endpoints with Hooks
598
+
599
+ ```typescript
600
+ async function auditHooks(
601
+ openapiEndpoints: Endpoint[],
602
+ existingHookFiles: string[]
603
+ ): Promise<HookAuditResult> {
604
+ const audit = {
605
+ implemented: [],
606
+ missing: [],
607
+ incorrect: [],
608
+ };
609
+
610
+ // Expected hooks based on endpoints
611
+ const expectedHooks = {
612
+ list: openapiEndpoints.find((e) => e.method === 'GET' && !e.path.includes('{')),
613
+ get: openapiEndpoints.find((e) => e.method === 'GET' && e.path.includes('{id}')),
614
+ create: openapiEndpoints.find((e) => e.method === 'POST'),
615
+ update: openapiEndpoints.find((e) => e.method === 'PUT'),
616
+ delete: openapiEndpoints.find((e) => e.method === 'DELETE'),
617
+ };
618
+
619
+ for (const [hookType, endpoint] of Object.entries(expectedHooks)) {
620
+ if (!endpoint) continue;
621
+
622
+ const expectedHookName = `use${capitalizeFirst(targetModule)}${capitalizeFirst(hookType === 'list' ? '' : hookType)}`;
623
+ const hookFile = existingHookFiles.find((f) => f.includes(expectedHookName));
624
+
625
+ if (!hookFile) {
626
+ audit.missing.push({
627
+ endpoint: `${endpoint.method} ${endpoint.path}`,
628
+ expectedHook: expectedHookName,
629
+ action: 'CREATE',
630
+ });
631
+ continue;
632
+ }
633
+
634
+ // Analyze hook implementation
635
+ const hookContent = await readFile(hookFile);
636
+ const hookAnalysis = analyzeHookCode(hookContent);
637
+
638
+ const issues = [];
639
+
640
+ if (!hookAnalysis.hasQueryKey) {
641
+ issues.push('Missing query key constant');
642
+ }
643
+
644
+ if (!hookAnalysis.isTypeSafe) {
645
+ issues.push('Uses `any` type or missing type annotations');
646
+ }
647
+
648
+ if (!hookAnalysis.hasErrorHandling) {
649
+ issues.push('Missing onError handler');
650
+ }
651
+
652
+ if (
653
+ hookAnalysis.hasCacheInvalidation === false &&
654
+ ['create', 'update', 'delete'].includes(hookType)
655
+ ) {
656
+ issues.push('Missing cache invalidation on success');
657
+ }
658
+
659
+ if (issues.length > 0) {
660
+ audit.incorrect.push({
661
+ hook: expectedHookName,
662
+ file: hookFile,
663
+ issues,
664
+ action: 'FIX',
665
+ });
666
+ } else {
667
+ audit.implemented.push(expectedHookName);
668
+ }
669
+ }
670
+
671
+ return audit;
672
+ }
673
+ ```
674
+
675
+ ### Step 5: Audit UI Components (Mobile-Specific)
676
+
677
+ ```typescript
678
+ async function auditComponents(
679
+ targetModule: string,
680
+ existingComponents: string[]
681
+ ): Promise<ComponentAuditResult> {
682
+ const expectedComponents = [
683
+ `${capitalizeFirst(targetModule)}List.tsx`, // FlatList component
684
+ `${capitalizeFirst(targetModule)}BottomSheet.tsx`, // Mobile uses Bottom Sheet instead of Drawer
685
+ `${capitalizeFirst(targetModule)}Form.tsx`,
686
+ `Delete${capitalizeFirst(targetModule)}Modal.tsx`, // Mobile uses Modal instead of Dialog
687
+ `${capitalizeFirst(targetModule)}Filters.tsx`,
688
+ ];
689
+
690
+ const audit = {
691
+ complete: [],
692
+ missing: [],
693
+ nonStandard: [],
694
+ };
695
+
696
+ for (const expectedComponent of expectedComponents) {
697
+ const componentFile = existingComponents.find((c) => c.endsWith(expectedComponent));
698
+
699
+ if (!componentFile) {
700
+ audit.missing.push({
701
+ component: expectedComponent,
702
+ action: 'CREATE',
703
+ });
704
+ continue;
705
+ }
706
+
707
+ // Analyze component quality
708
+ const componentContent = await readFile(componentFile);
709
+ const componentAnalysis = analyzeComponentCode(componentContent);
710
+
711
+ const issues = [];
712
+
713
+ if (expectedComponent.includes('List') && !componentAnalysis.usesFlatList) {
714
+ issues.push('Should use FlatList (React Native standard)');
715
+ }
716
+
717
+ if (expectedComponent.includes('Form') && !componentAnalysis.usesReactHookForm) {
718
+ issues.push('Should use React Hook Form (project standard)');
719
+ }
720
+
721
+ if (expectedComponent.includes('Form') && !componentAnalysis.usesZodValidation) {
722
+ issues.push('Missing Zod validation');
723
+ }
724
+
725
+ if (!componentAnalysis.hasLoadingStates) {
726
+ issues.push('Missing loading states (ActivityIndicator)');
727
+ }
728
+
729
+ if (!componentAnalysis.hasErrorHandling) {
730
+ issues.push('Missing error handling');
731
+ }
732
+
733
+ if (issues.length > 0) {
734
+ audit.nonStandard.push({
735
+ component: expectedComponent,
736
+ file: componentFile,
737
+ issues,
738
+ action: 'REFACTOR',
739
+ });
740
+ } else {
741
+ audit.complete.push(expectedComponent);
742
+ }
743
+ }
744
+
745
+ return audit;
746
+ }
747
+ ```
748
+
749
+ ### Step 6: Generate Implementation Status Report
750
+
751
+ ```typescript
752
+ function generateStatusReport(audits: AllAudits): ImplementationStatusReport {
753
+ const totalItems =
754
+ audits.types.missing.length +
755
+ audits.types.matching.length +
756
+ audits.types.incorrect.length +
757
+ audits.hooks.missing.length +
758
+ audits.hooks.implemented.length +
759
+ audits.hooks.incorrect.length +
760
+ audits.components.missing.length +
761
+ audits.components.complete.length +
762
+ audits.components.nonStandard.length;
763
+
764
+ const completeItems =
765
+ audits.types.matching.length +
766
+ audits.hooks.implemented.length +
767
+ audits.components.complete.length;
768
+
769
+ const score = Math.round((completeItems / totalItems) * 100);
770
+
771
+ let strategy: ImplementationStrategy;
772
+ if (score < 30) {
773
+ strategy = 'FULL_NEW';
774
+ } else if (score < 70) {
775
+ strategy = 'REFACTOR_COMPLETE';
776
+ } else {
777
+ strategy = 'MINOR_FIXES';
778
+ }
779
+
780
+ return {
781
+ score,
782
+ strategy,
783
+ audits,
784
+ summary: generateSummaryText(audits, score, strategy),
785
+ };
786
+ }
787
+ ```
788
+
789
+ **Report Output (Mobile):**
790
+
791
+ ```
792
+ 📊 Current Implementation Status: Users Module (Mobile)
793
+
794
+ 🗂️ Feature Structure:
795
+ ✅ Directory exists: src/features/users/
796
+
797
+ 📦 Types & Schemas:
798
+ ✅ UserResponseDto (matching OpenAPI)
799
+ ⚠️ CreateUserDto (missing fields: roleId, organizationId)
800
+ ❌ UpdateUserDto (not found - needs creation)
801
+
802
+ 🔧 Zod Schemas:
803
+ ❌ No validation schemas found
804
+
805
+ 🌐 API Services:
806
+ ✅ users.service.ts exists
807
+ ⚠️ Missing endpoints: PUT /users/{id}, DELETE /users/{id}
808
+
809
+ 🪝 Hooks:
810
+ ✅ useUsers (correct, type-safe, has query key)
811
+ ❌ useUserMutations (not found)
812
+ ❌ useCreateUser, useUpdateUser, useDeleteUser (not found)
813
+
814
+ 🎨 UI Components (Mobile):
815
+ ⚠️ UsersList.tsx (exists but doesn't use FlatList optimally - needs refactor)
816
+ ❌ UsersBottomSheet.tsx (not found)
817
+ ❌ UserForm.tsx (not found)
818
+ ❌ DeleteUserModal.tsx (not found)
819
+ ❌ UserFilters.tsx (not found)
820
+
821
+ 📱 Screens:
822
+ ⚠️ UsersScreen.tsx (exists but incomplete)
823
+
824
+ 🧪 Tests:
825
+ ❌ No tests found
826
+
827
+ 📊 Implementation Score: 35/100
828
+
829
+ 💡 Recommendation: REFACTOR + COMPLETE
830
+ - Update 1 existing type (CreateUserDto)
831
+ - Create 1 missing type (UpdateUserDto)
832
+ - Create all validation schemas (3 schemas)
833
+ - Add 2 missing API endpoints
834
+ - Create mutation hooks (3 hooks)
835
+ - Refactor existing list component
836
+ - Create 4 missing components
837
+ - Add comprehensive tests
838
+
839
+ ⏱️ Estimated: 13 SP / 8-10 hours
840
+ ```
841
+
842
+ ---
843
+
844
+ ## Phase 0.5: Project Standards Detection (Mobile-Specific)
845
+
846
+ **CRITICAL: Auto-detect React Native project stack and patterns.**
847
+
848
+ ### 1. Read package.json Dependencies
849
+
850
+ ```typescript
851
+ const packageJson = await readFile('package.json');
852
+ const { dependencies, devDependencies } = JSON.parse(packageJson);
853
+
854
+ // Detect installed libraries (React Native ecosystem)
855
+ const stack = {
856
+ ui: detectUILibrary(dependencies), // react-native-paper, @rneui, native-base
857
+ list: detectListComponent(dependencies), // FlatList (built-in), SectionList, FlashList
858
+ forms: detectFormsLibrary(dependencies), // react-hook-form
859
+ validation: detectValidation(dependencies), // zod, yup
860
+ query: detectDataFetching(dependencies), // @tanstack/react-query
861
+ state: detectStateManagement(dependencies), // zustand, redux, recoil
862
+ navigation: detectNavigation(dependencies), // @react-navigation/native
863
+ notifications: detectToasts(dependencies), // react-native-toast-message
864
+ };
865
+ ```
866
+
867
+ ### 2. Analyze Existing Components (Reference Pattern)
868
+
869
+ ```typescript
870
+ // Search for existing CRUD modules as reference
871
+ const existingModules = await searchFiles('src/features/**/components/**/*.tsx');
872
+
873
+ // Detect patterns from existing code:
874
+ const patterns = {
875
+ list: detectListComponent(existingModules), // FlatList | SectionList | FlashList
876
+ bottomSheet: detectBottomSheetPattern(existingModules), // @gorhom/bottom-sheet | Modal
877
+ filters: detectFiltersPattern(existingModules), // Collapsible | Bottom Sheet | Always Visible
878
+ formLayout: detectFormLayout(existingModules), // Single scroll | Sections
879
+ permissionGuards: detectAuthPattern(existingModules), // useAuth | usePermissions | role checks
880
+ };
881
+ ```
882
+
883
+ ### 3. Deep UI Pattern Extraction (Mobile-Specific)
884
+
885
+ **Extract EXACT design patterns from existing mobile code:**
886
+
887
+ ```typescript
888
+ // Analyze existing list components
889
+ const existingLists = await glob('src/features/**/components/*List.tsx');
890
+ const existingForms = await glob('src/features/**/components/*Form.tsx');
891
+ const existingBottomSheets = await glob('src/features/**/components/*BottomSheet.tsx');
892
+ const existingModals = await glob('src/features/**/components/*Modal.tsx');
893
+
894
+ const extractedDefaults = {
895
+ pagination: {
896
+ pageSize: detectMode(existingLists.map((l) => extractPaginationSize(l))), // Mode = 20 (mobile shows more)
897
+ initialLoad: detectMode(existingLists.map((l) => extractInitialLoad(l))), // 20
898
+ },
899
+ debounce: {
900
+ search: detectMode(existingHooks.map((h) => extractDebounceTime(h))), // 300ms
901
+ },
902
+ list: {
903
+ keyExtractor: detectMode(existingLists.map((l) => extractKeyPattern(l))), // (item) => item.id
904
+ windowSize: detectMode(existingLists.map((l) => extractWindowSize(l))), // 10
905
+ maxToRenderPerBatch: 10,
906
+ updateCellsBatchingPeriod: 50,
907
+ },
908
+ queryOptions: {
909
+ staleTime: detectMode(existingHooks.map((h) => extractStaleTime(h))),
910
+ gcTime: detectMode(existingHooks.map((h) => extractGcTime(h))),
911
+ retry: 1,
912
+ },
913
+ };
914
+
915
+ // Deep UI Pattern Analysis (Mobile)
916
+ const uiPatterns = {
917
+ list: {
918
+ contentContainerStyle: extractMode(existingLists, (code) =>
919
+ extractStyleProp(code, 'contentContainerStyle')
920
+ ),
921
+ itemSeparator: extractMode(existingLists, (code) =>
922
+ extractComponent(code, 'ItemSeparatorComponent')
923
+ ),
924
+ emptyComponent: extractMode(existingLists, (code) =>
925
+ extractComponent(code, 'ListEmptyComponent')
926
+ ),
927
+ headerComponent: extractMode(existingLists, (code) =>
928
+ extractComponent(code, 'ListHeaderComponent')
929
+ ),
930
+ footerComponent: extractMode(existingLists, (code) =>
931
+ extractComponent(code, 'ListFooterComponent')
932
+ ),
933
+ loadingIndicator: extractMode(existingLists, (code) => extractLoadingIndicator(code)),
934
+ },
935
+ form: {
936
+ layout: detectMode(existingForms.map((f) => detectFormLayout(f))), // 'scroll-view' | 'keyboard-avoiding'
937
+ spacing: detectMode(existingForms.map((f) => extractFieldSpacing(f))), // 16, 20, 24
938
+ textInputMode: detectMode(existingForms.map((f) => extractTextInputMode(f))), // 'outlined' | 'flat'
939
+ labelPosition: detectMode(existingForms.map((f) => extractLabelPosition(f))), // 'top' | 'floating'
940
+ buttonAlignment: detectMode(existingForms.map((f) => extractButtonAlignment(f))), // 'bottom' | 'inline'
941
+ validationDisplay: detectMode(existingForms.map((f) => extractValidationStyle(f))), // 'inline' | 'bottom'
942
+ },
943
+ bottomSheet: {
944
+ snapPoints: detectMode(existingBottomSheets.map((bs) => extractSnapPoints(bs))), // ['25%', '50%', '90%']
945
+ index: detectMode(existingBottomSheets.map((bs) => extractInitialIndex(bs))), // 1 (start at 50%)
946
+ enablePanDownToClose: true, // Most common
947
+ backdropComponent: detectMode(existingBottomSheets.map((bs) => extractBackdrop(bs))),
948
+ },
949
+ modal: {
950
+ animationType: detectMode(existingModals.map((m) => extractAnimationType(m))), // 'slide' | 'fade'
951
+ presentationStyle: detectMode(existingModals.map((m) => extractPresentationStyle(m))), // 'pageSheet' | 'formSheet'
952
+ transparent: detectMode(existingModals.map((m) => extractTransparent(m))), // true/false
953
+ },
954
+ reusableComponents: {
955
+ statusBadge: findComponent('StatusBadge'),
956
+ roleChip: findComponent('RoleChip'),
957
+ emptyState: findComponent('EmptyState'),
958
+ loadingSkeleton: findComponent('LoadingSkeleton'),
959
+ },
960
+ };
961
+
962
+ // Helper functions (mobile-specific)
963
+ function extractMode<T>(files: string[], extractor: (code: string) => T): T {
964
+ const values = files.map((f) => extractor(readFileSync(f, 'utf-8')));
965
+ return statisticalMode(values); // Most common value
966
+ }
967
+
968
+ function extractTextInputMode(code: string): 'outlined' | 'flat' {
969
+ const match = code.match(/<TextInput[^>]+mode="([^"]+)"/);
970
+ return (match?.[1] as any) || 'outlined';
971
+ }
972
+
973
+ function extractFormLayout(code: string): 'scroll-view' | 'keyboard-avoiding' | 'section-list' {
974
+ if (code.includes('<KeyboardAvoidingView')) {
975
+ return 'keyboard-avoiding';
976
+ }
977
+ if (code.includes('<SectionList')) {
978
+ return 'section-list';
979
+ }
980
+ return 'scroll-view';
981
+ }
982
+
983
+ function extractStyleProp(code: string, prop: string): string {
984
+ const styleMatch = code.match(new RegExp(`${prop}=\\{([^}]+)\\}`));
985
+ return styleMatch?.[1]?.trim() || 'not-found';
986
+ }
987
+ ```
988
+
989
+ ### 4. Reference Components for Consistency
990
+
991
+ Identify existing components to use as templates:
992
+
993
+ ```typescript
994
+ const referenceComponents = {
995
+ list: 'src/features/organizations/components/OrganizationsList.tsx',
996
+ screen: 'src/features/organizations/screens/OrganizationsScreen.tsx',
997
+ hooks: 'src/features/users/hooks/useUsers.ts',
998
+ mutations: 'src/features/users/hooks/useUserMutations.ts',
999
+ navigation: 'src/navigation/MainNavigator.tsx',
1000
+ bottomSheet: 'src/components/common/BottomSheet.tsx',
1001
+ };
1002
+ ```
1003
+
1004
+ ---
1005
+
1006
+ ## Phase 1: Analysis Summary (Show Only - Mobile)
1007
+
1008
+ Present detected configuration, API analysis, and implementation audit to user:
1009
+
1010
+ ```
1011
+ 🔍 Project Stack Detected (Mobile):
1012
+
1013
+ UI: React Native Paper v5.11.0
1014
+ List: FlatList (React Native built-in) + FlashList v1.6.0 ✅
1015
+ Forms: React Hook Form v7.50.0 + Zod v3.22.4 ✅
1016
+ Data: TanStack Query v5.20.0 ✅
1017
+ State: Zustand v4.5.0 ✅
1018
+ Navigation: React Navigation v6.1.0 ✅
1019
+ Toasts: react-native-toast-message v2.1.7 ✅
1020
+
1021
+ 📐 UX Standards (from existing code):
1022
+ ✅ CRUD Pattern: Bottom Sheet (snap points: 25%, 50%, 90%)
1023
+ ✅ List: FlatList with pull-to-refresh, infinite scroll
1024
+ ✅ Filters: Bottom Sheet with Apply/Reset buttons
1025
+ ✅ Forms: React Hook Form + Zod validation (inline errors)
1026
+ ✅ Delete: Modal with confirmation (destructive style)
1027
+ ✅ Permissions: Hook-based (useAuthStore + role checks)
1028
+
1029
+ 📊 API Module Analysis: Users
1030
+
1031
+ Endpoints Found:
1032
+ ✅ GET /api/users (List with pagination)
1033
+ ✅ POST /api/users (Create)
1034
+ ✅ GET /api/users/{id} (Read)
1035
+ ✅ PUT /api/users/{id} (Update)
1036
+ ✅ DELETE /api/users/{id} (Delete)
1037
+
1038
+ Entity Schema:
1039
+ Fields (8): id, email, firstName, lastName, status, role, createdAt, updatedAt
1040
+ Required: email, firstName, lastName, roleId
1041
+ Enums: status (active, pending, suspended)
1042
+ Relations (1): role → /api/roles (many-to-one, display: name)
1043
+
1044
+ Features:
1045
+ ✅ Server-side pagination (page, limit, max: 100)
1046
+ ✅ Search (by name, email)
1047
+ ✅ Filters (roleId, status)
1048
+ ✅ Authentication (Bearer JWT)
1049
+ ✅ Role-based access (ROOT, OWNER, ADMIN)
1050
+
1051
+ ✅ All standards locked. Module will match existing patterns.
1052
+
1053
+ Analysis complete. Returning data to flow-work orchestrator...
1054
+ ```
1055
+
1056
+ ---
1057
+
1058
+ ## 📤 OUTPUT Format (CRITICAL)
1059
+
1060
+ **This sub-prompt MUST return a structured JSON object that flow-work can consume.**
1061
+
1062
+ ### OpenAPIAnalysisResult Interface (Mobile)
1063
+
1064
+ ```typescript
1065
+ interface OpenAPIAnalysisResult {
1066
+ // Meta
1067
+ success: boolean;
1068
+ module: string;
1069
+ apiUrl: string;
1070
+ timestamp: string; // ISO 8601
1071
+
1072
+ // Implementation Audit
1073
+ implementationAudit: {
1074
+ status: 'NOT_IMPLEMENTED' | 'PARTIAL' | 'COMPLETE';
1075
+ score: number; // 0-100
1076
+ strategy: 'FULL_NEW' | 'REFACTOR_COMPLETE' | 'MINOR_FIXES';
1077
+ types: {
1078
+ matching: string[];
1079
+ missing: Array<{ schema: string; action: string; reason: string }>;
1080
+ incorrect: Array<{
1081
+ schema: string;
1082
+ file: string;
1083
+ issues: string[];
1084
+ action: string;
1085
+ }>;
1086
+ };
1087
+ hooks: {
1088
+ implemented: string[];
1089
+ missing: Array<{
1090
+ endpoint: string;
1091
+ expectedHook: string;
1092
+ action: string;
1093
+ }>;
1094
+ incorrect: Array<{
1095
+ hook: string;
1096
+ file: string;
1097
+ issues: string[];
1098
+ action: string;
1099
+ }>;
1100
+ };
1101
+ components: {
1102
+ complete: string[];
1103
+ missing: Array<{ component: string; action: string }>;
1104
+ nonStandard: Array<{
1105
+ component: string;
1106
+ file: string;
1107
+ issues: string[];
1108
+ action: string;
1109
+ }>;
1110
+ };
1111
+ actionItems: string[]; // Human-readable list
1112
+ };
1113
+
1114
+ // Project Standards (detected - mobile)
1115
+ projectStandards: {
1116
+ stack: {
1117
+ ui: string; // 'react-native-paper v5.11.0'
1118
+ list: string; // 'FlatList (React Native) + FlashList v1.6.0'
1119
+ forms: string; // 'react-hook-form v7.50.0'
1120
+ validation: string; // 'zod v3.22.4'
1121
+ query: string; // '@tanstack/react-query v5.20.0'
1122
+ state: string; // 'zustand v4.5.0'
1123
+ navigation: string; // '@react-navigation/native v6.1.0'
1124
+ notifications: string; // 'react-native-toast-message v2.1.7'
1125
+ };
1126
+ patterns: {
1127
+ crudPattern: string; // 'bottom-sheet' | 'modal' | 'full-screen'
1128
+ bottomSheetSnapPoints: string[]; // ['25%', '50%', '90%']
1129
+ listComponent: string; // 'FlatList' | 'FlashList' | 'SectionList'
1130
+ filterUI: string; // 'bottom-sheet' | 'modal'
1131
+ formLayout: string; // 'scroll-view' | 'keyboard-avoiding'
1132
+ deleteConfirmation: string; // 'modal'
1133
+ };
1134
+ defaults: {
1135
+ pagination: {
1136
+ pageSize: number; // 20 (mobile typically loads more)
1137
+ initialLoad: number; // 20
1138
+ };
1139
+ debounce: {
1140
+ search: number; // 300ms
1141
+ };
1142
+ list: {
1143
+ windowSize: number; // 10
1144
+ maxToRenderPerBatch: number; // 10
1145
+ updateCellsBatchingPeriod: number; // 50
1146
+ };
1147
+ caching: {
1148
+ staleTime: number; // 30000ms (30s)
1149
+ gcTime: number; // 300000ms (5min)
1150
+ retry: number; // 1 for mutations
1151
+ };
1152
+ };
1153
+ referenceComponents: {
1154
+ list: string; // 'src/features/organizations/components/OrganizationsList.tsx'
1155
+ screen: string; // 'src/features/organizations/screens/OrganizationsScreen.tsx'
1156
+ hooks: string; // 'src/features/users/hooks/useUsers.ts'
1157
+ mutations: string; // 'src/features/users/hooks/useUserMutations.ts'
1158
+ };
1159
+ };
1160
+
1161
+ // OpenAPI Analysis
1162
+ openapi: {
1163
+ version: string; // '3.0.3'
1164
+ title: string; // 'CROSS Mobile API'
1165
+ totalPaths: number; // 45
1166
+ totalSchemas: number; // 32
1167
+ };
1168
+
1169
+ // Module Endpoints
1170
+ endpoints: Array<{
1171
+ method: string; // 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'
1172
+ path: string; // '/api/users'
1173
+ operationId: string; // 'getUsers'
1174
+ summary: string; // 'List all users with pagination'
1175
+ tags: string[]; // ['Users']
1176
+ parameters: Array<{
1177
+ name: string; // 'page'
1178
+ in: string; // 'query'
1179
+ required: boolean;
1180
+ schema: {
1181
+ type: string;
1182
+ default?: any;
1183
+ enum?: any[];
1184
+ };
1185
+ }>;
1186
+ requestBody?: {
1187
+ required: boolean;
1188
+ schema: string; // 'CreateUserDto' (schema name)
1189
+ };
1190
+ responses: {
1191
+ [statusCode: string]: {
1192
+ description: string;
1193
+ schema?: string; // 'UserResponseDto' or 'PaginatedResponse<UserResponseDto>'
1194
+ };
1195
+ };
1196
+ }>;
1197
+
1198
+ // Entity Schemas (DTOs)
1199
+ schemas: {
1200
+ response: {
1201
+ name: string; // 'UserResponseDto'
1202
+ fields: FieldSpec[];
1203
+ };
1204
+ create: {
1205
+ name: string; // 'CreateUserDto'
1206
+ fields: FieldSpec[];
1207
+ };
1208
+ update: {
1209
+ name: string; // 'UpdateUserDto'
1210
+ fields: FieldSpec[];
1211
+ };
1212
+ };
1213
+
1214
+ // Field Specifications
1215
+ fields: FieldSpec[]; // All unique fields across DTOs
1216
+
1217
+ // Detected Features
1218
+ features: {
1219
+ pagination: {
1220
+ enabled: boolean;
1221
+ params: string[]; // ['page', 'limit']
1222
+ responseFormat: 'array' | 'object';
1223
+ dataKey?: string; // 'items' | 'data'
1224
+ totalKey?: string; // 'total' | 'meta.total'
1225
+ maxPageSize?: number; // 100
1226
+ };
1227
+ search: {
1228
+ enabled: boolean;
1229
+ params: string[]; // ['search', 'q']
1230
+ };
1231
+ sorting: {
1232
+ enabled: boolean;
1233
+ params: string[]; // ['sortBy', 'order']
1234
+ fields?: string[]; // Sortable fields
1235
+ };
1236
+ filtering: {
1237
+ enabled: boolean;
1238
+ fields: string[]; // ['status', 'roleId']
1239
+ };
1240
+ authentication: {
1241
+ type: 'bearer' | 'apiKey' | 'oauth2';
1242
+ required: boolean;
1243
+ };
1244
+ authorization: {
1245
+ roles: string[]; // ['ROOT', 'OWNER', 'ADMIN']
1246
+ permissions?: string[]; // ['user:read', 'user:write']
1247
+ };
1248
+ softDelete: {
1249
+ enabled: boolean;
1250
+ field?: string; // 'deletedAt' | 'status'
1251
+ };
1252
+ };
1253
+
1254
+ // Relationships
1255
+ relationships: Array<{
1256
+ field: string; // 'roleId' or 'role'
1257
+ relatedEntity: string; // 'Role'
1258
+ type: 'many-to-one' | 'one-to-many' | 'many-to-many' | 'one-to-one';
1259
+ foreignKey: string; // 'roleId'
1260
+ endpoint?: string; // '/api/roles'
1261
+ displayField: string; // 'name' (field to display in UI)
1262
+ populated: boolean; // true if backend returns full object
1263
+ }>;
1264
+
1265
+ // Complexity Analysis
1266
+ complexity: {
1267
+ level: 'SIMPLE' | 'MEDIUM' | 'COMPLEX';
1268
+ estimatedFiles: number; // 18-20
1269
+ estimatedSP: number; // 8
1270
+ estimatedHours: number; // 5-6
1271
+ factors: {
1272
+ endpoints: number; // 5
1273
+ relations: number; // 2
1274
+ customEndpoints: number; // 0
1275
+ validationRules: number; // 15
1276
+ };
1277
+ };
1278
+
1279
+ // Validation & Warnings
1280
+ warnings: string[]; // Issues detected (missing endpoints, inconsistent naming, etc.)
1281
+ suggestions: string[]; // Recommendations
1282
+ }
1283
+
1284
+ interface FieldSpec {
1285
+ name: string; // 'email'
1286
+ type: 'string' | 'number' | 'boolean' | 'date' | 'enum' | 'array' | 'object' | 'uuid';
1287
+ required: boolean;
1288
+ nullable: boolean;
1289
+ validation?: {
1290
+ min?: number;
1291
+ max?: number;
1292
+ pattern?: string;
1293
+ format?: 'email' | 'url' | 'uuid' | 'date-time' | 'password';
1294
+ enum?: string[];
1295
+ };
1296
+ relation?: {
1297
+ entity: string; // 'Role'
1298
+ type: 'many-to-one' | 'one-to-many' | 'many-to-many';
1299
+ foreignKey: string; // 'roleId'
1300
+ populated: boolean;
1301
+ displayField: string; // 'name'
1302
+ };
1303
+ category: 'auto-generated' | 'read-only' | 'editable' | 'metadata';
1304
+ usage: {
1305
+ showInList: boolean; // Mobile uses "list" not "table"
1306
+ showInForm: boolean; // In create/edit forms
1307
+ showInDetails: boolean;
1308
+ editable: boolean; // Can be edited after creation
1309
+ };
1310
+ default?: any;
1311
+ description?: string;
1312
+
1313
+ // DTO specific
1314
+ inResponseDto: boolean;
1315
+ inCreateDto: boolean;
1316
+ inUpdateDto: boolean;
1317
+ }
1318
+ ```
1319
+
1320
+ ### Return Format
1321
+
1322
+ ```json
1323
+ {
1324
+ "success": true,
1325
+ "module": "users",
1326
+ "apiUrl": "http://localhost:3001/api/docs-json",
1327
+ "timestamp": "2026-03-04T10:30:00-03:00",
1328
+ "projectStandards": {
1329
+ /* ... mobile-specific */
1330
+ },
1331
+ "openapi": {
1332
+ /* ... */
1333
+ },
1334
+ "endpoints": [
1335
+ /* ... */
1336
+ ],
1337
+ "schemas": {
1338
+ /* ... */
1339
+ },
1340
+ "fields": [
1341
+ /* ... */
1342
+ ],
1343
+ "features": {
1344
+ /* ... */
1345
+ },
1346
+ "relationships": [
1347
+ /* ... */
1348
+ ],
1349
+ "complexity": {
1350
+ /* ... */
1351
+ },
1352
+ "warnings": [],
1353
+ "suggestions": []
1354
+ }
1355
+ ```
1356
+
1357
+ ---
1358
+
1359
+ ## Best Practices Reference (For flow-work - Mobile)
1360
+
1361
+ **These patterns should be enforced by flow-work during mobile code generation:**
1362
+
1363
+ ### 1. Use FlatList or FlashList (React Native Standard)
1364
+
1365
+ ```typescript
1366
+ // ✅ CORRECT: Use FlatList with optimization
1367
+ import { FlatList, ActivityIndicator } from 'react-native';
1368
+
1369
+ <FlatList
1370
+ data={users}
1371
+ keyExtractor={(item) => item.id}
1372
+ renderItem={({ item }) => <UserListItem user={item} />}
1373
+ onEndReached={loadMore}
1374
+ onEndReachedThreshold={0.3}
1375
+ refreshing={isRefreshing}
1376
+ onRefresh={refetch}
1377
+ ListEmptyComponent={<EmptyState />}
1378
+ ListFooterComponent={isLoading ? <ActivityIndicator /> : null}
1379
+ windowSize={10}
1380
+ maxToRenderPerBatch={10}
1381
+ updateCellsBatchingPeriod={50}
1382
+ />
1383
+
1384
+ // ❌ WRONG: Don't use ScrollView with map for long lists
1385
+ // <ScrollView>{users.map(user => <UserItem user={user} />)}</ScrollView>
1386
+ ```
1387
+
1388
+ ### 2. Query Key Management
1389
+
1390
+ ```typescript
1391
+ // ✅ CORRECT: Export query key constant
1392
+ export const USERS_QUERY_KEY = 'users';
1393
+
1394
+ export const useUsers = (params: GetUsersParams) => {
1395
+ return useQuery({
1396
+ queryKey: [USERS_QUERY_KEY, params],
1397
+ queryFn: () => usersService.getUsers(params),
1398
+ });
1399
+ };
1400
+ ```
1401
+
1402
+ ### 3. Cache Invalidation Strategy (Broad)
1403
+
1404
+ ```typescript
1405
+ // ✅ CORRECT: Invalidate ALL related queries
1406
+ void queryClient.invalidateQueries({ queryKey: [USERS_QUERY_KEY] });
1407
+
1408
+ // ❌ WRONG: Too specific (misses cached list queries)
1409
+ // void queryClient.invalidateQueries({ queryKey: [USERS_QUERY_KEY, id] });
1410
+ ```
1411
+
1412
+ ### 4. Toast Message Standards (Mobile)
1413
+
1414
+ ```typescript
1415
+ // ✅ CREATE success
1416
+ Toast.show({
1417
+ type: 'success',
1418
+ text1: 'Usuario creado',
1419
+ text2: 'Email de bienvenida enviado',
1420
+ });
1421
+
1422
+ // ✅ UPDATE success
1423
+ Toast.show({
1424
+ type: 'success',
1425
+ text1: 'Usuario actualizado',
1426
+ });
1427
+
1428
+ // ✅ ERROR with status
1429
+ if (error.response?.status === 409) {
1430
+ Toast.show({
1431
+ type: 'error',
1432
+ text1: 'Error',
1433
+ text2: 'No puedes suspender al último OWNER activo',
1434
+ });
1435
+ }
1436
+ ```
1437
+
1438
+ ### 5. Loading States (Mobile)
1439
+
1440
+ ```typescript
1441
+ // ✅ Proper loading states for FlatList
1442
+ <FlatList
1443
+ data={data?.items ?? []}
1444
+ refreshing={isRefreshing} // Pull-to-refresh
1445
+ onRefresh={refetch}
1446
+ ListFooterComponent={
1447
+ isFetchingNextPage ? <ActivityIndicator /> : null
1448
+ }
1449
+ ListEmptyComponent={
1450
+ isLoading ? <LoadingSkeleton /> : <EmptyState />
1451
+ }
1452
+ />
1453
+ ```
1454
+
1455
+ ---
1456
+
1457
+ ## Error Handling
1458
+
1459
+ **If analysis fails at any step:**
1460
+
1461
+ ```json
1462
+ {
1463
+ "success": false,
1464
+ "module": "users",
1465
+ "error": "Failed to fetch OpenAPI spec",
1466
+ "details": "Connection timeout after 10 seconds",
1467
+ "suggestions": [
1468
+ "1. Ensure backend server is running and accessible from device",
1469
+ "2. Check network configuration (WiFi, mobile data)",
1470
+ "3. Verify API URL is correct (not localhost if testing on real device)",
1471
+ "4. Try --api-url=http://your-computer-ip:3001/api/docs-json"
1472
+ ]
1473
+ }
1474
+ ```
1475
+
1476
+ **flow-work should handle this by:**
1477
+
1478
+ 1. Showing error to user
1479
+ 2. Offering retry or manual mode
1480
+ 3. Logging error for debugging
1481
+
1482
+ ---
1483
+
1484
+ ## End of Sub-Prompt
1485
+
1486
+ **This prompt returns control to `flow-work` with the `OpenAPIAnalysisResult` data structure.**
1487
+
1488
+ Flow-work will use this data to:
1489
+
1490
+ - Generate detailed `work.md` (Phase 2)
1491
+ - Create branch with naming convention (Phase 3)
1492
+ - Execute implementation (Phase 3)
1493
+ - Validate and finalize (Phase 4)