@atlashub/smartstack-cli 3.33.0 → 3.35.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/.documentation/agents.html +5 -1
- package/.documentation/apex.html +644 -0
- package/.documentation/business-analyse.html +81 -1
- package/.documentation/cli-commands.html +5 -1
- package/.documentation/commands.html +5 -1
- package/.documentation/efcore.html +5 -1
- package/.documentation/gitflow.html +5 -1
- package/.documentation/hooks.html +5 -1
- package/.documentation/index.html +60 -2
- package/.documentation/init.html +414 -1
- package/.documentation/installation.html +5 -1
- package/.documentation/ralph-loop.html +365 -216
- package/.documentation/test-web.html +5 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +7 -24
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -2
- package/templates/agents/ba-writer.md +142 -15
- package/templates/mcp-scaffolding/controller.cs.hbs +5 -1
- package/templates/skills/apex/SKILL.md +9 -3
- package/templates/skills/apex/_shared.md +49 -4
- package/templates/skills/{ralph-loop → apex}/references/core-seed-data.md +20 -11
- package/templates/skills/{ralph-loop → apex}/references/error-classification.md +2 -1
- package/templates/skills/apex/references/post-checks.md +463 -3
- package/templates/skills/apex/references/smartstack-api.md +76 -8
- package/templates/skills/apex/references/smartstack-frontend.md +74 -1
- package/templates/skills/apex/references/smartstack-layers.md +21 -3
- package/templates/skills/apex/steps/step-00-init.md +121 -1
- package/templates/skills/apex/steps/step-01-analyze.md +58 -0
- package/templates/skills/apex/steps/step-02-plan.md +36 -0
- package/templates/skills/apex/steps/step-03-execute.md +114 -7
- package/templates/skills/apex/steps/step-04-examine.md +116 -2
- package/templates/skills/business-analyse/SKILL.md +31 -20
- package/templates/skills/business-analyse/_module-loop.md +68 -9
- package/templates/skills/business-analyse/_shared.md +80 -21
- package/templates/skills/business-analyse/questionnaire/00-application.md +4 -2
- package/templates/skills/business-analyse/questionnaire/00b-project.md +85 -0
- package/templates/skills/business-analyse/references/deploy-modes.md +69 -0
- package/templates/skills/business-analyse/references/team-orchestration.md +158 -7
- package/templates/skills/business-analyse/schemas/application-schema.json +15 -1
- package/templates/skills/business-analyse/schemas/project-schema.json +490 -0
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +2 -1
- package/templates/skills/business-analyse/steps/step-00-init.md +220 -38
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +184 -5
- package/templates/skills/business-analyse/steps/step-01b-applications.md +423 -0
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +23 -6
- package/templates/skills/business-analyse/steps/step-03c-compile.md +14 -2
- package/templates/skills/business-analyse/steps/step-03d-validate.md +32 -7
- package/templates/skills/business-analyse/steps/step-04a-collect.md +111 -0
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +296 -103
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +46 -14
- package/templates/skills/documentation/SKILL.md +92 -2
- package/templates/skills/ralph-loop/SKILL.md +14 -17
- package/templates/skills/ralph-loop/references/category-rules.md +63 -683
- package/templates/skills/ralph-loop/references/compact-loop.md +188 -428
- package/templates/skills/ralph-loop/references/section-splitting.md +439 -0
- package/templates/skills/ralph-loop/references/team-orchestration.md +13 -14
- package/templates/skills/ralph-loop/steps/step-01-task.md +27 -0
- package/templates/skills/ralph-loop/steps/step-02-execute.md +80 -691
- package/templates/skills/ralph-loop/steps/step-03-commit.md +38 -79
- package/templates/skills/ralph-loop/steps/step-04-check.md +39 -58
- package/templates/skills/ralph-loop/steps/step-05-report.md +31 -123
- package/scripts/health-check.sh +0 -168
- package/scripts/postinstall.js +0 -18
|
@@ -1,716 +1,96 @@
|
|
|
1
|
-
# Category
|
|
1
|
+
# Category Ordering & Dependency Rules
|
|
2
2
|
|
|
3
|
-
> **Loaded by:** step-02 (
|
|
4
|
-
> **Purpose:** Defines execution
|
|
3
|
+
> **Loaded by:** step-02 (task ordering validation) and compact-loop.md (batch grouping)
|
|
4
|
+
> **Purpose:** Defines execution order, dependencies, and batch grouping rules.
|
|
5
|
+
> **Note:** All execution rules, MCP tool usage, conventions, and POST-CHECKs are in `/apex` references.
|
|
5
6
|
|
|
6
7
|
---
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
+
## Execution Order
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
**Enums:** `Domain/Enums/{ContextPascal}/{App}/{Module}/`
|
|
12
|
-
**Exceptions:** `Domain/Exceptions/{ContextPascal}/{App}/{Module}/`
|
|
13
|
-
**MCP:** `validate_conventions`
|
|
11
|
+
Tasks MUST be executed in this order. Each category depends on the previous ones.
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
| Order | Category | Depends On | Description |
|
|
14
|
+
|-------|----------|------------|-------------|
|
|
15
|
+
| 0 | `domain` | — | Entities, enums, value objects |
|
|
16
|
+
| 1 | `infrastructure` | domain | EF configs, migrations, seed data |
|
|
17
|
+
| 2 | `application` | infrastructure | Services, DTOs, validators, DI |
|
|
18
|
+
| 3 | `api` | application | Controllers, route attributes |
|
|
19
|
+
| 4 | `seedData` | api | Navigation, permissions, roles, dev data |
|
|
20
|
+
| 5 | `frontend` | seedData | Pages, routes, i18n, components |
|
|
21
|
+
| 6 | `test` | frontend | Unit tests, integration tests, coverage |
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Infrastructure — EF Core Configurations
|
|
24
|
-
|
|
25
|
-
**Folder:** `Infrastructure/Persistence/Configurations/{ContextPascal}/{App}/{Module}/`
|
|
26
|
-
**MCP:** `validate_conventions`, `check_migrations`
|
|
27
|
-
|
|
28
|
-
Rules:
|
|
29
|
-
- One configuration per entity
|
|
30
|
-
- Table name = plural entity name
|
|
31
|
-
- All relationships explicitly configured (no convention-based)
|
|
32
|
-
- Indexes on foreign keys and frequently queried fields
|
|
33
|
-
- Register DbSet in ExtensionsDbContext
|
|
23
|
+
> **Key dependency:** `frontend` depends on `seedData` (not just `api`).
|
|
24
|
+
> Navigation seed data drives the menu — without it, frontend routes have no menu entries.
|
|
34
25
|
|
|
35
26
|
---
|
|
36
27
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
> **CRITICAL:** After EF configs, a migration MUST be created and applied.
|
|
40
|
-
> Without migrations, no database schema = no tests = no API = broken module.
|
|
41
|
-
|
|
42
|
-
**Folder:** `Infrastructure/Persistence/Migrations/` (NEVER subdirectories)
|
|
43
|
-
**MCP:** `suggest_migration` → get migration name
|
|
44
|
-
|
|
45
|
-
Execution sequence:
|
|
46
|
-
0. Ensure `dotnet ef` is on PATH (platform-aware):
|
|
47
|
-
```bash
|
|
48
|
-
if ! dotnet ef --version &>/dev/null; then
|
|
49
|
-
for TOOLS_DIR in "$USERPROFILE/.dotnet/tools" "$HOME/.dotnet/tools" "$LOCALAPPDATA/Microsoft/dotnet/tools"; do
|
|
50
|
-
[ -n "$TOOLS_DIR" ] && [ -d "$TOOLS_DIR" ] && export PATH="$TOOLS_DIR:$PATH"
|
|
51
|
-
done
|
|
52
|
-
dotnet ef --version &>/dev/null || { echo "ERROR: dotnet-ef not found. Install: dotnet tool install --global dotnet-ef"; exit 1; }
|
|
53
|
-
fi
|
|
54
|
-
```
|
|
55
|
-
> **Why:** On Windows (Git Bash), `$USERPROFILE/.dotnet/tools` is the correct path.
|
|
56
|
-
> NEVER use `$HOME/.dotnet/tools` alone — on WSL, `$HOME` resolves to `/home/{user}` where .NET SDK is not installed.
|
|
57
|
-
1. Call `mcp__smartstack__suggest_migration` → get standardized name
|
|
58
|
-
2. `dotnet ef migrations add {Name} --project src/{Infra}.csproj --startup-project src/{Api}.csproj -o Persistence/Migrations`
|
|
59
|
-
3. Cleanup corrupted EF Core artifacts: `for d in src/*/bin?Debug; do [ -d "$d" ] && rm -rf "$d"; done`
|
|
60
|
-
4. `dotnet ef database update --project src/{Infra}.csproj --startup-project src/{Api}.csproj`
|
|
61
|
-
5. `dotnet build --no-restore` → verify build passes
|
|
62
|
-
|
|
63
|
-
**BLOCKING:** If migration fails, DO NOT proceed. Fix the EF configs first.
|
|
64
|
-
|
|
65
|
-
**Rules:**
|
|
66
|
-
- NEVER create subdirectories under Migrations/
|
|
67
|
-
- Always use `-o Persistence/Migrations` flag
|
|
68
|
-
- One migration per module (all entities together)
|
|
69
|
-
- Migration name from MCP (NEVER invented)
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## Infrastructure — Seed Data (MANDATORY reference loading)
|
|
74
|
-
|
|
75
|
-
**Folder:** `Infrastructure/Persistence/Seeding/Data/{Module}/`
|
|
76
|
-
**MCP:** `generate_permissions` (PRIMARY), fallback to templates
|
|
77
|
-
|
|
78
|
-
> **IF** task description contains "seed data", "SeedData", "NavigationModule",
|
|
79
|
-
> "PermissionsSeedData", "RolesSeedData", or "IClientSeedDataProvider":
|
|
80
|
-
> **THEN read `references/core-seed-data.md`** — this is MANDATORY, DO NOT improvise.
|
|
81
|
-
|
|
82
|
-
**Seed Data Chain (9 files minimum):**
|
|
28
|
+
## Dependency Rules
|
|
83
29
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
- **NavigationModuleSeedData.cs**: deterministic GUIDs (SHA256), 4 languages (fr, en, it, de)
|
|
90
|
-
- **Permissions.cs**: Static permission constants (`Permissions.{Module}.Read`). Referenced by `[RequirePermission]`.
|
|
91
|
-
- **PermissionsSeedData.cs**: MCP `generate_permissions` first, fallback to template
|
|
92
|
-
- **RolesSeedData.cs**: code-based role-permission mapping (Admin=wildcard, Manager=CRU, Contributor=CR, Viewer=R)
|
|
93
|
-
|
|
94
|
-
Per-module (MANDATORY when `seedDataCore.navigationSections` exists in feature.json):
|
|
95
|
-
- **NavigationSectionSeedData**: section entries, section translations (4 languages), section-level permissions, and section-level role mappings — all within `NavigationModuleSeedData.cs`, `PermissionsSeedData.cs`, and `RolesSeedData.cs`
|
|
96
|
-
- Section-level permissions: wildcard + CRUD per section (same pattern as module-level)
|
|
97
|
-
- Section-level role mappings: Admin=wildcard, Manager=CRU, Contributor=CR, Viewer=R per section
|
|
98
|
-
|
|
99
|
-
Infrastructure:
|
|
100
|
-
- **SeedConstants.cs**: shared deterministic GUIDs (ApplicationId, ModuleIds)
|
|
101
|
-
- **{App}SeedDataProvider.cs**: implements IClientSeedDataProvider with 4 methods
|
|
102
|
-
- DI: `services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()`
|
|
103
|
-
|
|
104
|
-
**Rules:**
|
|
105
|
-
- IClientSeedDataProvider: SeedNavigationAsync + SeedRolesAsync + SeedPermissionsAsync + SeedRolePermissionsAsync
|
|
106
|
-
- Admin=wildcard(*), Manager=CRU (read+create+update), Contributor=CR (read+create), Viewer=R (read only)
|
|
107
|
-
|
|
108
|
-
**Business seed data (DevDataSeeder):**
|
|
109
|
-
- ALL seeded business entities MUST include `TenantId = {tenantGuid}`
|
|
110
|
-
- Reference entities (types, categories, statuses) MUST set TenantId
|
|
111
|
-
- Use deterministic TenantId from SeedConstants (NEVER hardcoded inline)
|
|
112
|
-
- DevDataSeeder MUST implement `IDevDataSeeder` with idempotent `SeedAsync()` method
|
|
113
|
-
|
|
114
|
-
**Section route conventions (BLOCKING):**
|
|
115
|
-
- `list` section route = module route (e.g., `/business/human-resources/employees`) — NO `/list` suffix
|
|
116
|
-
- `detail` section route = module route + `/:id` (e.g., `/business/human-resources/employees/:id`) — NOT `/detail/:id`
|
|
117
|
-
- Other sections (dashboard, approve, import) = module route + `/{section-kebab}` (normal)
|
|
118
|
-
- FORBIDDEN: `/{module}/list`, `/{module}/detail/:id` — these are CRUD view modes, not sub-areas
|
|
119
|
-
|
|
120
|
-
**FORBIDDEN:**
|
|
121
|
-
- `Guid.NewGuid()` → use deterministic GUIDs
|
|
122
|
-
- Empty seed data classes with only GUIDs and no seeding methods
|
|
123
|
-
- Missing translations (must have all 4 languages)
|
|
124
|
-
- Seeding business entities WITHOUT `TenantId`
|
|
125
|
-
- Navigation section routes ending in `/list` or `/detail/:id`
|
|
126
|
-
|
|
127
|
-
### POST-CHECK: Navigation translations diacritical marks
|
|
128
|
-
|
|
129
|
-
After generating `NavigationTranslationSeedEntry` strings, verify ALL non-English translations contain proper diacritical marks:
|
|
130
|
-
|
|
131
|
-
- **FR** must use: é, è, ê, ë, à, â, ç, ù, û, ô, î (e.g., "Employés" NOT "Employes", "Activités" NOT "Activites")
|
|
132
|
-
- **DE** must use: ä, ö, ü, ß (e.g., "Aktivitäten" NOT "Aktivitaten")
|
|
133
|
-
- **IT** must use: à, è, é, ì, ò, ù (e.g., "Attività" NOT "Attivita")
|
|
134
|
-
|
|
135
|
-
Cross-reference navigation seed data labels with the corresponding frontend `i18n/index.ts` translations for consistency.
|
|
136
|
-
|
|
137
|
-
**BLOCKING** if any `NavigationTranslationSeedEntry` for fr/de/it contains only ASCII characters where diacritical marks are expected in the target language.
|
|
30
|
+
1. **A task can only start when ALL its dependencies are `completed`**
|
|
31
|
+
2. **If a dependency is `failed` or `blocked`, the task becomes `blocked`**
|
|
32
|
+
3. **Cross-category dependencies are implicit** — domain before infrastructure, etc.
|
|
33
|
+
4. **Same-category tasks MAY run in parallel** (batched together)
|
|
34
|
+
5. **PRD `dependencies[]` array overrides implicit ordering** — explicit deps always checked first
|
|
138
35
|
|
|
139
36
|
---
|
|
140
37
|
|
|
141
|
-
##
|
|
142
|
-
|
|
143
|
-
**Folder:** `Infrastructure/Persistence/SqlObjects/Functions/`
|
|
144
|
-
|
|
145
|
-
Rules:
|
|
146
|
-
- `.sql` files with `CREATE OR ALTER` for idempotency
|
|
147
|
-
- `SqlObjectHelper.cs` for embedded resource loading
|
|
148
|
-
|
|
149
|
-
---
|
|
38
|
+
## Batch Grouping Rules
|
|
150
39
|
|
|
151
|
-
|
|
40
|
+
When selecting tasks for a batch:
|
|
152
41
|
|
|
153
|
-
|
|
42
|
+
1. **Group by category** — take the first eligible category with pending tasks
|
|
43
|
+
2. **Max batch size: 5 tasks** — prevents overloading a single apex invocation
|
|
44
|
+
3. **Same category only** — never mix domain + infrastructure in one batch
|
|
45
|
+
4. **Order within batch: by task ID** (ascending) — preserves PRD creation order
|
|
154
46
|
|
|
155
|
-
```
|
|
156
|
-
|
|
47
|
+
```javascript
|
|
48
|
+
const eligible = prd.tasks.filter(/* pending + deps met */);
|
|
49
|
+
const firstCategory = eligible[0].category;
|
|
50
|
+
const batch = eligible.filter(t => t.category === firstCategory).slice(0, 5);
|
|
157
51
|
```
|
|
158
52
|
|
|
159
|
-
**BLOCKING:** Build MUST pass before proceeding to application/api/test/frontend.
|
|
160
|
-
If build fails, fix infrastructure code first.
|
|
161
|
-
|
|
162
|
-
> **NOTE:** If build passes but you suspect assembly issues (e.g., new packages added to Infrastructure
|
|
163
|
-
> that may not be in the meta-package), also run a quick startup check:
|
|
164
|
-
> `dotnet run --project {ApiProject} --urls "http://localhost:5098" &` and verify the process survives 5 seconds.
|
|
165
|
-
> See `references/error-classification.md` for error classification if it crashes.
|
|
166
|
-
|
|
167
53
|
---
|
|
168
54
|
|
|
169
|
-
##
|
|
170
|
-
|
|
171
|
-
**Services:** `Application/Services/{ContextPascal}/{App}/{Module}/`
|
|
172
|
-
**DTOs:** `Application/DTOs/{ContextPascal}/{App}/{Module}/`
|
|
173
|
-
**Validators:** `Application/Validators/{ContextPascal}/{App}/{Module}/`
|
|
174
|
-
**MCP:** `validate_conventions`, `scaffold_extension`
|
|
175
|
-
|
|
176
|
-
Rules:
|
|
177
|
-
- CQRS pattern with MediatR
|
|
178
|
-
- FluentValidation for all commands
|
|
179
|
-
- DTOs separate from domain entities
|
|
180
|
-
- Service interfaces in Application, implementations in Infrastructure
|
|
181
|
-
|
|
182
|
-
**Tenant isolation (BLOCKING — SECURITY CRITICAL):**
|
|
183
|
-
|
|
184
|
-
> **ROOT CAUSE (test-v4-005):** Services were generated WITHOUT TenantId filtering,
|
|
185
|
-
> creating cross-tenant data leakage on ALL 70+ CRUD endpoints.
|
|
186
|
-
> This is an OWASP A01 (Broken Access Control) vulnerability.
|
|
187
|
-
|
|
188
|
-
- ALL queries on tenant entities MUST include `.Where(x => x.TenantId == _currentUser.TenantId)`
|
|
189
|
-
- ALL entity creation MUST pass `_currentUser.TenantId` as first parameter to `Entity.Create(tenantId, ...)`
|
|
190
|
-
- NEVER use `new Entity { }` without `TenantId =` — always prefer factory method `Entity.Create()`
|
|
191
|
-
- NEVER use `Guid.Empty` as a placeholder for userId, tenantId, or any business identifier
|
|
192
|
-
- Service constructor MUST inject `ICurrentUserService _currentUser` to access TenantId
|
|
193
|
-
|
|
194
|
-
**MANDATORY Service Template (use as skeleton for ALL services):**
|
|
195
|
-
|
|
196
|
-
```csharp
|
|
197
|
-
public class {Entity}Service : I{Entity}Service
|
|
198
|
-
{
|
|
199
|
-
private readonly IExtensionsDbContext _db;
|
|
200
|
-
private readonly ICurrentUserService _currentUser;
|
|
201
|
-
|
|
202
|
-
public {Entity}Service(IExtensionsDbContext db, ICurrentUserService currentUser)
|
|
203
|
-
{
|
|
204
|
-
_db = db;
|
|
205
|
-
_currentUser = currentUser;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
public async Task<PaginatedResult<{Entity}Response>> GetAllAsync(/* filters */, CancellationToken ct)
|
|
209
|
-
{
|
|
210
|
-
var tenantId = _currentUser.TenantId;
|
|
211
|
-
var query = _db.{Entities}
|
|
212
|
-
.Where(x => x.TenantId == tenantId) // ← MANDATORY tenant filter
|
|
213
|
-
.AsQueryable();
|
|
214
|
-
// ... apply filters, pagination, projection to Response DTO
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
public async Task<{Entity}Response?> GetByIdAsync(Guid id, CancellationToken ct)
|
|
218
|
-
{
|
|
219
|
-
var tenantId = _currentUser.TenantId;
|
|
220
|
-
var entity = await _db.{Entities}
|
|
221
|
-
.Where(x => x.TenantId == tenantId) // ← MANDATORY tenant filter
|
|
222
|
-
.FirstOrDefaultAsync(x => x.Id == id, ct);
|
|
223
|
-
// ...
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
public async Task<{Entity}Response> CreateAsync(Create{Entity}Dto dto, CancellationToken ct)
|
|
227
|
-
{
|
|
228
|
-
var entity = {Entity}.Create(_currentUser.TenantId, /* dto fields */);
|
|
229
|
-
// ← TenantId as FIRST parameter
|
|
230
|
-
_db.{Entities}.Add(entity);
|
|
231
|
-
await _db.SaveChangesAsync(ct);
|
|
232
|
-
return MapToResponse(entity);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
public async Task<{Entity}Response> UpdateAsync(Guid id, Update{Entity}Dto dto, CancellationToken ct)
|
|
236
|
-
{
|
|
237
|
-
var entity = await _db.{Entities}
|
|
238
|
-
.Where(x => x.TenantId == _currentUser.TenantId) // ← MANDATORY
|
|
239
|
-
.FirstOrDefaultAsync(x => x.Id == id, ct)
|
|
240
|
-
?? throw new NotFoundException(nameof({Entity}), id);
|
|
241
|
-
// ... update fields
|
|
242
|
-
}
|
|
55
|
+
## PRD Category Completeness
|
|
243
56
|
|
|
244
|
-
|
|
245
|
-
{
|
|
246
|
-
var entity = await _db.{Entities}
|
|
247
|
-
.Where(x => x.TenantId == _currentUser.TenantId) // ← MANDATORY
|
|
248
|
-
.FirstOrDefaultAsync(x => x.Id == id, ct)
|
|
249
|
-
?? throw new NotFoundException(nameof({Entity}), id);
|
|
250
|
-
// ...
|
|
251
|
-
}
|
|
57
|
+
A valid PRD MUST contain tasks in these categories:
|
|
252
58
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
**POST-CHECK after writing ANY service:** Grep the file for `TenantId`. If 0 occurrences → FAIL, rewrite with tenant filtering.
|
|
261
|
-
|
|
262
|
-
**Lifecycle-aware services:**
|
|
263
|
-
- Services operating on entities with a `lifeCycle` (status field) MUST validate entity state before mutations
|
|
264
|
-
- Example: `if (entity.Status == EmployeeStatus.Terminated) throw new BusinessException("Cannot update terminated employee")`
|
|
265
|
-
- Guard checks MUST match the `allowedTransitions` from the entity's lifecycle definition
|
|
266
|
-
- Delete/archive operations MUST respect terminal states (`isTerminal: true`)
|
|
267
|
-
|
|
268
|
-
**Validator completeness (BLOCKING):**
|
|
269
|
-
- For EVERY `Create{Entity}Validator`, a matching `Update{Entity}Validator` MUST exist
|
|
270
|
-
- ALL validators MUST be registered in `Application/DependencyInjection.cs`:
|
|
271
|
-
`services.AddValidatorsFromAssemblyContaining<Create{Entity}Validator>()`
|
|
272
|
-
- DependencyInjection.cs MUST NOT be empty or contain only TODO comments
|
|
273
|
-
- After writing validators, VERIFY DI registration exists — if missing, add it immediately
|
|
59
|
+
- `domain` — at least 1 entity task
|
|
60
|
+
- `infrastructure` — at least 1 EF config + migration task
|
|
61
|
+
- `application` — at least 1 service task
|
|
62
|
+
- `api` — at least 1 controller task
|
|
63
|
+
- `seedData` — at least 1 navigation/permission task
|
|
64
|
+
- `frontend` — at least 1 page task
|
|
65
|
+
- `test` — at least 1 test task
|
|
274
66
|
|
|
275
|
-
|
|
276
|
-
```bash
|
|
277
|
-
# Count Create validators vs Update validators
|
|
278
|
-
CREATE_COUNT=$(find . -path "*/Validators/*" -name "Create*Validator.cs" | wc -l)
|
|
279
|
-
UPDATE_COUNT=$(find . -path "*/Validators/*" -name "Update*Validator.cs" | wc -l)
|
|
280
|
-
if [ "$CREATE_COUNT" -ne "$UPDATE_COUNT" ]; then
|
|
281
|
-
echo "VALIDATOR MISMATCH: $CREATE_COUNT Create vs $UPDATE_COUNT Update → MUST be equal"
|
|
282
|
-
# List missing UpdateValidators and CREATE them
|
|
283
|
-
fi
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**Mapper pattern (DRY):**
|
|
287
|
-
- Each service MUST include a `private static {Entity}Response MapToResponse({Entity} entity)` method
|
|
288
|
-
- For complex mappings with related entities, use an extension method in `Application/Mappings/{Module}Mappings.cs`
|
|
289
|
-
- NEVER duplicate mapping logic between GetAll, GetById, Create, Update — always call MapToResponse
|
|
290
|
-
|
|
291
|
-
**FORBIDDEN:**
|
|
292
|
-
- Empty DependencyInjection.cs with `// TODO` placeholder
|
|
293
|
-
- CreateValidator without matching UpdateValidator
|
|
294
|
-
- Validators not registered in DI container
|
|
295
|
-
- Service query without TenantId filter (cross-tenant data leak)
|
|
296
|
-
- `new Entity { }` without TenantId assignment
|
|
297
|
-
- `Guid.Empty` as a business value in services or controllers
|
|
298
|
-
- Entity.Create() without tenantId as first parameter
|
|
299
|
-
- Duplicated mapping logic (entity→response) in multiple methods
|
|
67
|
+
If any category is missing, ralph step-01 injects a guardrail task.
|
|
300
68
|
|
|
301
69
|
---
|
|
302
70
|
|
|
303
|
-
##
|
|
304
|
-
|
|
305
|
-
**Controllers:** `Api/Controllers/{ContextShort}/{App}/{Entity}Controller.cs`
|
|
306
|
-
**MCP:** `scaffold_routes`, `validate_security`
|
|
307
|
-
|
|
308
|
-
**Context-to-folder mapping (`{ContextShort}`):**
|
|
309
|
-
|
|
310
|
-
| NavRoute Prefix | Controller Folder |
|
|
311
|
-
|-----------------|-------------------|
|
|
312
|
-
| `platform.administration` | `Admin` |
|
|
313
|
-
| `platform.support` | `Support` |
|
|
314
|
-
| `business.*` | `Business` |
|
|
315
|
-
| `personal.*` | `User` |
|
|
316
|
-
|
|
317
|
-
**Rules:**
|
|
318
|
-
- `[RequirePermission(Permissions.{Module}.{Action})]` on EVERY endpoint (NOT `[Authorize]`)
|
|
319
|
-
- Swagger XML documentation
|
|
320
|
-
- Consistent route patterns: `api/{context}/{app}/{module}`
|
|
321
|
-
- Return DTOs, never domain entities
|
|
322
|
-
- **ALL GetAll endpoints MUST accept `?search=` query parameter** (enables EntityLookup on frontend)
|
|
323
|
-
- **GetAll returns paginated results:** `PaginatedResult<T>` with items, totalCount, page, pageSize
|
|
324
|
-
|
|
325
|
-
**Controller coverage (BLOCKING):**
|
|
326
|
-
- EVERY entity in the module MUST have a controller with CRUD endpoints (GET list, GET by id, POST, PUT, DELETE)
|
|
327
|
-
- Reference/lookup entities (types, categories, statuses) MUST also have controllers — they are needed for dropdowns and configuration
|
|
328
|
-
- Count: `controllers created >= entities in module`. If fewer → FAIL
|
|
329
|
-
|
|
330
|
-
**Section-level controllers (CONDITIONAL: when `navSections[]` defined in feature.json):**
|
|
331
|
-
- File path: `Api/Controllers/{ContextShort}/{App}/{Section}Controller.cs`
|
|
332
|
-
- NavRoute attribute: `[NavRoute("{context}.{app}.{module}.{section}")]`
|
|
333
|
-
- Permission attribute: `[RequirePermission(Permissions.{Module}.{Section}.{Action})]`
|
|
334
|
-
- Route prefix: `api/{context}/{app}/{module}/{section}`
|
|
335
|
-
- Each section gets its own controller with CRUD endpoints scoped to the section
|
|
336
|
-
|
|
337
|
-
**FORBIDDEN:**
|
|
338
|
-
- `[Authorize]` without specific permission → use `[RequirePermission]`
|
|
339
|
-
- Returning domain entities directly
|
|
340
|
-
- Skipping controllers for reference/lookup entities
|
|
341
|
-
|
|
342
|
-
---
|
|
343
|
-
|
|
344
|
-
## Frontend (MCP-FIRST PROTOCOL)
|
|
345
|
-
|
|
346
|
-
> **CRITICAL:** Frontend code is generated via MCP tools, NOT written from scratch.
|
|
347
|
-
|
|
348
|
-
**Page hierarchy:** `src/pages/{ContextPascal}/{AppPascal}/{Module}/{Page}.tsx`
|
|
349
|
-
**FORBIDDEN:** `src/pages/{Module}/` (flat structure without Context/App)
|
|
350
|
-
|
|
351
|
-
**Execution sequence (IN ORDER):**
|
|
352
|
-
0. **Read back feature.json** (via `prd.source.featurePath`) — The PRD only carries the file list (structural skeleton). The behavioral specs (columnDefs, componentMapping, layout, rowActions, emptyState, tab structure, i18n key-value pairs, dashboard KPIs, state machine transitions) are ONLY in the original feature.json. Without this step, generated code will be generic and miss entity-specific UI details.
|
|
353
|
-
1. `mcp__smartstack__scaffold_api_client` → API client + types + React Query hook
|
|
354
|
-
2. `mcp__smartstack__scaffold_routes` (with `outputFormat: "clientRoutes"`) → route registry + route fragments
|
|
355
|
-
3. **Wire routes to App.tsx (detect pattern: `contextRoutes` array OR JSX `<Route>`):**
|
|
356
|
-
- **Pattern A** (`contextRoutes: ContextRouteExtensions` in App.tsx): add to `contextRoutes.{context}[]` with RELATIVE paths → auto-injected into both standard + tenant trees
|
|
357
|
-
- **Pattern B** (JSX `<Route>` in App.tsx): insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
|
|
358
|
-
4. Create pages using **`/ui-components` skill** — **LOAD the skill step files before creating any page.** This is MANDATORY for entity lists, grids, tables, dashboards, charts. The skill ensures CSS variables, EntityCard, SmartTable, loading/error/empty states, and responsive grid patterns. **Pages MUST use `t()` from `useTranslation()` for ALL visible text from the very first line of code.** Do NOT hardcode English strings with the intent of adding i18n later — the i18n keys are created in step 6 based on what keys the pages reference. Pattern: `t('{moduleLower}:actions.create', 'Create')`.
|
|
359
|
-
5. Create preferences hook: `use{Module}Preferences.ts`
|
|
360
|
-
6. Generate i18n (4 languages: fr, en, it, de) — see I18n section below for detailed rules
|
|
361
|
-
7. `npm run typecheck` MUST pass (BLOCKING)
|
|
362
|
-
|
|
363
|
-
**Components:**
|
|
364
|
-
- Lists: `SmartTable` + `SmartFilter` (NOT HTML `<table>`)
|
|
365
|
-
- Grids: `EntityCard` (NOT custom `<div>` cards)
|
|
366
|
-
- Detail: `EntityDetailCard`, `StatusBadge`, tab layout
|
|
367
|
-
- Forms: `SmartForm` with FluentValidation-backed fields — **MUST be full pages, NEVER modals**
|
|
368
|
-
- FK fields: `EntityLookup` for searchable entity selection (NEVER plain text for Guid FK)
|
|
369
|
-
- Dashboard: `StatCard`, Recharts components
|
|
370
|
-
|
|
371
|
-
**Per-page /ui-components validation (MANDATORY before commit):**
|
|
372
|
-
- After creating EACH page, verify it follows /ui-components patterns:
|
|
373
|
-
1. List pages: uses `DataTable` or `SmartTable` (NOT raw `<table>`)
|
|
374
|
-
2. List pages: uses `EntityCard` for card/grid views (NOT custom `<div>` cards)
|
|
375
|
-
3. All pages: ALL colors use CSS variables (`bg-[var(--xxx)]`) — ZERO hardcoded Tailwind colors
|
|
376
|
-
4. All pages: loading skeleton, error state with retry, empty state are present
|
|
377
|
-
5. All pages: ALL visible text uses `t()` from `useTranslation()`
|
|
378
|
-
6. Delete actions: use `ConfirmDialog` component (NOT `window.confirm()`)
|
|
379
|
-
7. Status displays: use `StatusBadge` with CSS variable colors (NOT inline styled spans)
|
|
380
|
-
- **If ANY check fails: fix the page BEFORE moving to the next page**
|
|
381
|
-
- The POST-CHECKs in step-02 enforce these rules with BLOCKING severity
|
|
382
|
-
|
|
383
|
-
**Form pages (CRITICAL — ZERO modals/popups/drawers):**
|
|
384
|
-
- Create form: `EntityCreatePage.tsx` with route `/{module}/create`
|
|
385
|
-
- Edit form: `EntityEditPage.tsx` with route `/{module}/:id/edit`
|
|
386
|
-
- ALL forms are full pages with their own URL — NEVER Modal/Dialog/Drawer/Popup
|
|
387
|
-
- Back button with `navigate(-1)` on every form page
|
|
388
|
-
- Use React.lazy() + `<Suspense fallback={<PageLoader />}>` for ALL page imports (not just forms — see "Lazy loading" section above)
|
|
389
|
-
- **Form tests (MANDATORY):** Co-located `EntityCreatePage.test.tsx` and `EntityEditPage.test.tsx`
|
|
390
|
-
→ Cover: rendering, validation, submit, pre-fill (edit), navigation, error handling
|
|
391
|
-
|
|
392
|
-
**Layout wrapper mapping:**
|
|
393
|
-
|
|
394
|
-
| Context | Layout | Route path |
|
|
395
|
-
|---------|--------|------------|
|
|
396
|
-
| `platform.*` | `AdminLayout` | `/platform` |
|
|
397
|
-
| `business.*` | `BusinessLayout` | `/business` |
|
|
398
|
-
| `personal.*` | `UserLayout` | `/personal/myspace` |
|
|
399
|
-
|
|
400
|
-
**Route naming convention (CRITICAL — must match backend):**
|
|
401
|
-
- Frontend route paths MUST use **kebab-case** matching the backend API route convention
|
|
402
|
-
- Convention: `HumanResources` (C# namespace) → `human-resources` (URL segment)
|
|
403
|
-
- ALL multi-word route segments MUST contain hyphens: `time-management`, `human-resources`, `leave-balance`
|
|
404
|
-
- FORBIDDEN: `humanresources`, `timemanagement`, `leavebalance` (concatenated without hyphens)
|
|
405
|
-
- Frontend path `/business/human-resources/clients` matches API `api/business/human-resources/clients`
|
|
406
|
-
- Navigation seed data Route values MUST also use kebab-case
|
|
407
|
-
- The POST-CHECK in step-02 will BLOCK if frontend routes don't match backend kebab-case convention
|
|
408
|
-
|
|
409
|
-
**Section-level pages (CONDITIONAL: when `navSections[]` defined in feature.json):**
|
|
410
|
-
- Page file: `src/pages/{ContextPascal}/{AppPascal}/{Module}/{Section}Page.tsx`
|
|
411
|
-
- Route: nested as child of module route in App.tsx (e.g., `/business/human-resources/projects/timesheets`)
|
|
412
|
-
- Add to `contextRoutes.{context}[]` (Pattern A) or as nested `<Route>` (Pattern B)
|
|
413
|
-
- Each section page has its own route and permission check
|
|
414
|
-
|
|
415
|
-
**React Router mapping for sections:**
|
|
416
|
-
- `list` section → already the module's `index: true` route (NO separate `path: 'list'`)
|
|
417
|
-
- `detail` section → already the module's `path: ':id'` route (NO separate `path: 'detail'`)
|
|
418
|
-
- Other sections → `path: '{section-kebab}'` as child of module route
|
|
419
|
-
- FORBIDDEN frontend paths: `path: 'list'`, `path: 'detail'` — these are handled by the module's index and :id routes
|
|
420
|
-
|
|
421
|
-
**CSS:** Variables ONLY → `bg-[var(--bg-card)]`, `text-[var(--text-primary)]`
|
|
422
|
-
|
|
423
|
-
**Form error handling (MANDATORY):**
|
|
424
|
-
- ALL SmartForm components MUST include `onError` callback for API errors
|
|
425
|
-
- Validation errors MUST be displayed inline next to the relevant field
|
|
426
|
-
- API errors (4xx, 5xx) MUST show a user-friendly error notification (toast/banner)
|
|
427
|
-
- Forms MUST preserve user input on error (no data loss on failed submit)
|
|
428
|
-
|
|
429
|
-
**Hook error handling pattern (MANDATORY):**
|
|
430
|
-
- ALL hooks MUST preserve server validation errors from axios responses
|
|
431
|
-
- Pattern: `catch (err) { if (axios.isAxiosError(err)) { return err.response?.data; } throw err; }`
|
|
432
|
-
- NEVER discard `err.response.data` — it contains field-level validation errors from FluentValidation
|
|
433
|
-
- Error messages MUST use i18n: `setError(t('{mod}:errors.saveFailed'))` — NEVER hardcoded English strings
|
|
434
|
-
- Toast/notification messages MUST also use i18n
|
|
435
|
-
|
|
436
|
-
**Service call pattern (MANDATORY — NO custom entity hooks):**
|
|
437
|
-
|
|
438
|
-
> **ROOT CAUSE (test-v4-014):** ralph-loop generated custom hooks (`useEmployees()`, `useProjects()`,
|
|
439
|
-
> `useTimeManagement()`) that wrapped services with `useState`/`useEffect`/`try-catch`.
|
|
440
|
-
> These hooks swallowed 401 errors with generic `catch → setError(string)`, creating a race condition
|
|
441
|
-
> with SmartStack's auth interceptor (`window.location.href = "/login"`).
|
|
442
|
-
> Result: token cleared + redirect + React re-render → cascade of unauthenticated requests.
|
|
443
|
-
|
|
444
|
-
- Pages MUST call API services **directly** in `useCallback` + `useEffect` — NOT through custom wrapper hooks
|
|
445
|
-
- FORBIDDEN: `useEmployees()`, `useProjects()`, `use{Entity}()` — custom hooks that wrap service calls with useState/useEffect/try-catch
|
|
446
|
-
- Only allowed custom hook: `use{Module}Preferences.ts` (preferences only, step 5)
|
|
447
|
-
- Pattern for pages:
|
|
448
|
-
```tsx
|
|
449
|
-
// CORRECT — direct service call in page component
|
|
450
|
-
const [data, setData] = useState<EmployeeResponseDto[]>([]);
|
|
451
|
-
const fetchData = useCallback(async () => {
|
|
452
|
-
setLoading(true);
|
|
453
|
-
try {
|
|
454
|
-
const response = await employeeApi.getAll({ pageSize: 200 });
|
|
455
|
-
setData(response.items); // ← extract .items from PaginatedResult
|
|
456
|
-
} catch { setError(t('employees:errors.loadFailed')); }
|
|
457
|
-
finally { setLoading(false); }
|
|
458
|
-
}, []);
|
|
459
|
-
useEffect(() => { fetchData(); }, [fetchData]);
|
|
460
|
-
```
|
|
461
|
-
- **PaginatedResult<T>**: ALL service `getAll` methods MUST type responses as `PaginatedResult<T>` — extract `.items` in the page
|
|
462
|
-
- FORBIDDEN: `api.get<Employee[]>(url)` — use `api.get<PaginatedResult<EmployeeResponseDto>>(url)` then `.items`
|
|
463
|
-
|
|
464
|
-
**Lazy loading (MANDATORY — ALL pages, not just forms):**
|
|
465
|
-
- ALL page imports in App.tsx MUST use `React.lazy()` + `<Suspense fallback={<PageLoader />}>`
|
|
466
|
-
- FORBIDDEN: `lazy(() => import(...))` without a `<Suspense>` boundary in the rendering tree
|
|
467
|
-
- Pattern for App.tsx:
|
|
468
|
-
```tsx
|
|
469
|
-
import { lazy, Suspense } from 'react';
|
|
470
|
-
const EmployeesListPage = lazy(() => import('./pages/.../EmployeesListPage'));
|
|
471
|
-
// In routes:
|
|
472
|
-
{ path: 'employees', element: <Suspense fallback={<PageLoader />}><EmployeesListPage /></Suspense> }
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
**Dependency verification (BLOCKING):**
|
|
476
|
-
- BEFORE writing any import, verify the package exists in `package.json`
|
|
477
|
-
- If package not present: run `npm install {package}` BEFORE writing the import
|
|
478
|
-
- Common missing packages: @tanstack/react-query, recharts, date-fns, react-router-dom
|
|
479
|
-
- After ALL frontend tasks: run `npm ls --depth=0` to detect any MISSING dependencies
|
|
480
|
-
|
|
481
|
-
**FORBIDDEN patterns (any = FAIL):**
|
|
482
|
-
```
|
|
483
|
-
import axios from 'axios' → use @/services/api/apiClient
|
|
484
|
-
<table>...</table> → use SmartTable
|
|
485
|
-
<div className="bg-blue-600"> → use bg-[var(--color-accent-600)]
|
|
486
|
-
<Route path="/business/app/mod" /> → MUST be nested inside Layout
|
|
487
|
-
Only fr/en translations → MUST have 4 languages
|
|
488
|
-
src/pages/{Module}/ → MUST be src/pages/{Context}/{App}/{Module}/
|
|
489
|
-
Routes generated but NOT added to App.tsx → MUST wire routes to App.tsx after scaffold_routes
|
|
490
|
-
Routes in standard block only → MUST also add to /t/:slug/ tenant block (Pattern B only; Pattern A auto-handles)
|
|
491
|
-
Adding routes to clientRoutes[] instead of contextRoutes.{context}[] → MUST use contextRoutes for business/platform/personal routes
|
|
492
|
-
'00000000-0000-0000-0000-000000000000' → use dynamic ID from auth context or route params
|
|
493
|
-
const api = axios.create(...) → use @/services/api/apiClient (single instance)
|
|
494
|
-
useXxx with raw axios inside hooks → hooks MUST use the shared apiClient from @/services/api
|
|
495
|
-
<input type="text" value={...employeeId} → FK Guid fields MUST use EntityLookup (searchable select)
|
|
496
|
-
placeholder="Enter ID" / "Enter GUID" → EntityLookup provides search-based entity selection
|
|
497
|
-
<Modal>/<Dialog>/<Drawer>/<Popup> for forms → forms are FULL PAGES with own URL routes (/create, /:id/edit)
|
|
498
|
-
useState(showCreateModal/editDialog) → navigate to form page, NEVER toggle modal visibility
|
|
499
|
-
<table>/<thead>/<tbody> → MUST use SmartTable or DataTable component
|
|
500
|
-
bg-blue-600 / bg-red-500 / etc. → MUST use CSS variables: bg-[var(--color-accent-600)]
|
|
501
|
-
text-green-700 / text-gray-500 / etc. → MUST use CSS variables: text-[var(--success-text)]
|
|
502
|
-
window.confirm() / confirm() → MUST use ConfirmDialog component with i18n
|
|
503
|
-
Hardcoded English text in JSX (>Create<) → MUST use t('{mod}:actions.create', 'Create')
|
|
504
|
-
Hardcoded error strings in hooks → MUST use t('{mod}:errors.loadFailed') in all hooks
|
|
505
|
-
/business/humanresources/ → MUST use kebab-case: /business/human-resources/
|
|
506
|
-
useEmployees() / use{Entity}() hooks → pages call services DIRECTLY in useCallback (no hook wrapper)
|
|
507
|
-
api.get<Employee[]>(url) → api.get<PaginatedResult<Employee>>(url) then .items
|
|
508
|
-
lazy(() => import(...)) without Suspense → ALL lazy pages MUST have <Suspense fallback={<PageLoader />}>
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
---
|
|
512
|
-
|
|
513
|
-
## I18n (MANDATORY for frontend)
|
|
514
|
-
|
|
515
|
-
> **CRITICAL:** Every frontend module MUST have 4 translation files. Missing i18n = broken UI (untranslated labels, empty buttons).
|
|
516
|
-
|
|
517
|
-
**File path:** `src/i18n/locales/{lang}/{moduleLower}.json` (4 files: fr, en, it, de)
|
|
518
|
-
|
|
519
|
-
**JSON key structure (identical across all 4 languages):**
|
|
520
|
-
```json
|
|
521
|
-
{
|
|
522
|
-
"actions": { "create": "...", "edit": "...", "delete": "...", "save": "...", "cancel": "...", "search": "...", "filter": "...", "export": "...", "refresh": "..." },
|
|
523
|
-
"labels": { "title": "...", "description": "...", "status": "...", "createdAt": "...", "updatedAt": "..." },
|
|
524
|
-
"columns": { "name": "...", "status": "...", "date": "...", "actions": "..." },
|
|
525
|
-
"form": { "name": "...", "description": "...", "submit": "...", "required": "..." },
|
|
526
|
-
"errors": { "loadFailed": "...", "saveFailed": "...", "deleteFailed": "...", "notFound": "..." },
|
|
527
|
-
"validation": { "required": "...", "minLength": "...", "maxLength": "...", "invalid": "..." },
|
|
528
|
-
"messages": { "created": "...", "updated": "...", "deleted": "...", "confirmDelete": "..." },
|
|
529
|
-
"empty": { "title": "...", "description": "...", "action": "..." }
|
|
530
|
-
}
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
**Usage pattern in TSX:** `t('{moduleLower}:actions.create', 'Create')` — ALWAYS namespace prefix + fallback value.
|
|
534
|
-
|
|
535
|
-
**Rules:**
|
|
536
|
-
- 4 JSON files per module: fr, en, it, de — ALL mandatory (not just fr/en)
|
|
537
|
-
- File structure: `src/i18n/locales/{lang}/{moduleLower}.json` — NEVER inline translations in index.ts
|
|
538
|
-
- Identical key structures across all languages — same keys, translated values
|
|
539
|
-
- All UI labels, validation messages, button text, empty states, error messages
|
|
540
|
-
- Depends on frontend page completion (pages define which keys are needed)
|
|
541
|
-
- Add entity-specific keys under `labels`, `columns`, `form` for each entity field
|
|
542
|
-
- **Hooks MUST also use i18n** — error messages like "Failed to load" MUST use `t('{mod}:errors.loadFailed')`
|
|
543
|
-
- **FORBIDDEN:** Inline translation objects in `i18n/index.ts` — use separate JSON files per language per module
|
|
544
|
-
- Reference `smartstack-frontend.md` for the complete template
|
|
545
|
-
|
|
546
|
-
---
|
|
547
|
-
|
|
548
|
-
## Test (BLOCKING)
|
|
549
|
-
|
|
550
|
-
**MCP:** `scaffold_tests`, `analyze_test_coverage`
|
|
551
|
-
|
|
552
|
-
> **CRITICAL:** Test generation is a MANDATORY category. If the PRD has no test tasks,
|
|
553
|
-
> the category completeness check (step-01 section 4b) will inject a guardrail task.
|
|
554
|
-
> Test projects MUST be created as the FIRST action in this category — before generating any test files.
|
|
555
|
-
|
|
556
|
-
**Execution sequence:**
|
|
557
|
-
|
|
558
|
-
1. **Ensure test projects exist (FIRST — before any test generation):**
|
|
559
|
-
```bash
|
|
560
|
-
# Unit test project
|
|
561
|
-
UNIT_TEST_PROJECT="tests/${PROJECT_NAME}.Tests.Unit"
|
|
562
|
-
if [ ! -d "$UNIT_TEST_PROJECT" ]; then
|
|
563
|
-
dotnet new xunit -n "${PROJECT_NAME}.Tests.Unit" -o "$UNIT_TEST_PROJECT"
|
|
564
|
-
dotnet add "$UNIT_TEST_PROJECT" package Moq
|
|
565
|
-
dotnet add "$UNIT_TEST_PROJECT" package FluentAssertions
|
|
566
|
-
for proj in src/*/*.csproj; do dotnet add "$UNIT_TEST_PROJECT" reference "$proj"; done
|
|
567
|
-
dotnet sln add "$UNIT_TEST_PROJECT/${PROJECT_NAME}.Tests.Unit.csproj"
|
|
568
|
-
fi
|
|
569
|
-
|
|
570
|
-
# Integration test project (SQL Server LocalDB)
|
|
571
|
-
INT_TEST_PROJECT="tests/${PROJECT_NAME}.Tests.Integration"
|
|
572
|
-
if [ ! -d "$INT_TEST_PROJECT" ]; then
|
|
573
|
-
dotnet new xunit -n "${PROJECT_NAME}.Tests.Integration" -o "$INT_TEST_PROJECT"
|
|
574
|
-
dotnet add "$INT_TEST_PROJECT" package FluentAssertions
|
|
575
|
-
dotnet add "$INT_TEST_PROJECT" package Microsoft.AspNetCore.Mvc.Testing
|
|
576
|
-
dotnet add "$INT_TEST_PROJECT" package Microsoft.EntityFrameworkCore.SqlServer
|
|
577
|
-
dotnet add "$INT_TEST_PROJECT" package Microsoft.Data.SqlClient
|
|
578
|
-
dotnet add "$INT_TEST_PROJECT" package Respawn
|
|
579
|
-
for proj in src/*/*.csproj; do dotnet add "$INT_TEST_PROJECT" reference "$proj"; done
|
|
580
|
-
dotnet sln add "$INT_TEST_PROJECT/${PROJECT_NAME}.Tests.Integration.csproj"
|
|
581
|
-
fi
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
2. **Generate test infrastructure** (FIRST, before entity tests):
|
|
585
|
-
- `scaffold_tests` with target="infrastructure" → generates DatabaseFixture, DatabaseCollection, SmartStackTestFactory, TestAuthHandler, TestTenantService, IntegrationTestBase, TestDataSeeder
|
|
586
|
-
- These use **SQL Server LocalDB** (not SQLite) → real migrations, real LINQ→SQL
|
|
587
|
-
|
|
588
|
-
3. **Generate entity tests via MCP** (NOT manually):
|
|
589
|
-
- Domain: `scaffold_tests` with target="entity"
|
|
590
|
-
- Service: `scaffold_tests` with target="service"
|
|
591
|
-
- Controller: `scaffold_tests` with target="controller", testTypes=["integration"]
|
|
592
|
-
- Repository: `scaffold_tests` with target="repository", testTypes=["integration"]
|
|
593
|
-
- Security: `scaffold_tests` with target="controller", testTypes=["security"]
|
|
594
|
-
|
|
595
|
-
4. **Run tests (BLOCKING):**
|
|
596
|
-
```bash
|
|
597
|
-
dotnet build --no-restore
|
|
598
|
-
dotnet test --no-build --verbosity normal
|
|
599
|
-
```
|
|
600
|
-
Integration tests run against **real SQL Server LocalDB** via DatabaseFixture:
|
|
601
|
-
- Migrations are applied automatically (validates migration chain)
|
|
602
|
-
- Queries execute real T-SQL (validates LINQ→SQL translation)
|
|
603
|
-
- Multi-tenant isolation is enforced via global query filters on SQL Server
|
|
604
|
-
- Respawn resets data between tests (~50ms)
|
|
605
|
-
|
|
606
|
-
5. **Fix loop:** If tests fail → analyze → fix code (NOT tests) → rebuild → retest → loop until 100% pass
|
|
607
|
-
|
|
608
|
-
6. **Coverage check:** `analyze_test_coverage` → must be >= 80%
|
|
609
|
-
|
|
610
|
-
**Completion criteria (ALL required):**
|
|
611
|
-
- Unit test project exists
|
|
612
|
-
- Integration test project exists with SQL Server LocalDB infrastructure
|
|
613
|
-
- Tests generated via MCP (scaffold_tests)
|
|
614
|
-
- `dotnet test` exits 0 (all pass — including integration tests on SQL Server)
|
|
615
|
-
- Coverage >= 80%
|
|
616
|
-
- No `[Fact(Skip = "...")]`
|
|
617
|
-
|
|
618
|
-
**Frontend test completeness (BLOCKING):**
|
|
619
|
-
|
|
620
|
-
After all frontend pages are generated, verify test coverage:
|
|
621
|
-
|
|
622
|
-
```bash
|
|
623
|
-
# Count page files vs test files
|
|
624
|
-
PAGE_COUNT=$(find src/pages/ -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null | wc -l)
|
|
625
|
-
TEST_COUNT=$(find src/pages/ -name "*.test.tsx" 2>/dev/null | wc -l)
|
|
626
|
-
|
|
627
|
-
if [ "$PAGE_COUNT" -gt 0 ] && [ "$TEST_COUNT" -eq 0 ]; then
|
|
628
|
-
echo "BLOCKING: Category D — No .test.tsx files found in src/pages/"
|
|
629
|
-
echo "Every module with pages MUST have at least one test file"
|
|
630
|
-
echo "Pages found: $PAGE_COUNT, Tests found: 0"
|
|
631
|
-
exit 1
|
|
632
|
-
fi
|
|
633
|
-
|
|
634
|
-
# Minimum ratio: at least 1 test per 3 pages
|
|
635
|
-
MIN_TESTS=$(( (PAGE_COUNT + 2) / 3 ))
|
|
636
|
-
if [ "$TEST_COUNT" -lt "$MIN_TESTS" ]; then
|
|
637
|
-
echo "WARNING: Low test coverage — $TEST_COUNT tests for $PAGE_COUNT pages (minimum: $MIN_TESTS)"
|
|
638
|
-
fi
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
---
|
|
642
|
-
|
|
643
|
-
## Validation (FINAL — BLOCKING)
|
|
644
|
-
|
|
645
|
-
**Execution sequence:**
|
|
646
|
-
|
|
647
|
-
1. `dotnet clean && dotnet restore && dotnet build` → MUST pass
|
|
648
|
-
|
|
649
|
-
1a. **DB migration validation (BLOCKING — if infrastructure tasks exist):**
|
|
650
|
-
```bash
|
|
651
|
-
INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)
|
|
652
|
-
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
653
|
-
if [ -n "$INFRA_PROJECT" ] && [ -n "$API_PROJECT" ]; then
|
|
654
|
-
# Check no pending model changes
|
|
655
|
-
dotnet ef migrations has-pending-model-changes \
|
|
656
|
-
--project "$INFRA_PROJECT" --startup-project "$API_PROJECT"
|
|
657
|
-
# Apply migrations on fresh SQL Server LocalDB
|
|
658
|
-
DB_NAME="SmartStack_Validation_$(date +%s)"
|
|
659
|
-
CONN_STRING="Server=(localdb)\\MSSQLLocalDB;Database=$DB_NAME;Integrated Security=true;TrustServerCertificate=true;Connect Timeout=120;"
|
|
660
|
-
dotnet ef database update --connection "$CONN_STRING" \
|
|
661
|
-
--project "$INFRA_PROJECT" --startup-project "$API_PROJECT"
|
|
662
|
-
# Cleanup
|
|
663
|
-
sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "IF DB_ID('$DB_NAME') IS NOT NULL BEGIN ALTER DATABASE [$DB_NAME] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [$DB_NAME]; END" 2>/dev/null
|
|
664
|
-
fi
|
|
665
|
-
```
|
|
666
|
-
If FAIL → migration is broken → fix before continuing
|
|
71
|
+
## Section-Level Splitting (>4 Entities)
|
|
667
72
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
671
|
-
if [ -n "$API_PROJECT" ]; then
|
|
672
|
-
dotnet run --project "$API_PROJECT" --urls "http://localhost:5098" > /tmp/ralph-validation-startup.log 2>&1 &
|
|
673
|
-
VAL_PID=$!
|
|
73
|
+
When a module has many entities, a single `/apex -d` call saturates the context window.
|
|
74
|
+
Section-level splitting breaks the module into smaller, manageable phases.
|
|
674
75
|
|
|
675
|
-
|
|
676
|
-
STARTED=false
|
|
677
|
-
for i in $(seq 1 10); do
|
|
678
|
-
if ! kill -0 $VAL_PID 2>/dev/null; then
|
|
679
|
-
# Process crashed
|
|
680
|
-
CRASH_LOG=$(cat /tmp/ralph-validation-startup.log 2>/dev/null)
|
|
681
|
-
echo "VALIDATION FAILED: API startup crash"
|
|
682
|
-
echo "$CRASH_LOG"
|
|
683
|
-
break
|
|
684
|
-
fi
|
|
685
|
-
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5098/health 2>/dev/null)
|
|
686
|
-
if [ "$HTTP_CODE" != "000" ]; then
|
|
687
|
-
STARTED=true
|
|
688
|
-
break
|
|
689
|
-
fi
|
|
690
|
-
sleep 1
|
|
691
|
-
done
|
|
76
|
+
**Activation threshold:** `> 4 domain tasks` AND `> 1 architecture section`
|
|
692
77
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
78
|
+
| Phase | Content | Depends On |
|
|
79
|
+
|-------|---------|------------|
|
|
80
|
+
| Phase 0 | ALL domain + infrastructure + migration + module seed data | — |
|
|
81
|
+
| Phase 1 | Section A: services, controllers, section seed, pages, i18n, tests | Phase 0 |
|
|
82
|
+
| Phase 2 | Section B: idem | Phase 0 (+ Phase 1 if FK cross-section) |
|
|
83
|
+
| Phase N | Section N: idem | Phase 0 (+ earlier phases if FK) |
|
|
84
|
+
| Final | Cross-validation (dotnet build, typecheck, MCP validate) | All phases |
|
|
696
85
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
```
|
|
86
|
+
**Key rules:**
|
|
87
|
+
- Phase 0 creates ALL entity classes + ALL EF configs + ONE migration (avoids ModelSnapshot conflicts)
|
|
88
|
+
- Section phases NEVER create entities or migrations — only services, controllers, pages on top
|
|
89
|
+
- FK cross-section dependencies determine section execution order (topological sort)
|
|
90
|
+
- Orphan entities (not in any section) are included in Phase 0 with their services/controllers
|
|
91
|
+
- Each phase produces a temporary PRD file: `.ralph/prd-{module}-phase0.json`, `.ralph/prd-{module}-section-{code}.json`
|
|
92
|
+
- Temporary PRD files are cleaned up in step-05 (report)
|
|
705
93
|
|
|
706
|
-
|
|
707
|
-
3. `mcp__smartstack__validate_conventions` → 0 errors
|
|
708
|
-
4. Generate validation report in progress.txt
|
|
94
|
+
**Backward compatible:** Modules with `<= 4 domain tasks` or `1 section` → standard execution, zero impact.
|
|
709
95
|
|
|
710
|
-
|
|
711
|
-
- Build exit code 0
|
|
712
|
-
- DB migrations apply on SQL Server LocalDB (no pending model changes)
|
|
713
|
-
- Runtime startup check passes (no assembly errors)
|
|
714
|
-
- Test exit code 0 (unit tests + integration tests on real SQL Server)
|
|
715
|
-
- MCP validation 0 errors
|
|
716
|
-
- Production ready = true
|
|
96
|
+
See `references/section-splitting.md` for full detection, mapping, and execution logic.
|