ai-flow-dev 2.7.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.
- package/LICENSE +21 -21
- package/README.md +573 -570
- package/package.json +74 -74
- package/prompts/backend/flow-build-phase-0.md +535 -535
- package/prompts/backend/flow-build-phase-1.md +626 -626
- package/prompts/backend/flow-build-phase-10.md +340 -340
- package/prompts/backend/flow-build-phase-2.md +573 -573
- package/prompts/backend/flow-build-phase-3.md +834 -834
- package/prompts/backend/flow-build-phase-4.md +554 -554
- package/prompts/backend/flow-build-phase-5.md +703 -703
- package/prompts/backend/flow-build-phase-6.md +524 -524
- package/prompts/backend/flow-build-phase-7.md +1001 -1001
- package/prompts/backend/flow-build-phase-8.md +1407 -1407
- package/prompts/backend/flow-build-phase-9.md +477 -477
- package/prompts/backend/flow-build.md +137 -137
- package/prompts/backend/flow-check-review.md +656 -20
- package/prompts/backend/flow-check-test.md +526 -14
- package/prompts/backend/flow-check.md +725 -67
- package/prompts/backend/flow-commit.md +88 -119
- package/prompts/backend/flow-docs-sync.md +354 -354
- package/prompts/backend/flow-finish.md +919 -0
- package/prompts/backend/flow-release.md +949 -0
- package/prompts/backend/flow-work-feature.md +61 -61
- package/prompts/backend/flow-work-fix.md +46 -46
- package/prompts/backend/flow-work-refactor.md +48 -48
- package/prompts/backend/flow-work-resume.md +34 -34
- package/prompts/backend/flow-work.md +1098 -1286
- package/prompts/desktop/flow-build-phase-0.md +359 -359
- package/prompts/desktop/flow-build-phase-1.md +295 -295
- package/prompts/desktop/flow-build-phase-10.md +357 -357
- package/prompts/desktop/flow-build-phase-2.md +282 -282
- package/prompts/desktop/flow-build-phase-3.md +291 -291
- package/prompts/desktop/flow-build-phase-4.md +308 -308
- package/prompts/desktop/flow-build-phase-5.md +269 -269
- package/prompts/desktop/flow-build-phase-6.md +350 -350
- package/prompts/desktop/flow-build-phase-7.md +297 -297
- package/prompts/desktop/flow-build-phase-8.md +541 -541
- package/prompts/desktop/flow-build-phase-9.md +439 -439
- package/prompts/desktop/flow-build.md +156 -156
- package/prompts/desktop/flow-check-review.md +656 -20
- package/prompts/desktop/flow-check-test.md +526 -14
- package/prompts/desktop/flow-check.md +725 -67
- package/prompts/desktop/flow-commit.md +88 -119
- package/prompts/desktop/flow-docs-sync.md +354 -354
- package/prompts/desktop/flow-finish.md +910 -0
- package/prompts/desktop/flow-release.md +662 -0
- package/prompts/desktop/flow-work-feature.md +61 -61
- package/prompts/desktop/flow-work-fix.md +46 -46
- package/prompts/desktop/flow-work-refactor.md +48 -48
- package/prompts/desktop/flow-work-resume.md +34 -34
- package/prompts/desktop/flow-work.md +1202 -1390
- package/prompts/frontend/flow-build-phase-0.md +425 -425
- package/prompts/frontend/flow-build-phase-1.md +626 -626
- package/prompts/frontend/flow-build-phase-10.md +33 -33
- package/prompts/frontend/flow-build-phase-2.md +573 -573
- package/prompts/frontend/flow-build-phase-3.md +782 -782
- package/prompts/frontend/flow-build-phase-4.md +554 -554
- package/prompts/frontend/flow-build-phase-5.md +703 -703
- package/prompts/frontend/flow-build-phase-6.md +524 -524
- package/prompts/frontend/flow-build-phase-7.md +1001 -1001
- package/prompts/frontend/flow-build-phase-8.md +872 -872
- package/prompts/frontend/flow-build-phase-9.md +94 -94
- package/prompts/frontend/flow-build.md +137 -137
- package/prompts/frontend/flow-check-review.md +656 -20
- package/prompts/frontend/flow-check-test.md +526 -14
- package/prompts/frontend/flow-check.md +725 -67
- package/prompts/frontend/flow-commit.md +88 -119
- package/prompts/frontend/flow-docs-sync.md +550 -550
- package/prompts/frontend/flow-finish.md +910 -0
- package/prompts/frontend/flow-release.md +519 -0
- package/prompts/frontend/flow-work-api.md +1540 -0
- package/prompts/frontend/flow-work-feature.md +61 -61
- package/prompts/frontend/flow-work-fix.md +38 -38
- package/prompts/frontend/flow-work-refactor.md +48 -48
- package/prompts/frontend/flow-work-resume.md +34 -34
- package/prompts/frontend/flow-work.md +1583 -1320
- package/prompts/mobile/flow-build-phase-0.md +425 -425
- package/prompts/mobile/flow-build-phase-1.md +626 -626
- package/prompts/mobile/flow-build-phase-10.md +32 -32
- package/prompts/mobile/flow-build-phase-2.md +573 -573
- package/prompts/mobile/flow-build-phase-3.md +782 -782
- package/prompts/mobile/flow-build-phase-4.md +554 -554
- package/prompts/mobile/flow-build-phase-5.md +703 -703
- package/prompts/mobile/flow-build-phase-6.md +524 -524
- package/prompts/mobile/flow-build-phase-7.md +1001 -1001
- package/prompts/mobile/flow-build-phase-8.md +888 -888
- package/prompts/mobile/flow-build-phase-9.md +90 -90
- package/prompts/mobile/flow-build.md +135 -135
- package/prompts/mobile/flow-check-review.md +656 -20
- package/prompts/mobile/flow-check-test.md +526 -14
- package/prompts/mobile/flow-check.md +725 -67
- package/prompts/mobile/flow-commit.md +88 -119
- package/prompts/mobile/flow-docs-sync.md +620 -620
- package/prompts/mobile/flow-finish.md +910 -0
- package/prompts/mobile/flow-release.md +751 -0
- package/prompts/mobile/flow-work-api.md +1493 -0
- package/prompts/mobile/flow-work-feature.md +61 -61
- package/prompts/mobile/flow-work-fix.md +46 -46
- package/prompts/mobile/flow-work-refactor.md +48 -48
- package/prompts/mobile/flow-work-resume.md +34 -34
- package/prompts/mobile/flow-work.md +1593 -1329
- package/prompts/shared/mermaid-guidelines.md +102 -102
- package/prompts/shared/scope-levels.md +114 -114
- package/prompts/shared/smart-skip-preflight.md +214 -214
- package/prompts/shared/story-points.md +55 -55
- package/prompts/shared/task-format.md +74 -74
- package/prompts/shared/task-summary-template.md +277 -277
- package/templates/AGENT.template.md +443 -443
- package/templates/backend/.clauderules.template +112 -112
- package/templates/backend/.cursorrules.template +102 -102
- package/templates/backend/README.template.md +2 -2
- package/templates/backend/ai-instructions.template.md +2 -2
- package/templates/backend/copilot-instructions.template.md +2 -2
- package/templates/backend/docs/api.template.md +320 -320
- package/templates/backend/docs/business-flows.template.md +97 -97
- package/templates/backend/docs/code-standards.template.md +2 -2
- package/templates/backend/docs/contributing.template.md +3 -3
- package/templates/backend/docs/data-model.template.md +520 -520
- package/templates/backend/docs/testing.template.md +2 -2
- package/templates/backend/project-brief.template.md +2 -2
- package/templates/backend/specs/configuration.template.md +2 -2
- package/templates/backend/specs/security.template.md +2 -2
- package/templates/desktop/.clauderules.template +112 -112
- package/templates/desktop/.cursorrules.template +102 -102
- package/templates/desktop/README.template.md +170 -170
- package/templates/desktop/ai-instructions.template.md +366 -366
- package/templates/desktop/copilot-instructions.template.md +140 -140
- package/templates/desktop/docs/docs/api.template.md +320 -320
- package/templates/desktop/docs/docs/architecture.template.md +724 -724
- package/templates/desktop/docs/docs/business-flows.template.md +102 -102
- package/templates/desktop/docs/docs/code-standards.template.md +792 -792
- package/templates/desktop/docs/docs/contributing.template.md +149 -149
- package/templates/desktop/docs/docs/data-model.template.md +520 -520
- package/templates/desktop/docs/docs/operations.template.md +720 -720
- package/templates/desktop/docs/docs/testing.template.md +722 -722
- package/templates/desktop/project-brief.template.md +150 -150
- package/templates/desktop/specs/specs/configuration.template.md +121 -121
- package/templates/desktop/specs/specs/security.template.md +392 -392
- package/templates/frontend/README.template.md +2 -2
- package/templates/frontend/ai-instructions.template.md +2 -2
- package/templates/frontend/docs/api-integration.template.md +362 -362
- package/templates/frontend/docs/components.template.md +2 -2
- package/templates/frontend/docs/error-handling.template.md +360 -360
- package/templates/frontend/docs/operations.template.md +107 -107
- package/templates/frontend/docs/performance.template.md +124 -124
- package/templates/frontend/docs/pwa.template.md +119 -119
- package/templates/frontend/docs/state-management.template.md +2 -2
- package/templates/frontend/docs/styling.template.md +2 -2
- package/templates/frontend/docs/testing.template.md +2 -2
- package/templates/frontend/project-brief.template.md +2 -2
- package/templates/frontend/specs/accessibility.template.md +95 -95
- package/templates/frontend/specs/configuration.template.md +2 -2
- package/templates/frontend/specs/security.template.md +175 -175
- package/templates/fullstack/README.template.md +252 -252
- package/templates/fullstack/ai-instructions.template.md +444 -444
- package/templates/fullstack/project-brief.template.md +157 -157
- package/templates/fullstack/specs/configuration.template.md +340 -340
- package/templates/mobile/README.template.md +167 -167
- package/templates/mobile/ai-instructions.template.md +196 -196
- package/templates/mobile/docs/app-store.template.md +135 -135
- package/templates/mobile/docs/architecture.template.md +63 -63
- package/templates/mobile/docs/native-features.template.md +94 -94
- package/templates/mobile/docs/navigation.template.md +59 -59
- package/templates/mobile/docs/offline-strategy.template.md +65 -65
- package/templates/mobile/docs/permissions.template.md +56 -56
- package/templates/mobile/docs/state-management.template.md +85 -85
- package/templates/mobile/docs/testing.template.md +109 -109
- package/templates/mobile/project-brief.template.md +69 -69
- package/templates/mobile/specs/build-configuration.template.md +91 -91
- package/templates/mobile/specs/deployment.template.md +92 -92
- package/templates/work.template.md +47 -47
|
@@ -0,0 +1,1540 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Analyze OpenAPI specification to extract complete module metadata for frontend CRUD generation
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# API Module Analyzer (Sub-Prompt)
|
|
6
|
+
|
|
7
|
+
**YOU ARE AN EXPERT API ANALYZER specialized in extracting comprehensive metadata from OpenAPI specifications for frontend 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, 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 project-specific patterns
|
|
44
|
+
3. Ensure type-safety between frontend 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, CORS, and timeouts.**
|
|
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('CORS')) {
|
|
101
|
+
throw new Error('CORS error. Backend must allow frontend origin.');
|
|
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 Backoffice 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
|
+
showInTable: field.name === 'id' ? false : true,
|
|
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
|
+
showInTable: 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
|
+
showInTable: 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
|
+
showInTable: 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 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
|
+
pages: await glob(`${featurePath}/pages/**/*.tsx`),
|
|
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
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
async function auditComponents(
|
|
679
|
+
targetModule: string,
|
|
680
|
+
existingComponents: string[]
|
|
681
|
+
): Promise<ComponentAuditResult> {
|
|
682
|
+
const expectedComponents = [
|
|
683
|
+
`${capitalizeFirst(targetModule)}Table.tsx`,
|
|
684
|
+
`${capitalizeFirst(targetModule)}Drawer.tsx`,
|
|
685
|
+
`${capitalizeFirst(targetModule)}Form.tsx`,
|
|
686
|
+
`Delete${capitalizeFirst(targetModule)}Dialog.tsx`,
|
|
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('Table') && !componentAnalysis.usesMaterialReactTable) {
|
|
714
|
+
issues.push('Should use MaterialReactTable (project 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');
|
|
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:**
|
|
790
|
+
|
|
791
|
+
```
|
|
792
|
+
📊 Current Implementation Status: Users Module
|
|
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:
|
|
815
|
+
⚠️ UsersTable.tsx (exists but doesn't use MaterialReactTable - needs refactor)
|
|
816
|
+
❌ UserDrawer.tsx (not found)
|
|
817
|
+
❌ UserForm.tsx (not found)
|
|
818
|
+
❌ DeleteUserDialog.tsx (not found)
|
|
819
|
+
❌ UserFilters.tsx (not found)
|
|
820
|
+
|
|
821
|
+
📄 Pages:
|
|
822
|
+
⚠️ UsersPage.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 table 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 (Automatic)
|
|
845
|
+
|
|
846
|
+
**CRITICAL: Auto-detect project stack and patterns to ensure consistency across all modules.**
|
|
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
|
|
855
|
+
const stack = {
|
|
856
|
+
ui: detectUILibrary(dependencies), // @mui/material, antd, chakra-ui, etc.
|
|
857
|
+
table: detectTableLibrary(dependencies), // material-react-table, @tanstack/react-table, @mui/x-data-grid
|
|
858
|
+
forms: detectFormsLibrary(dependencies), // react-hook-form, formik
|
|
859
|
+
validation: detectValidation(dependencies), // zod, yup, joi
|
|
860
|
+
query: detectDataFetching(dependencies), // @tanstack/react-query, swr, rtk-query
|
|
861
|
+
state: detectStateManagement(dependencies), // zustand, redux, recoil
|
|
862
|
+
notifications: detectToasts(dependencies), // sonner, react-toastify, notistack
|
|
863
|
+
};
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### 2. Analyze Existing Components (Reference Pattern)
|
|
867
|
+
|
|
868
|
+
```typescript
|
|
869
|
+
// Search for existing CRUD modules as reference
|
|
870
|
+
const existingModules = await searchFiles('src/features/**/components/**/*.tsx');
|
|
871
|
+
|
|
872
|
+
// Detect patterns from existing code:
|
|
873
|
+
const patterns = {
|
|
874
|
+
table: detectTableComponent(existingModules), // MaterialReactTable | DataGrid | TanStack
|
|
875
|
+
drawer: detectDrawerPattern(existingModules), // MUI Drawer | Dialog | Full Page
|
|
876
|
+
filters: detectFiltersPattern(existingModules), // Collapsible | Drawer | Always Visible
|
|
877
|
+
formLayout: detectFormLayout(existingModules), // Single form | Stepper | Tabs
|
|
878
|
+
permissionGuards: detectAuthPattern(existingModules), // useAuth | usePermissions | role checks
|
|
879
|
+
};
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### 3. Deep UI Pattern Extraction (Enhanced)
|
|
883
|
+
|
|
884
|
+
**Extract EXACT design patterns from existing code (spacing, colors, typography, components):**
|
|
885
|
+
|
|
886
|
+
```typescript
|
|
887
|
+
// Analyze existing table components
|
|
888
|
+
const existingTables = await glob('src/features/**/components/*Table.tsx');
|
|
889
|
+
const existingForms = await glob('src/features/**/components/*Form.tsx');
|
|
890
|
+
const existingDrawers = await glob('src/features/**/components/*Drawer.tsx');
|
|
891
|
+
const existingDialogs = await glob('src/features/**/components/*Dialog.tsx');
|
|
892
|
+
|
|
893
|
+
const extractedDefaults = {
|
|
894
|
+
pagination: {
|
|
895
|
+
pageSize: detectMode(existingTables.map((t) => extractPaginationSize(t))), // Mode = 10
|
|
896
|
+
pageSizeOptions: detectUnique(existingTables.map((t) => extractPaginationOptions(t))), // [10, 25, 50, 100]
|
|
897
|
+
},
|
|
898
|
+
debounce: {
|
|
899
|
+
search: detectMode(existingHooks.map((h) => extractDebounceTime(h))), // 300ms
|
|
900
|
+
},
|
|
901
|
+
table: {
|
|
902
|
+
density: detectMode(existingTables.map((t) => extractDensity(t))), // 'comfortable'
|
|
903
|
+
showProgressBars: true, // All tables use this
|
|
904
|
+
showAlertBanner: true,
|
|
905
|
+
enableRowActions: true,
|
|
906
|
+
positionActionsColumn: detectMode(existingTables.map((t) => extractActionsPosition(t))), // 'last'
|
|
907
|
+
},
|
|
908
|
+
queryOptions: {
|
|
909
|
+
staleTime: detectMode(existingHooks.map((h) => extractStaleTime(h))), // Most common value
|
|
910
|
+
gcTime: detectMode(existingHooks.map((h) => extractGcTime(h))),
|
|
911
|
+
retry: 1, // Default for mutations
|
|
912
|
+
},
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
// Deep UI Pattern Analysis
|
|
916
|
+
const uiPatterns = {
|
|
917
|
+
table: {
|
|
918
|
+
padding: extractMode(existingTables, (code) => extractSxProp(code, 'p')), // theme.spacing(2, 3)
|
|
919
|
+
headerColor: extractMode(existingTables, (code) => extractSxProp(code, 'backgroundColor')),
|
|
920
|
+
actionButtons: extractMode(existingTables, (code) => extractActionButtonStyle(code)),
|
|
921
|
+
emptyState: extractMode(existingTables, (code) => extractEmptyStateComponent(code)),
|
|
922
|
+
loading: extractMode(existingTables, (code) => extractLoadingComponent(code)),
|
|
923
|
+
},
|
|
924
|
+
form: {
|
|
925
|
+
layout: detectMode(existingForms.map((f) => detectFormLayout(f))), // 'single-column' | 'two-column'
|
|
926
|
+
maxWidth: detectMode(existingForms.map((f) => extractMaxWidth(f))), // '600px'
|
|
927
|
+
spacing: detectMode(existingForms.map((f) => extractFieldSpacing(f))), // theme.spacing(3)
|
|
928
|
+
textFieldVariant: detectMode(existingForms.map((f) => extractTextFieldVariant(f))), // 'outlined'
|
|
929
|
+
labelPosition: detectMode(existingForms.map((f) => extractLabelPosition(f))), // 'top'
|
|
930
|
+
buttonAlignment: detectMode(existingForms.map((f) => extractButtonAlignment(f))), // 'right'
|
|
931
|
+
validationDisplay: detectMode(existingForms.map((f) => extractValidationStyle(f))), // 'inline'
|
|
932
|
+
},
|
|
933
|
+
drawer: {
|
|
934
|
+
width: detectMode(existingDrawers.map((d) => extractDrawerWidth(d))), // 600
|
|
935
|
+
anchor: detectMode(existingDrawers.map((d) => extractDrawerAnchor(d))), // 'right'
|
|
936
|
+
headerHeight: detectMode(existingDrawers.map((d) => extractHeaderHeight(d))), // 64
|
|
937
|
+
headerPadding: detectMode(existingDrawers.map((d) => extractHeaderPadding(d))),
|
|
938
|
+
contentPadding: detectMode(existingDrawers.map((d) => extractContentPadding(d))),
|
|
939
|
+
footerLayout: detectMode(existingDrawers.map((d) => extractFooterLayout(d))),
|
|
940
|
+
closeIconPosition: detectMode(existingDrawers.map((d) => extractCloseIconPosition(d))),
|
|
941
|
+
},
|
|
942
|
+
dialog: {
|
|
943
|
+
maxWidth: detectMode(existingDialogs.map((d) => extractDialogMaxWidth(d))), // 'sm' (600px)
|
|
944
|
+
titleAlignment: detectMode(existingDialogs.map((d) => extractTitleAlignment(d))), // 'left'
|
|
945
|
+
buttonOrder: detectMode(existingDialogs.map((d) => extractButtonOrder(d))), // ['cancel', 'confirm']
|
|
946
|
+
destructiveColor: detectMode(existingDialogs.map((d) => extractDestructiveColor(d))), // 'error'
|
|
947
|
+
},
|
|
948
|
+
reusableComponents: {
|
|
949
|
+
statusBadge: findComponent('StatusBadge'),
|
|
950
|
+
roleChip: findComponent('RoleChip'),
|
|
951
|
+
emptyState: findComponent('EmptyState'),
|
|
952
|
+
loadingSkeleton: findComponent('LoadingSkeleton'),
|
|
953
|
+
},
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
// Helper functions
|
|
957
|
+
function extractMode<T>(files: string[], extractor: (code: string) => T): T {
|
|
958
|
+
const values = files.map((f) => extractor(readFileSync(f, 'utf-8')));
|
|
959
|
+
return statisticalMode(values); // Most common value
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function extractTextFieldVariant(code: string): 'outlined' | 'filled' | 'standard' {
|
|
963
|
+
const match = code.match(/<TextField[^>]+variant="([^"]+)"/);
|
|
964
|
+
return (match?.[1] as any) || 'outlined';
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function extractFormLayout(code: string): 'single-column' | 'two-column' | 'grid' {
|
|
968
|
+
if (code.includes('Grid container') || code.includes('grid-template-columns')) {
|
|
969
|
+
return 'grid';
|
|
970
|
+
}
|
|
971
|
+
if (code.match(/Grid.*xs=\{6\}/)) {
|
|
972
|
+
return 'two-column';
|
|
973
|
+
}
|
|
974
|
+
return 'single-column';
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function extractSxProp(code: string, prop: string): string {
|
|
978
|
+
const sxMatch = code.match(/sx=\{\{([^}]+)\}\}/);
|
|
979
|
+
if (!sxMatch) return 'not-found';
|
|
980
|
+
|
|
981
|
+
const sxContent = sxMatch[1];
|
|
982
|
+
const propMatch = sxContent.match(new RegExp(`${prop}:\s*([^,}]+)`));
|
|
983
|
+
return propMatch?.[1]?.trim() || 'not-found';
|
|
984
|
+
}
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### 4. Reference Components for Consistency
|
|
988
|
+
|
|
989
|
+
Identify existing components to use as templates:
|
|
990
|
+
|
|
991
|
+
```typescript
|
|
992
|
+
const referenceComponents = {
|
|
993
|
+
table: 'src/features/organizations/components/OrganizationTable.tsx',
|
|
994
|
+
page: 'src/features/organizations/pages/OrganizationsPage.tsx',
|
|
995
|
+
hooks: 'src/features/backoffice-users/hooks/useBackofficeUsers.ts',
|
|
996
|
+
mutations: 'src/features/backoffice-users/hooks/useBackofficeUserMutations.ts',
|
|
997
|
+
layout: 'src/components/layout/AppLayout.tsx',
|
|
998
|
+
sidebar: 'src/components/layout/Sidebar.tsx',
|
|
999
|
+
};
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
---
|
|
1003
|
+
|
|
1004
|
+
## Phase 1: Analysis Summary (Show Only)
|
|
1005
|
+
|
|
1006
|
+
Present detected configuration, API analysis, and implementation audit to user:
|
|
1007
|
+
|
|
1008
|
+
```
|
|
1009
|
+
🔍 Project Stack Detected:
|
|
1010
|
+
|
|
1011
|
+
UI: Material-UI v5.15.10
|
|
1012
|
+
Table: Material React Table v2.11.0 ✅
|
|
1013
|
+
Forms: React Hook Form v7.50.0 + Zod v3.22.4 ✅
|
|
1014
|
+
Data: TanStack Query v5.20.0 ✅
|
|
1015
|
+
State: Zustand v4.5.0 ✅
|
|
1016
|
+
Toasts: Sonner v1.4.0 ✅
|
|
1017
|
+
|
|
1018
|
+
📐 UX Standards (from existing code):
|
|
1019
|
+
✅ CRUD Pattern: Drawer (600px, right side)
|
|
1020
|
+
✅ Table: MaterialReactTable with sorting, filtering, pagination
|
|
1021
|
+
✅ Filters: Collapsible panel (MUI Accordion)
|
|
1022
|
+
✅ Forms: React Hook Form + Zod validation (inline errors)
|
|
1023
|
+
✅ Delete: Confirmation Dialog (error color for destructive)
|
|
1024
|
+
✅ Permissions: Hook-based (useAuthStore + role checks)
|
|
1025
|
+
|
|
1026
|
+
📊 API Module Analysis: Users
|
|
1027
|
+
|
|
1028
|
+
Endpoints Found:
|
|
1029
|
+
✅ GET /api/users (List with pagination)
|
|
1030
|
+
✅ POST /api/users (Create)
|
|
1031
|
+
✅ GET /api/users/{id} (Read)
|
|
1032
|
+
✅ PUT /api/users/{id} (Update)
|
|
1033
|
+
✅ DELETE /api/users/{id} (Delete)
|
|
1034
|
+
ℹ️ GET /api/users/me (Profile)
|
|
1035
|
+
ℹ️ PUT /api/users/me/password (Change Password)
|
|
1036
|
+
|
|
1037
|
+
Entity Schema:
|
|
1038
|
+
Fields (8): id, email, firstName, lastName, status, role, createdAt, updatedAt
|
|
1039
|
+
Required: email, firstName, lastName, roleId
|
|
1040
|
+
Enums: status (active, pending, suspended)
|
|
1041
|
+
Relations (1): role → /api/roles (many-to-one, display: name)
|
|
1042
|
+
|
|
1043
|
+
Features:
|
|
1044
|
+
✅ Server-side pagination (page, limit, max: 100)
|
|
1045
|
+
✅ Search (by name, email)
|
|
1046
|
+
✅ Filters (roleId, status)
|
|
1047
|
+
✅ Authentication (Bearer JWT)
|
|
1048
|
+
✅ Role-based access (ROOT, OWNER, ADMIN)
|
|
1049
|
+
❌ Bulk operations
|
|
1050
|
+
❌ Export
|
|
1051
|
+
|
|
1052
|
+
📊 Current Implementation Status:
|
|
1053
|
+
|
|
1054
|
+
🗂️ Feature Structure:
|
|
1055
|
+
⚠️ Directory exists: src/features/users/ (partial implementation)
|
|
1056
|
+
|
|
1057
|
+
📦 Types (2/3 complete):
|
|
1058
|
+
✅ UserResponseDto (matching OpenAPI)
|
|
1059
|
+
⚠️ CreateUserDto (missing: roleId, organizationId)
|
|
1060
|
+
❌ UpdateUserDto (not found)
|
|
1061
|
+
|
|
1062
|
+
🔧 Zod Schemas (0/3 complete):
|
|
1063
|
+
❌ No validation schemas found
|
|
1064
|
+
|
|
1065
|
+
🌐 API Services (3/5 endpoints):
|
|
1066
|
+
✅ GET /users
|
|
1067
|
+
✅ POST /users
|
|
1068
|
+
✅ GET /users/{id}
|
|
1069
|
+
❌ PUT /users/{id} (not implemented)
|
|
1070
|
+
❌ DELETE /users/{id} (not implemented)
|
|
1071
|
+
|
|
1072
|
+
🪝 Hooks (1/4 complete):
|
|
1073
|
+
✅ useUsers (type-safe, correct query key)
|
|
1074
|
+
❌ useCreateUser (not found)
|
|
1075
|
+
❌ useUpdateUser (not found)
|
|
1076
|
+
❌ useDeleteUser (not found)
|
|
1077
|
+
|
|
1078
|
+
🎨 UI Components (1/5 complete):
|
|
1079
|
+
⚠️ UsersTable.tsx (uses old DataGrid, should use MaterialReactTable)
|
|
1080
|
+
❌ UserDrawer.tsx (not found)
|
|
1081
|
+
❌ UserForm.tsx (not found)
|
|
1082
|
+
❌ DeleteUserDialog.tsx (not found)
|
|
1083
|
+
❌ UserFilters.tsx (not found)
|
|
1084
|
+
|
|
1085
|
+
📄 Pages:
|
|
1086
|
+
⚠️ UsersPage.tsx (incomplete, missing drawer integration)
|
|
1087
|
+
|
|
1088
|
+
🧪 Tests (0/2 complete):
|
|
1089
|
+
❌ No tests found
|
|
1090
|
+
|
|
1091
|
+
📊 Implementation Score: 35/100
|
|
1092
|
+
|
|
1093
|
+
💡 Recommendation: REFACTOR_COMPLETE
|
|
1094
|
+
Strategy: Keep working types, refactor table, complete missing pieces
|
|
1095
|
+
|
|
1096
|
+
Action Items:
|
|
1097
|
+
- Update 1 type (CreateUserDto: +2 fields)
|
|
1098
|
+
- Create 1 type (UpdateUserDto)
|
|
1099
|
+
- Create 3 Zod schemas
|
|
1100
|
+
- Add 2 API endpoints (PUT, DELETE)
|
|
1101
|
+
- Create 3 mutation hooks
|
|
1102
|
+
- Refactor table (DataGrid → MaterialReactTable)
|
|
1103
|
+
- Create 4 UI components
|
|
1104
|
+
- Add comprehensive tests
|
|
1105
|
+
|
|
1106
|
+
⏱️ Estimated: 13 SP / 8-10 hours
|
|
1107
|
+
|
|
1108
|
+
Complexity: 🏗️ COMPLEX (refactor + complete)
|
|
1109
|
+
|
|
1110
|
+
Reference Components (for consistency):
|
|
1111
|
+
📄 Table: src/features/organizations/components/OrganizationTable.tsx
|
|
1112
|
+
📄 Page: src/features/organizations/pages/OrganizationsPage.tsx
|
|
1113
|
+
📄 Hooks: src/features/backoffice-users/hooks/useBackofficeUsers.ts
|
|
1114
|
+
📄 Mutations: src/features/backoffice-users/hooks/useBackofficeUserMutations.ts
|
|
1115
|
+
|
|
1116
|
+
✅ All standards locked. Module will match existing patterns.
|
|
1117
|
+
|
|
1118
|
+
Analysis complete. Returning data to flow-work orchestrator...
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
---
|
|
1122
|
+
|
|
1123
|
+
## 📤 OUTPUT Format (CRITICAL)
|
|
1124
|
+
|
|
1125
|
+
**This sub-prompt MUST return a structured JSON object that flow-work can consume.**
|
|
1126
|
+
|
|
1127
|
+
### OpenAPIAnalysisResult Interface
|
|
1128
|
+
|
|
1129
|
+
```typescript
|
|
1130
|
+
interface OpenAPIAnalysisResult {
|
|
1131
|
+
// Meta
|
|
1132
|
+
success: boolean;
|
|
1133
|
+
module: string;
|
|
1134
|
+
apiUrl: string;
|
|
1135
|
+
timestamp: string; // ISO 8601
|
|
1136
|
+
|
|
1137
|
+
// Implementation Audit
|
|
1138
|
+
implementationAudit: {
|
|
1139
|
+
status: 'NOT_IMPLEMENTED' | 'PARTIAL' | 'COMPLETE';
|
|
1140
|
+
score: number; // 0-100
|
|
1141
|
+
strategy: 'FULL_NEW' | 'REFACTOR_COMPLETE' | 'MINOR_FIXES';
|
|
1142
|
+
types: {
|
|
1143
|
+
matching: string[];
|
|
1144
|
+
missing: Array<{ schema: string; action: string; reason: string }>;
|
|
1145
|
+
incorrect: Array<{
|
|
1146
|
+
schema: string;
|
|
1147
|
+
file: string;
|
|
1148
|
+
issues: string[];
|
|
1149
|
+
action: string;
|
|
1150
|
+
}>;
|
|
1151
|
+
};
|
|
1152
|
+
hooks: {
|
|
1153
|
+
implemented: string[];
|
|
1154
|
+
missing: Array<{
|
|
1155
|
+
endpoint: string;
|
|
1156
|
+
expectedHook: string;
|
|
1157
|
+
action: string;
|
|
1158
|
+
}>;
|
|
1159
|
+
incorrect: Array<{
|
|
1160
|
+
hook: string;
|
|
1161
|
+
file: string;
|
|
1162
|
+
issues: string[];
|
|
1163
|
+
action: string;
|
|
1164
|
+
}>;
|
|
1165
|
+
};
|
|
1166
|
+
components: {
|
|
1167
|
+
complete: string[];
|
|
1168
|
+
missing: Array<{ component: string; action: string }>;
|
|
1169
|
+
nonStandard: Array<{
|
|
1170
|
+
component: string;
|
|
1171
|
+
file: string;
|
|
1172
|
+
issues: string[];
|
|
1173
|
+
action: string;
|
|
1174
|
+
}>;
|
|
1175
|
+
};
|
|
1176
|
+
actionItems: string[]; // Human-readable list
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
// Project Standards (detected)
|
|
1180
|
+
projectStandards: {
|
|
1181
|
+
stack: {
|
|
1182
|
+
ui: string; // '@mui/material v5.15.10'
|
|
1183
|
+
table: string; // 'material-react-table v2.11.0'
|
|
1184
|
+
forms: string; // 'react-hook-form v7.50.0'
|
|
1185
|
+
validation: string; // 'zod v3.22.4'
|
|
1186
|
+
query: string; // '@tanstack/react-query v5.20.0'
|
|
1187
|
+
state: string; // 'zustand v4.5.0'
|
|
1188
|
+
notifications: string; // 'sonner v1.4.0'
|
|
1189
|
+
};
|
|
1190
|
+
patterns: {
|
|
1191
|
+
crudPattern: string; // 'drawer' | 'modal' | 'page'
|
|
1192
|
+
drawerWidth: number; // 600
|
|
1193
|
+
drawerAnchor: string; // 'right'
|
|
1194
|
+
tableComponent: string; // 'MaterialReactTable'
|
|
1195
|
+
filterUI: string; // 'collapsible_panel'
|
|
1196
|
+
formLayout: string; // 'single_form'
|
|
1197
|
+
deleteConfirmation: string; // 'dialog'
|
|
1198
|
+
};
|
|
1199
|
+
defaults: {
|
|
1200
|
+
pagination: {
|
|
1201
|
+
pageSize: number; // 10
|
|
1202
|
+
options: number[]; // [10, 25, 50, 100]
|
|
1203
|
+
};
|
|
1204
|
+
debounce: {
|
|
1205
|
+
search: number; // 300ms
|
|
1206
|
+
};
|
|
1207
|
+
table: {
|
|
1208
|
+
density: string; // 'comfortable'
|
|
1209
|
+
showProgressBars: boolean;
|
|
1210
|
+
showAlertBanner: boolean;
|
|
1211
|
+
};
|
|
1212
|
+
caching: {
|
|
1213
|
+
staleTime: number; // 30000ms (30s)
|
|
1214
|
+
gcTime: number; // 300000ms (5min)
|
|
1215
|
+
retry: number; // 1 for mutations
|
|
1216
|
+
};
|
|
1217
|
+
};
|
|
1218
|
+
referenceComponents: {
|
|
1219
|
+
table: string; // 'src/features/organizations/components/OrganizationTable.tsx'
|
|
1220
|
+
page: string; // 'src/features/organizations/pages/OrganizationsPage.tsx'
|
|
1221
|
+
hooks: string; // 'src/features/backoffice-users/hooks/useBackofficeUsers.ts'
|
|
1222
|
+
mutations: string; // 'src/features/backoffice-users/hooks/useBackofficeUserMutations.ts'
|
|
1223
|
+
};
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
// OpenAPI Analysis
|
|
1227
|
+
openapi: {
|
|
1228
|
+
version: string; // '3.0.3'
|
|
1229
|
+
title: string; // 'CROSS Backoffice API'
|
|
1230
|
+
totalPaths: number; // 45
|
|
1231
|
+
totalSchemas: number; // 32
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
// Module Endpoints
|
|
1235
|
+
endpoints: Array<{
|
|
1236
|
+
method: string; // 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'
|
|
1237
|
+
path: string; // '/api/users'
|
|
1238
|
+
operationId: string; // 'getUsers'
|
|
1239
|
+
summary: string; // 'List all users with pagination'
|
|
1240
|
+
tags: string[]; // ['Users']
|
|
1241
|
+
parameters: Array<{
|
|
1242
|
+
name: string; // 'page'
|
|
1243
|
+
in: string; // 'query'
|
|
1244
|
+
required: boolean;
|
|
1245
|
+
schema: {
|
|
1246
|
+
type: string;
|
|
1247
|
+
default?: any;
|
|
1248
|
+
enum?: any[];
|
|
1249
|
+
};
|
|
1250
|
+
}>;
|
|
1251
|
+
requestBody?: {
|
|
1252
|
+
required: boolean;
|
|
1253
|
+
schema: string; // 'CreateUserDto' (schema name)
|
|
1254
|
+
};
|
|
1255
|
+
responses: {
|
|
1256
|
+
[statusCode: string]: {
|
|
1257
|
+
description: string;
|
|
1258
|
+
schema?: string; // 'UserResponseDto' or 'PaginatedResponse<UserResponseDto>'
|
|
1259
|
+
};
|
|
1260
|
+
};
|
|
1261
|
+
}>;
|
|
1262
|
+
|
|
1263
|
+
// Entity Schemas (DTOs)
|
|
1264
|
+
schemas: {
|
|
1265
|
+
response: {
|
|
1266
|
+
name: string; // 'UserResponseDto'
|
|
1267
|
+
fields: FieldSpec[];
|
|
1268
|
+
};
|
|
1269
|
+
create: {
|
|
1270
|
+
name: string; // 'CreateUserDto'
|
|
1271
|
+
fields: FieldSpec[];
|
|
1272
|
+
};
|
|
1273
|
+
update: {
|
|
1274
|
+
name: string; // 'UpdateUserDto'
|
|
1275
|
+
fields: FieldSpec[];
|
|
1276
|
+
};
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
// Field Specifications
|
|
1280
|
+
fields: FieldSpec[]; // All unique fields across DTOs
|
|
1281
|
+
|
|
1282
|
+
// Detected Features
|
|
1283
|
+
features: {
|
|
1284
|
+
pagination: {
|
|
1285
|
+
enabled: boolean;
|
|
1286
|
+
params: string[]; // ['page', 'limit']
|
|
1287
|
+
responseFormat: 'array' | 'object';
|
|
1288
|
+
dataKey?: string; // 'items' | 'data'
|
|
1289
|
+
totalKey?: string; // 'total' | 'meta.total'
|
|
1290
|
+
maxPageSize?: number; // 100
|
|
1291
|
+
};
|
|
1292
|
+
search: {
|
|
1293
|
+
enabled: boolean;
|
|
1294
|
+
params: string[]; // ['search', 'q']
|
|
1295
|
+
};
|
|
1296
|
+
sorting: {
|
|
1297
|
+
enabled: boolean;
|
|
1298
|
+
params: string[]; // ['sortBy', 'order']
|
|
1299
|
+
fields?: string[]; // Sortable fields
|
|
1300
|
+
};
|
|
1301
|
+
filtering: {
|
|
1302
|
+
enabled: boolean;
|
|
1303
|
+
fields: string[]; // ['status', 'roleId']
|
|
1304
|
+
};
|
|
1305
|
+
authentication: {
|
|
1306
|
+
type: 'bearer' | 'apiKey' | 'oauth2';
|
|
1307
|
+
required: boolean;
|
|
1308
|
+
};
|
|
1309
|
+
authorization: {
|
|
1310
|
+
roles: string[]; // ['ROOT', 'OWNER', 'ADMIN']
|
|
1311
|
+
permissions?: string[]; // ['user:read', 'user:write']
|
|
1312
|
+
};
|
|
1313
|
+
softDelete: {
|
|
1314
|
+
enabled: boolean;
|
|
1315
|
+
field?: string; // 'deletedAt' | 'status'
|
|
1316
|
+
};
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
// Relationships
|
|
1320
|
+
relationships: Array<{
|
|
1321
|
+
field: string; // 'roleId' or 'role'
|
|
1322
|
+
relatedEntity: string; // 'Role'
|
|
1323
|
+
type: 'many-to-one' | 'one-to-many' | 'many-to-many' | 'one-to-one';
|
|
1324
|
+
foreignKey: string; // 'roleId'
|
|
1325
|
+
endpoint?: string; // '/api/roles'
|
|
1326
|
+
displayField: string; // 'name' (field to display in UI)
|
|
1327
|
+
populated: boolean; // true if backend returns full object
|
|
1328
|
+
}>;
|
|
1329
|
+
|
|
1330
|
+
// Complexity Analysis
|
|
1331
|
+
complexity: {
|
|
1332
|
+
level: 'SIMPLE' | 'MEDIUM' | 'COMPLEX';
|
|
1333
|
+
estimatedFiles: number; // 18-20
|
|
1334
|
+
estimatedSP: number; // 8
|
|
1335
|
+
estimatedHours: number; // 5-6
|
|
1336
|
+
factors: {
|
|
1337
|
+
endpoints: number; // 5
|
|
1338
|
+
relations: number; // 2
|
|
1339
|
+
customEndpoints: number; // 0
|
|
1340
|
+
validationRules: number; // 15
|
|
1341
|
+
};
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
// Validation & Warnings
|
|
1345
|
+
warnings: string[]; // Issues detected (missing endpoints, inconsistent naming, etc.)
|
|
1346
|
+
suggestions: string[]; // Recommendations
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
interface FieldSpec {
|
|
1350
|
+
name: string; // 'email'
|
|
1351
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'enum' | 'array' | 'object' | 'uuid';
|
|
1352
|
+
required: boolean;
|
|
1353
|
+
nullable: boolean;
|
|
1354
|
+
validation?: {
|
|
1355
|
+
min?: number;
|
|
1356
|
+
max?: number;
|
|
1357
|
+
pattern?: string;
|
|
1358
|
+
format?: 'email' | 'url' | 'uuid' | 'date-time' | 'password';
|
|
1359
|
+
enum?: string[];
|
|
1360
|
+
};
|
|
1361
|
+
relation?: {
|
|
1362
|
+
entity: string; // 'Role'
|
|
1363
|
+
type: 'many-to-one' | 'one-to-many' | 'many-to-many';
|
|
1364
|
+
foreignKey: string; // 'roleId'
|
|
1365
|
+
populated: boolean;
|
|
1366
|
+
displayField: string; // 'name'
|
|
1367
|
+
};
|
|
1368
|
+
category: 'auto-generated' | 'read-only' | 'editable' | 'metadata';
|
|
1369
|
+
usage: {
|
|
1370
|
+
showInTable: boolean;
|
|
1371
|
+
showInForm: boolean; // In create/edit forms
|
|
1372
|
+
showInDetails: boolean;
|
|
1373
|
+
editable: boolean; // Can be edited after creation
|
|
1374
|
+
};
|
|
1375
|
+
default?: any;
|
|
1376
|
+
description?: string;
|
|
1377
|
+
|
|
1378
|
+
// DTO specific
|
|
1379
|
+
inResponseDto: boolean;
|
|
1380
|
+
inCreateDto: boolean;
|
|
1381
|
+
inUpdateDto: boolean;
|
|
1382
|
+
}
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
### Return Format
|
|
1386
|
+
|
|
1387
|
+
```json
|
|
1388
|
+
{
|
|
1389
|
+
"success": true,
|
|
1390
|
+
"module": "users",
|
|
1391
|
+
"apiUrl": "http://localhost:3001/api/docs-json",
|
|
1392
|
+
"timestamp": "2026-03-04T10:30:00-03:00",
|
|
1393
|
+
"projectStandards": {
|
|
1394
|
+
/* ... */
|
|
1395
|
+
},
|
|
1396
|
+
"openapi": {
|
|
1397
|
+
/* ... */
|
|
1398
|
+
},
|
|
1399
|
+
"endpoints": [
|
|
1400
|
+
/* ... */
|
|
1401
|
+
],
|
|
1402
|
+
"schemas": {
|
|
1403
|
+
/* ... */
|
|
1404
|
+
},
|
|
1405
|
+
"fields": [
|
|
1406
|
+
/* ... */
|
|
1407
|
+
],
|
|
1408
|
+
"features": {
|
|
1409
|
+
/* ... */
|
|
1410
|
+
},
|
|
1411
|
+
"relationships": [
|
|
1412
|
+
/* ... */
|
|
1413
|
+
],
|
|
1414
|
+
"complexity": {
|
|
1415
|
+
/* ... */
|
|
1416
|
+
},
|
|
1417
|
+
"warnings": [],
|
|
1418
|
+
"suggestions": []
|
|
1419
|
+
}
|
|
1420
|
+
```
|
|
1421
|
+
|
|
1422
|
+
---
|
|
1423
|
+
|
|
1424
|
+
## Best Practices Reference (For flow-work)
|
|
1425
|
+
|
|
1426
|
+
**These patterns should be enforced by flow-work during code generation:**
|
|
1427
|
+
|
|
1428
|
+
### 1. Use MaterialReactTable (MRT) - Project Standard
|
|
1429
|
+
|
|
1430
|
+
```typescript
|
|
1431
|
+
// ✅ CORRECT: Use MRT as detected in OrganizationTable.tsx
|
|
1432
|
+
import { MaterialReactTable, useMaterialReactTable, type MRT_ColumnDef } from 'material-react-table';
|
|
1433
|
+
|
|
1434
|
+
const table = useMaterialReactTable({
|
|
1435
|
+
columns,
|
|
1436
|
+
data: users ?? [],
|
|
1437
|
+
enableRowActions: true,
|
|
1438
|
+
manualPagination: true,
|
|
1439
|
+
rowCount: totalRows,
|
|
1440
|
+
state: { pagination, isLoading },
|
|
1441
|
+
onPaginationChange: setPagination,
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
return <MaterialReactTable table={table} />;
|
|
1445
|
+
|
|
1446
|
+
// ❌ WRONG: Don't use other table libraries
|
|
1447
|
+
// import { DataGrid } from '@mui/x-data-grid'; // NO
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
### 2. Query Key Management
|
|
1451
|
+
|
|
1452
|
+
```typescript
|
|
1453
|
+
// ✅ CORRECT: Export query key constant
|
|
1454
|
+
export const BACKOFFICE_USERS_QUERY_KEY = 'backoffice-users';
|
|
1455
|
+
|
|
1456
|
+
export const useBackofficeUsers = (params: GetBackofficeUsersParams) => {
|
|
1457
|
+
return useQuery({
|
|
1458
|
+
queryKey: [BACKOFFICE_USERS_QUERY_KEY, params],
|
|
1459
|
+
queryFn: () => backofficeUsersService.getBackofficeUsers(params),
|
|
1460
|
+
});
|
|
1461
|
+
};
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
### 3. Cache Invalidation Strategy (Broad)
|
|
1465
|
+
|
|
1466
|
+
```typescript
|
|
1467
|
+
// ✅ CORRECT: Invalidate ALL related queries
|
|
1468
|
+
void queryClient.invalidateQueries({ queryKey: [BACKOFFICE_USERS_QUERY_KEY] });
|
|
1469
|
+
|
|
1470
|
+
// ❌ WRONG: Too specific (misses cached list queries)
|
|
1471
|
+
// void queryClient.invalidateQueries({ queryKey: [BACKOFFICE_USERS_QUERY_KEY, id] });
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
### 4. Toast Message Standards
|
|
1475
|
+
|
|
1476
|
+
```typescript
|
|
1477
|
+
// ✅ CREATE success
|
|
1478
|
+
toast.success('Usuario creado exitosamente. Email de bienvenida enviado.');
|
|
1479
|
+
|
|
1480
|
+
// ✅ UPDATE success
|
|
1481
|
+
toast.success('Usuario actualizado');
|
|
1482
|
+
|
|
1483
|
+
// ✅ ERROR with status
|
|
1484
|
+
if (error.response?.status === 409) {
|
|
1485
|
+
toast.error('No puedes suspender al último OWNER activo de la organización');
|
|
1486
|
+
}
|
|
1487
|
+
```
|
|
1488
|
+
|
|
1489
|
+
### 5. Empty & Loading States
|
|
1490
|
+
|
|
1491
|
+
```typescript
|
|
1492
|
+
const table = useMaterialReactTable({
|
|
1493
|
+
data: data?.items ?? [],
|
|
1494
|
+
state: {
|
|
1495
|
+
isLoading, // Initial load → Full skeleton
|
|
1496
|
+
showProgressBars: isFetching, // Refetch → Top progress bar
|
|
1497
|
+
showAlertBanner: isError, // Error → Red banner
|
|
1498
|
+
},
|
|
1499
|
+
});
|
|
1500
|
+
```
|
|
1501
|
+
|
|
1502
|
+
---
|
|
1503
|
+
|
|
1504
|
+
## Error Handling
|
|
1505
|
+
|
|
1506
|
+
**If analysis fails at any step:**
|
|
1507
|
+
|
|
1508
|
+
```json
|
|
1509
|
+
{
|
|
1510
|
+
"success": false,
|
|
1511
|
+
"module": "users",
|
|
1512
|
+
"error": "Failed to fetch OpenAPI spec",
|
|
1513
|
+
"details": "Connection timeout after 10 seconds",
|
|
1514
|
+
"suggestions": [
|
|
1515
|
+
"1. Ensure backend server is running on http://localhost:3001",
|
|
1516
|
+
"2. Check CORS configuration in backend",
|
|
1517
|
+
"3. Verify /api/docs-json endpoint is available",
|
|
1518
|
+
"4. Try --api-url=http://other-host:3000/api/docs"
|
|
1519
|
+
]
|
|
1520
|
+
}
|
|
1521
|
+
```
|
|
1522
|
+
|
|
1523
|
+
**flow-work should handle this by:**
|
|
1524
|
+
|
|
1525
|
+
1. Showing error to user
|
|
1526
|
+
2. Offering retry or manual mode
|
|
1527
|
+
3. Logging error for debugging
|
|
1528
|
+
|
|
1529
|
+
---
|
|
1530
|
+
|
|
1531
|
+
## End of Sub-Prompt
|
|
1532
|
+
|
|
1533
|
+
**This prompt returns control to `flow-work` with the `OpenAPIAnalysisResult` data structure.**
|
|
1534
|
+
|
|
1535
|
+
Flow-work will use this data to:
|
|
1536
|
+
|
|
1537
|
+
- Generate detailed `work.md` (Phase 2)
|
|
1538
|
+
- Create branch with naming convention (Phase 3)
|
|
1539
|
+
- Execute implementation (Phase 3)
|
|
1540
|
+
- Validate and finalize (Phase 4)
|