@atlashub/smartstack-cli 3.14.0 → 3.15.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/dist/index.js +26 -28
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +626 -141
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/efcore/migration.md +15 -0
- package/templates/skills/apex/steps/step-04-validate.md +64 -5
- package/templates/skills/application/references/frontend-verification.md +20 -0
- package/templates/skills/application/steps/step-04-backend.md +17 -1
- package/templates/skills/application/steps/step-05-frontend.md +49 -23
- package/templates/skills/application/templates-seed.md +14 -4
- package/templates/skills/business-analyse/html/ba-interactive.html +165 -0
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +2 -0
- package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +85 -0
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +65 -0
- package/templates/skills/business-analyse/html/src/template.html +13 -0
- package/templates/skills/business-analyse/schemas/application-schema.json +5 -0
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +1 -0
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +90 -0
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +4 -0
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +39 -0
- package/templates/skills/efcore/steps/shared/step-00-init.md +55 -0
- package/templates/skills/ralph-loop/SKILL.md +1 -0
- package/templates/skills/ralph-loop/references/category-rules.md +131 -27
- package/templates/skills/ralph-loop/references/compact-loop.md +61 -3
- package/templates/skills/ralph-loop/references/core-seed-data.md +251 -5
- package/templates/skills/ralph-loop/references/error-classification.md +143 -0
- package/templates/skills/ralph-loop/steps/step-05-report.md +54 -0
- package/templates/skills/review-code/references/smartstack-conventions.md +16 -0
- package/templates/skills/validate-feature/SKILL.md +11 -1
- package/templates/skills/validate-feature/steps/step-00-dependencies.md +121 -0
- package/templates/skills/validate-feature/steps/step-04-api-smoke.md +61 -13
- package/templates/skills/validate-feature/steps/step-05-db-validation.md +250 -0
|
@@ -43,6 +43,17 @@ Rules:
|
|
|
43
43
|
**MCP:** `suggest_migration` → get migration name
|
|
44
44
|
|
|
45
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.
|
|
46
57
|
1. Call `mcp__smartstack__suggest_migration` → get standardized name
|
|
47
58
|
2. `dotnet ef migrations add {Name} --project src/{Infra}.csproj --startup-project src/{Api}.csproj -o Persistence/Migrations`
|
|
48
59
|
3. `dotnet ef database update --project src/{Infra}.csproj --startup-project src/{Api}.csproj`
|
|
@@ -110,6 +121,11 @@ dotnet build --no-restore
|
|
|
110
121
|
**BLOCKING:** Build MUST pass before proceeding to application/api/test/frontend.
|
|
111
122
|
If build fails, fix infrastructure code first.
|
|
112
123
|
|
|
124
|
+
> **NOTE:** If build passes but you suspect assembly issues (e.g., new packages added to Infrastructure
|
|
125
|
+
> that may not be in the meta-package), also run a quick startup check:
|
|
126
|
+
> `dotnet run --project {ApiProject} --urls "http://localhost:5098" &` and verify the process survives 5 seconds.
|
|
127
|
+
> See `references/error-classification.md` for error classification if it crashes.
|
|
128
|
+
|
|
113
129
|
---
|
|
114
130
|
|
|
115
131
|
## Application
|
|
@@ -186,11 +202,12 @@ Rules:
|
|
|
186
202
|
|
|
187
203
|
**Execution sequence (IN ORDER):**
|
|
188
204
|
1. `mcp__smartstack__scaffold_api_client` → API client + types + React Query hook
|
|
189
|
-
2. `mcp__smartstack__scaffold_routes` →
|
|
190
|
-
3.
|
|
191
|
-
4. Create
|
|
192
|
-
5.
|
|
193
|
-
6.
|
|
205
|
+
2. `mcp__smartstack__scaffold_routes` (with `outputFormat: "clientRoutes"`) → route registry + route fragments
|
|
206
|
+
3. **Wire routes to App.tsx** → insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
|
|
207
|
+
4. Create pages using SmartStack components
|
|
208
|
+
5. Create preferences hook: `use{Module}Preferences.ts`
|
|
209
|
+
6. Generate i18n (4 languages: fr, en, it, de)
|
|
210
|
+
7. `npm run typecheck` MUST pass (BLOCKING)
|
|
194
211
|
|
|
195
212
|
**Components:**
|
|
196
213
|
- Lists: `SmartTable` + `SmartFilter` (NOT HTML `<table>`)
|
|
@@ -229,6 +246,8 @@ import axios from 'axios' → use @/services/api/apiClient
|
|
|
229
246
|
<Route path="/business/app/mod" /> → MUST be nested inside Layout
|
|
230
247
|
Only fr/en translations → MUST have 4 languages
|
|
231
248
|
src/pages/{Module}/ → MUST be src/pages/{Context}/{App}/{Module}/
|
|
249
|
+
Routes generated but NOT added to App.tsx → MUST wire routes to App.tsx after scaffold_routes
|
|
250
|
+
Routes in standard block only → MUST also add to /t/:slug/ tenant block
|
|
232
251
|
'00000000-0000-0000-0000-000000000000' → use dynamic ID from auth context or route params
|
|
233
252
|
const api = axios.create(...) → use @/services/api/apiClient (single instance)
|
|
234
253
|
useXxx with raw axios inside hooks → hooks MUST use the shared apiClient from @/services/api
|
|
@@ -252,38 +271,63 @@ useXxx with raw axios inside hooks → hooks MUST use the shared apiCl
|
|
|
252
271
|
|
|
253
272
|
**Execution sequence:**
|
|
254
273
|
|
|
255
|
-
1. **Ensure test
|
|
274
|
+
1. **Ensure test projects exist:**
|
|
256
275
|
```bash
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
dotnet
|
|
261
|
-
dotnet add "$
|
|
262
|
-
|
|
263
|
-
dotnet
|
|
276
|
+
# Unit test project
|
|
277
|
+
UNIT_TEST_PROJECT="tests/${PROJECT_NAME}.Tests.Unit"
|
|
278
|
+
if [ ! -d "$UNIT_TEST_PROJECT" ]; then
|
|
279
|
+
dotnet new xunit -n "${PROJECT_NAME}.Tests.Unit" -o "$UNIT_TEST_PROJECT"
|
|
280
|
+
dotnet add "$UNIT_TEST_PROJECT" package Moq
|
|
281
|
+
dotnet add "$UNIT_TEST_PROJECT" package FluentAssertions
|
|
282
|
+
for proj in src/*/*.csproj; do dotnet add "$UNIT_TEST_PROJECT" reference "$proj"; done
|
|
283
|
+
dotnet sln add "$UNIT_TEST_PROJECT/${PROJECT_NAME}.Tests.Unit.csproj"
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
# Integration test project (SQL Server LocalDB)
|
|
287
|
+
INT_TEST_PROJECT="tests/${PROJECT_NAME}.Tests.Integration"
|
|
288
|
+
if [ ! -d "$INT_TEST_PROJECT" ]; then
|
|
289
|
+
dotnet new xunit -n "${PROJECT_NAME}.Tests.Integration" -o "$INT_TEST_PROJECT"
|
|
290
|
+
dotnet add "$INT_TEST_PROJECT" package FluentAssertions
|
|
291
|
+
dotnet add "$INT_TEST_PROJECT" package Microsoft.AspNetCore.Mvc.Testing
|
|
292
|
+
dotnet add "$INT_TEST_PROJECT" package Microsoft.EntityFrameworkCore.SqlServer
|
|
293
|
+
dotnet add "$INT_TEST_PROJECT" package Microsoft.Data.SqlClient
|
|
294
|
+
dotnet add "$INT_TEST_PROJECT" package Respawn
|
|
295
|
+
for proj in src/*/*.csproj; do dotnet add "$INT_TEST_PROJECT" reference "$proj"; done
|
|
296
|
+
dotnet sln add "$INT_TEST_PROJECT/${PROJECT_NAME}.Tests.Integration.csproj"
|
|
264
297
|
fi
|
|
265
298
|
```
|
|
266
299
|
|
|
267
|
-
2. **Generate
|
|
268
|
-
-
|
|
269
|
-
-
|
|
270
|
-
- Controller: `scaffold_tests` with target_layer="api", test_type="integration"
|
|
271
|
-
- Security: `scaffold_tests` with test_type="security"
|
|
300
|
+
2. **Generate test infrastructure** (FIRST, before entity tests):
|
|
301
|
+
- `scaffold_tests` with target="infrastructure" → generates DatabaseFixture, DatabaseCollection, SmartStackTestFactory, TestAuthHandler, TestTenantService, IntegrationTestBase, TestDataSeeder
|
|
302
|
+
- These use **SQL Server LocalDB** (not SQLite) → real migrations, real LINQ→SQL
|
|
272
303
|
|
|
273
|
-
3. **
|
|
304
|
+
3. **Generate entity tests via MCP** (NOT manually):
|
|
305
|
+
- Domain: `scaffold_tests` with target="entity"
|
|
306
|
+
- Service: `scaffold_tests` with target="service"
|
|
307
|
+
- Controller: `scaffold_tests` with target="controller", testTypes=["integration"]
|
|
308
|
+
- Repository: `scaffold_tests` with target="repository", testTypes=["integration"]
|
|
309
|
+
- Security: `scaffold_tests` with target="controller", testTypes=["security"]
|
|
310
|
+
|
|
311
|
+
4. **Run tests (BLOCKING):**
|
|
274
312
|
```bash
|
|
275
|
-
dotnet build
|
|
276
|
-
dotnet test
|
|
313
|
+
dotnet build --no-restore
|
|
314
|
+
dotnet test --no-build --verbosity normal
|
|
277
315
|
```
|
|
316
|
+
Integration tests run against **real SQL Server LocalDB** via DatabaseFixture:
|
|
317
|
+
- Migrations are applied automatically (validates migration chain)
|
|
318
|
+
- Queries execute real T-SQL (validates LINQ→SQL translation)
|
|
319
|
+
- Multi-tenant isolation is enforced via global query filters on SQL Server
|
|
320
|
+
- Respawn resets data between tests (~50ms)
|
|
278
321
|
|
|
279
|
-
|
|
322
|
+
5. **Fix loop:** If tests fail → analyze → fix code (NOT tests) → rebuild → retest → loop until 100% pass
|
|
280
323
|
|
|
281
|
-
|
|
324
|
+
6. **Coverage check:** `analyze_test_coverage` → must be >= 80%
|
|
282
325
|
|
|
283
326
|
**Completion criteria (ALL required):**
|
|
284
|
-
-
|
|
285
|
-
-
|
|
286
|
-
-
|
|
327
|
+
- Unit test project exists
|
|
328
|
+
- Integration test project exists with SQL Server LocalDB infrastructure
|
|
329
|
+
- Tests generated via MCP (scaffold_tests)
|
|
330
|
+
- `dotnet test` exits 0 (all pass — including integration tests on SQL Server)
|
|
287
331
|
- Coverage >= 80%
|
|
288
332
|
- No `[Fact(Skip = "...")]`
|
|
289
333
|
|
|
@@ -294,12 +338,72 @@ useXxx with raw axios inside hooks → hooks MUST use the shared apiCl
|
|
|
294
338
|
**Execution sequence:**
|
|
295
339
|
|
|
296
340
|
1. `dotnet clean && dotnet restore && dotnet build` → MUST pass
|
|
341
|
+
|
|
342
|
+
1a. **DB migration validation (BLOCKING — if infrastructure tasks exist):**
|
|
343
|
+
```bash
|
|
344
|
+
INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)
|
|
345
|
+
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
346
|
+
if [ -n "$INFRA_PROJECT" ] && [ -n "$API_PROJECT" ]; then
|
|
347
|
+
# Check no pending model changes
|
|
348
|
+
dotnet ef migrations has-pending-model-changes \
|
|
349
|
+
--project "$INFRA_PROJECT" --startup-project "$API_PROJECT"
|
|
350
|
+
# Apply migrations on fresh SQL Server LocalDB
|
|
351
|
+
DB_NAME="SmartStack_Validation_$(date +%s)"
|
|
352
|
+
CONN_STRING="Server=(localdb)\\MSSQLLocalDB;Database=$DB_NAME;Integrated Security=true;TrustServerCertificate=true;Connect Timeout=120;"
|
|
353
|
+
dotnet ef database update --connection "$CONN_STRING" \
|
|
354
|
+
--project "$INFRA_PROJECT" --startup-project "$API_PROJECT"
|
|
355
|
+
# Cleanup
|
|
356
|
+
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
|
|
357
|
+
fi
|
|
358
|
+
```
|
|
359
|
+
If FAIL → migration is broken → fix before continuing
|
|
360
|
+
|
|
361
|
+
1b. **Runtime assembly validation (BLOCKING):**
|
|
362
|
+
```bash
|
|
363
|
+
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
364
|
+
if [ -n "$API_PROJECT" ]; then
|
|
365
|
+
dotnet run --project "$API_PROJECT" --urls "http://localhost:5098" > /tmp/ralph-validation-startup.log 2>&1 &
|
|
366
|
+
VAL_PID=$!
|
|
367
|
+
|
|
368
|
+
# Wait up to 10 seconds
|
|
369
|
+
STARTED=false
|
|
370
|
+
for i in $(seq 1 10); do
|
|
371
|
+
if ! kill -0 $VAL_PID 2>/dev/null; then
|
|
372
|
+
# Process crashed
|
|
373
|
+
CRASH_LOG=$(cat /tmp/ralph-validation-startup.log 2>/dev/null)
|
|
374
|
+
echo "VALIDATION FAILED: API startup crash"
|
|
375
|
+
echo "$CRASH_LOG"
|
|
376
|
+
break
|
|
377
|
+
fi
|
|
378
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5098/health 2>/dev/null)
|
|
379
|
+
if [ "$HTTP_CODE" != "000" ]; then
|
|
380
|
+
STARTED=true
|
|
381
|
+
break
|
|
382
|
+
fi
|
|
383
|
+
sleep 1
|
|
384
|
+
done
|
|
385
|
+
|
|
386
|
+
kill $VAL_PID 2>/dev/null
|
|
387
|
+
wait $VAL_PID 2>/dev/null
|
|
388
|
+
rm -f /tmp/ralph-validation-startup.log
|
|
389
|
+
|
|
390
|
+
if [ "$STARTED" != "true" ]; then
|
|
391
|
+
# Classify error per references/error-classification.md
|
|
392
|
+
# Provide actionable fix command
|
|
393
|
+
# DO NOT proceed to tests — fix the startup issue first
|
|
394
|
+
echo "VALIDATION FAILED: Runtime assembly error detected"
|
|
395
|
+
fi
|
|
396
|
+
fi
|
|
397
|
+
```
|
|
398
|
+
|
|
297
399
|
2. `dotnet test` (full suite) → MUST pass
|
|
298
400
|
3. `mcp__smartstack__validate_conventions` → 0 errors
|
|
299
401
|
4. Generate validation report in progress.txt
|
|
300
402
|
|
|
301
403
|
**Completion criteria:**
|
|
302
404
|
- Build exit code 0
|
|
303
|
-
-
|
|
405
|
+
- DB migrations apply on SQL Server LocalDB (no pending model changes)
|
|
406
|
+
- Runtime startup check passes (no assembly errors)
|
|
407
|
+
- Test exit code 0 (unit tests + integration tests on real SQL Server)
|
|
304
408
|
- MCP validation 0 errors
|
|
305
409
|
- Production ready = true
|
|
@@ -70,7 +70,13 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
70
70
|
2. **BUILD verification (BLOCKING per task):**
|
|
71
71
|
- Backend (domain/infra/app/api): `dotnet build --no-restore` → MUST exit 0
|
|
72
72
|
- Frontend: `npm run typecheck` → MUST exit 0
|
|
73
|
-
3. If build fails →
|
|
73
|
+
3. If build fails → **CLASSIFY ERROR FIRST** (read `references/error-classification.md`):
|
|
74
|
+
- **Category A/B (assembly/package):** Run `dotnet add package {Name}` → `dotnet restore` → rebuild. Do NOT edit source code.
|
|
75
|
+
- **Category C (DI):** Edit DI registration file only → rebuild.
|
|
76
|
+
- **Category D (migration):** Run migration commands → rebuild.
|
|
77
|
+
- **Category E (config):** Edit config file → rebuild.
|
|
78
|
+
- **Category F (code):** Fix source code → rebuild → loop until pass.
|
|
79
|
+
- **If 2+ rebuild attempts fail on same error and Category F was chosen:** Re-classify — likely wrong category.
|
|
74
80
|
4. **ARTIFACT verification (BLOCKING per task):**
|
|
75
81
|
- `files_created` MUST NOT be empty for code-generating tasks
|
|
76
82
|
- Category-specific checks (see below)
|
|
@@ -97,7 +103,7 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
97
103
|
|----------|--------|
|
|
98
104
|
| `infrastructure` with `_migrationMeta` | Migration sequence: `suggest_migration` MCP → `dotnet ef migrations add` → `dotnet ef database update` → `dotnet build` |
|
|
99
105
|
| `infrastructure` with seed data keywords | **MANDATORY:** Read `references/core-seed-data.md` → implement templates → `dotnet build` |
|
|
100
|
-
| `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` → create pages → `npm run typecheck && npm run lint` |
|
|
106
|
+
| `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) → **wire to App.tsx (standard + tenant blocks)** → create pages → `npm run typecheck && npm run lint` |
|
|
101
107
|
| `test` | **Create tests:** `scaffold_tests` MCP → **Run:** `dotnet test --verbosity normal` → **Fix loop:** if fail → fix SOURCE CODE → rebuild → retest → repeat until 100% pass |
|
|
102
108
|
| `validation` | `dotnet clean && dotnet restore && dotnet build` → `dotnet test` (full) → `validate_conventions` MCP |
|
|
103
109
|
|
|
@@ -116,7 +122,27 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
116
122
|
```
|
|
117
123
|
If FAIL → fix source code (NOT tests) → rebuild → retest → loop until 100% pass
|
|
118
124
|
|
|
119
|
-
2bis. **
|
|
125
|
+
2bis. **Runtime assembly check (if api/validation tasks in batch):**
|
|
126
|
+
```bash
|
|
127
|
+
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
128
|
+
if [ -n "$API_PROJECT" ]; then
|
|
129
|
+
dotnet run --project "$API_PROJECT" --urls "http://localhost:5098" > /tmp/ralph-startup-check.log 2>&1 &
|
|
130
|
+
CHECK_PID=$!
|
|
131
|
+
sleep 5
|
|
132
|
+
if ! kill -0 $CHECK_PID 2>/dev/null; then
|
|
133
|
+
CRASH_OUTPUT=$(cat /tmp/ralph-startup-check.log 2>/dev/null)
|
|
134
|
+
echo "RUNTIME ASSEMBLY ERROR DETECTED"
|
|
135
|
+
# Classify per references/error-classification.md
|
|
136
|
+
# If Category A: dotnet add package → rebuild → DO NOT count as iteration failure
|
|
137
|
+
fi
|
|
138
|
+
kill $CHECK_PID 2>/dev/null
|
|
139
|
+
wait $CHECK_PID 2>/dev/null
|
|
140
|
+
rm -f /tmp/ralph-startup-check.log
|
|
141
|
+
fi
|
|
142
|
+
```
|
|
143
|
+
If FAIL → classify error per `references/error-classification.md` → apply fix → rebuild → verify again
|
|
144
|
+
|
|
145
|
+
2ter. **DI completeness (if application tasks in batch):**
|
|
120
146
|
Verify `Application/DependencyInjection.cs` registers all validators and services:
|
|
121
147
|
- Count validators in `Validators/` folder
|
|
122
148
|
- Verify `AddValidatorsFromAssemblyContaining` is present
|
|
@@ -138,6 +164,38 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
138
164
|
4. **MCP validation:**
|
|
139
165
|
`mcp__smartstack__validate_conventions` ONCE for the whole batch
|
|
140
166
|
|
|
167
|
+
5. **DB validation (if infrastructure/migration tasks in batch):**
|
|
168
|
+
```bash
|
|
169
|
+
# Quick check: pending model changes after batch?
|
|
170
|
+
INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)
|
|
171
|
+
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
172
|
+
if [ -n "$INFRA_PROJECT" ] && [ -n "$API_PROJECT" ]; then
|
|
173
|
+
dotnet ef migrations has-pending-model-changes \
|
|
174
|
+
--project "$INFRA_PROJECT" \
|
|
175
|
+
--startup-project "$API_PROJECT"
|
|
176
|
+
if [ $? -ne 0 ]; then
|
|
177
|
+
echo "MIGRATION MISSING: Model has uncommitted changes"
|
|
178
|
+
# Create fix task for missing migration
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# If batch included migration tasks: verify migration applies on real SQL Server
|
|
182
|
+
DB_NAME="SmartStack_Ralph_Check_${iteration}"
|
|
183
|
+
CONN_STRING="Server=(localdb)\\MSSQLLocalDB;Database=$DB_NAME;Integrated Security=true;TrustServerCertificate=true;Connect Timeout=120;"
|
|
184
|
+
dotnet ef database update \
|
|
185
|
+
--connection "$CONN_STRING" \
|
|
186
|
+
--project "$INFRA_PROJECT" \
|
|
187
|
+
--startup-project "$API_PROJECT"
|
|
188
|
+
if [ $? -ne 0 ]; then
|
|
189
|
+
echo "MIGRATION BROKEN: Cannot apply migrations on SQL Server"
|
|
190
|
+
# Fix migration → rebuild → DO NOT commit until this passes
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# Cleanup temp database
|
|
194
|
+
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
|
|
195
|
+
fi
|
|
196
|
+
```
|
|
197
|
+
If FAIL → migration is broken → fix → rebuild → DO NOT commit
|
|
198
|
+
|
|
141
199
|
**Error resolution cycle:**
|
|
142
200
|
1. Read the FULL error output (not just first line)
|
|
143
201
|
2. Identify root cause: file path + line number
|
|
@@ -19,6 +19,8 @@ const coreSeedData = meta.coreSeedData || {};
|
|
|
19
19
|
|
|
20
20
|
// Navigation hierarchy
|
|
21
21
|
const navModules = coreSeedData.navigationModules || coreSeedData.navigation || [];
|
|
22
|
+
const navSections = coreSeedData.navigationSections || [];
|
|
23
|
+
const navResources = coreSeedData.navigationResources || [];
|
|
22
24
|
const permissions = coreSeedData.permissions || [];
|
|
23
25
|
const rolePerms = coreSeedData.rolePermissions || [];
|
|
24
26
|
|
|
@@ -44,6 +46,8 @@ if (!navRoute) {
|
|
|
44
46
|
| `appCode` | `humanresources` | `_seedDataMeta.appCode` |
|
|
45
47
|
| `moduleCode` | `projects` | `task.module` |
|
|
46
48
|
| `navModules[]` | `[{code, label, icon, route, translations}]` | `coreSeedData.navigationModules` |
|
|
49
|
+
| `navSections[]` | `[{code, label, icon, route, parentCode, permission, sort}]` | `coreSeedData.navigationSections` |
|
|
50
|
+
| `navResources[]` | `[{code, type, entity, parentCode, permission}]` | `coreSeedData.navigationResources` |
|
|
47
51
|
| `permissions[]` | `[{path, action, description}]` | `coreSeedData.permissions` |
|
|
48
52
|
| `rolePerms[]` | `[{role, permissions[]}]` | `coreSeedData.rolePermissions` |
|
|
49
53
|
|
|
@@ -187,6 +191,198 @@ public class NavigationTranslationSeedEntry
|
|
|
187
191
|
|
|
188
192
|
---
|
|
189
193
|
|
|
194
|
+
## 2b. Navigation Sections & Resources (in same NavigationSeedData.cs)
|
|
195
|
+
|
|
196
|
+
> **CONDITIONAL:** Only generate if `seedDataCore.navigationSections[]` exists and is non-empty in feature.json.
|
|
197
|
+
> Sections and resources are added as additional methods in the **same** `{ModulePascal}NavigationSeedData.cs` file.
|
|
198
|
+
|
|
199
|
+
### GUID Generation Rules
|
|
200
|
+
|
|
201
|
+
```csharp
|
|
202
|
+
// Section GUID: deterministic from navRoute + section code
|
|
203
|
+
public static readonly Guid {SectionPascal}SectionId =
|
|
204
|
+
GenerateDeterministicGuid("navigation-section-{navRoute}.{sectionCode}");
|
|
205
|
+
// Example: GenerateDeterministicGuid("navigation-section-business.ressourceshumaines.employees.list")
|
|
206
|
+
|
|
207
|
+
// Resource GUID: deterministic from navRoute + section code + resource code
|
|
208
|
+
public static readonly Guid {ResourcePascal}ResourceId =
|
|
209
|
+
GenerateDeterministicGuid("navigation-resource-{navRoute}.{sectionCode}.{resourceCode}");
|
|
210
|
+
// Example: GenerateDeterministicGuid("navigation-resource-business.ressourceshumaines.employees.list.employees-grid")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Section Methods (add to {ModulePascal}NavigationSeedData.cs)
|
|
214
|
+
|
|
215
|
+
```csharp
|
|
216
|
+
// --- Add AFTER GetTranslationEntries() in {ModulePascal}NavigationSeedData.cs ---
|
|
217
|
+
|
|
218
|
+
// Deterministic GUIDs for sections
|
|
219
|
+
public static readonly Guid {Section1Pascal}SectionId =
|
|
220
|
+
GenerateDeterministicGuid("navigation-section-{navRoute}.{section1Code}");
|
|
221
|
+
// Repeat for each section...
|
|
222
|
+
|
|
223
|
+
/// <summary>
|
|
224
|
+
/// Returns navigation section entries for seeding into core.nav_Sections.
|
|
225
|
+
/// </summary>
|
|
226
|
+
public static IEnumerable<NavigationSectionSeedEntry> GetSectionEntries(Guid moduleId)
|
|
227
|
+
{
|
|
228
|
+
return new[]
|
|
229
|
+
{
|
|
230
|
+
new NavigationSectionSeedEntry
|
|
231
|
+
{
|
|
232
|
+
Id = {Section1Pascal}SectionId,
|
|
233
|
+
ModuleId = moduleId,
|
|
234
|
+
Code = "{section1Code}",
|
|
235
|
+
Label = "{section1_label_en}",
|
|
236
|
+
Description = "{section1_desc_en}",
|
|
237
|
+
Icon = "{section1_icon}",
|
|
238
|
+
IconType = IconType.Lucide,
|
|
239
|
+
Route = "/{contextCode}/{appCode}/{moduleCode}/{section1Code}",
|
|
240
|
+
DisplayOrder = {section1_sort},
|
|
241
|
+
IsActive = true
|
|
242
|
+
}
|
|
243
|
+
// Repeat for each section...
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// <summary>
|
|
248
|
+
/// Returns 4-language translations for sections.
|
|
249
|
+
/// </summary>
|
|
250
|
+
public static IEnumerable<NavigationTranslationSeedEntry> GetSectionTranslationEntries()
|
|
251
|
+
{
|
|
252
|
+
var entries = new List<NavigationTranslationSeedEntry>();
|
|
253
|
+
|
|
254
|
+
// Section: {section1Code}
|
|
255
|
+
var sec1Id = {Section1Pascal}SectionId;
|
|
256
|
+
entries.AddRange(new[]
|
|
257
|
+
{
|
|
258
|
+
new NavigationTranslationSeedEntry
|
|
259
|
+
{
|
|
260
|
+
EntityType = NavigationEntityType.Section,
|
|
261
|
+
EntityId = sec1Id,
|
|
262
|
+
LanguageCode = "fr",
|
|
263
|
+
Label = "{section1_label_fr}",
|
|
264
|
+
Description = "{section1_desc_fr}"
|
|
265
|
+
},
|
|
266
|
+
new NavigationTranslationSeedEntry
|
|
267
|
+
{
|
|
268
|
+
EntityType = NavigationEntityType.Section,
|
|
269
|
+
EntityId = sec1Id,
|
|
270
|
+
LanguageCode = "en",
|
|
271
|
+
Label = "{section1_label_en}",
|
|
272
|
+
Description = "{section1_desc_en}"
|
|
273
|
+
},
|
|
274
|
+
new NavigationTranslationSeedEntry
|
|
275
|
+
{
|
|
276
|
+
EntityType = NavigationEntityType.Section,
|
|
277
|
+
EntityId = sec1Id,
|
|
278
|
+
LanguageCode = "it",
|
|
279
|
+
Label = "{section1_label_it}",
|
|
280
|
+
Description = "{section1_desc_it}"
|
|
281
|
+
},
|
|
282
|
+
new NavigationTranslationSeedEntry
|
|
283
|
+
{
|
|
284
|
+
EntityType = NavigationEntityType.Section,
|
|
285
|
+
EntityId = sec1Id,
|
|
286
|
+
LanguageCode = "de",
|
|
287
|
+
Label = "{section1_label_de}",
|
|
288
|
+
Description = "{section1_desc_de}"
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
// Repeat for each section...
|
|
292
|
+
|
|
293
|
+
return entries;
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Resource Methods (add to {ModulePascal}NavigationSeedData.cs)
|
|
298
|
+
|
|
299
|
+
> **CONDITIONAL:** Only generate if `seedDataCore.navigationResources[]` exists and is non-empty.
|
|
300
|
+
|
|
301
|
+
```csharp
|
|
302
|
+
// --- Add AFTER GetSectionTranslationEntries() ---
|
|
303
|
+
|
|
304
|
+
// Deterministic GUIDs for resources
|
|
305
|
+
public static readonly Guid {Resource1Pascal}ResourceId =
|
|
306
|
+
GenerateDeterministicGuid("navigation-resource-{navRoute}.{parentSectionCode}.{resource1Code}");
|
|
307
|
+
// Repeat for each resource...
|
|
308
|
+
|
|
309
|
+
/// <summary>
|
|
310
|
+
/// Returns navigation resource entries for a given section.
|
|
311
|
+
/// </summary>
|
|
312
|
+
public static IEnumerable<NavigationResourceSeedEntry> GetResourceEntries(Guid sectionId)
|
|
313
|
+
{
|
|
314
|
+
var entries = new List<NavigationResourceSeedEntry>();
|
|
315
|
+
|
|
316
|
+
// Resources for section: {section1Code}
|
|
317
|
+
if (sectionId == {Section1Pascal}SectionId)
|
|
318
|
+
{
|
|
319
|
+
entries.AddRange(new[]
|
|
320
|
+
{
|
|
321
|
+
new NavigationResourceSeedEntry
|
|
322
|
+
{
|
|
323
|
+
Id = {Resource1Pascal}ResourceId,
|
|
324
|
+
SectionId = sectionId,
|
|
325
|
+
Code = "{resource1Code}",
|
|
326
|
+
Label = "{resource1_label_en}",
|
|
327
|
+
EntityType = "{resource1_entity}",
|
|
328
|
+
Route = "/{contextCode}/{appCode}/{moduleCode}/{section1Code}/{resource1Code}",
|
|
329
|
+
DisplayOrder = 1
|
|
330
|
+
}
|
|
331
|
+
// Repeat for each resource in this section...
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
// Repeat if-block for each section with resources...
|
|
335
|
+
|
|
336
|
+
return entries;
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Additional DTO Classes (add to bottom of file)
|
|
341
|
+
|
|
342
|
+
```csharp
|
|
343
|
+
/// <summary>Seed entry DTO for navigation section.</summary>
|
|
344
|
+
public class NavigationSectionSeedEntry
|
|
345
|
+
{
|
|
346
|
+
public Guid Id { get; init; }
|
|
347
|
+
public Guid ModuleId { get; init; }
|
|
348
|
+
public string Code { get; init; } = null!;
|
|
349
|
+
public string Label { get; init; } = null!;
|
|
350
|
+
public string Description { get; init; } = null!;
|
|
351
|
+
public string Icon { get; init; } = null!;
|
|
352
|
+
public IconType IconType { get; init; }
|
|
353
|
+
public string Route { get; init; } = null!;
|
|
354
|
+
public int DisplayOrder { get; init; }
|
|
355
|
+
public bool IsActive { get; init; }
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// <summary>Seed entry DTO for navigation resource.</summary>
|
|
359
|
+
public class NavigationResourceSeedEntry
|
|
360
|
+
{
|
|
361
|
+
public Guid Id { get; init; }
|
|
362
|
+
public Guid SectionId { get; init; }
|
|
363
|
+
public string Code { get; init; } = null!;
|
|
364
|
+
public string Label { get; init; } = null!;
|
|
365
|
+
public string? EntityType { get; init; }
|
|
366
|
+
public string? Route { get; init; }
|
|
367
|
+
public int DisplayOrder { get; init; }
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Placeholder Values Source
|
|
372
|
+
|
|
373
|
+
| Placeholder | Source in feature.json |
|
|
374
|
+
|-------------|----------------------|
|
|
375
|
+
| `{sectionCode}` | `seedDataCore.navigationSections[].code` |
|
|
376
|
+
| `{section_label_xx}` | `specification.navigation.entries[]` where `level == "section"` → `labels.xx` |
|
|
377
|
+
| `{section_icon}` | `seedDataCore.navigationSections[].icon` |
|
|
378
|
+
| `{section_sort}` | `seedDataCore.navigationSections[].sort` |
|
|
379
|
+
| `{section_route}` | `seedDataCore.navigationSections[].route` |
|
|
380
|
+
| `{resourceCode}` | `seedDataCore.navigationResources[].code` |
|
|
381
|
+
| `{resource_entity}` | `seedDataCore.navigationResources[].entity` |
|
|
382
|
+
| `{parentSectionCode}` | `seedDataCore.navigationResources[].parentCode` |
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
190
386
|
## 3. PermissionsSeedData.cs — MCP-First
|
|
191
387
|
|
|
192
388
|
### Step A: Call MCP (PRIMARY)
|
|
@@ -542,6 +738,43 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
|
542
738
|
}
|
|
543
739
|
// Repeat for each module...
|
|
544
740
|
await ((DbContext)context).SaveChangesAsync(ct);
|
|
741
|
+
|
|
742
|
+
// --- Sections (if seedDataCore.navigationSections exists) ---
|
|
743
|
+
// Module 1 sections
|
|
744
|
+
foreach (var secEntry in {Module1Pascal}NavigationSeedData.GetSectionEntries(mod1.Id))
|
|
745
|
+
{
|
|
746
|
+
var sec = NavigationSection.Create(
|
|
747
|
+
secEntry.ModuleId, secEntry.Code, secEntry.Label,
|
|
748
|
+
secEntry.Description, secEntry.Icon, secEntry.IconType,
|
|
749
|
+
secEntry.Route, secEntry.DisplayOrder);
|
|
750
|
+
context.NavigationSections.Add(sec);
|
|
751
|
+
}
|
|
752
|
+
// Repeat for each module that has sections...
|
|
753
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
754
|
+
|
|
755
|
+
// --- Section translations ---
|
|
756
|
+
foreach (var t in {Module1Pascal}NavigationSeedData.GetSectionTranslationEntries())
|
|
757
|
+
{
|
|
758
|
+
context.NavigationTranslations.Add(
|
|
759
|
+
NavigationTranslation.Create(t.EntityType, t.EntityId, t.LanguageCode, t.Label, t.Description));
|
|
760
|
+
}
|
|
761
|
+
// Repeat for each module that has sections...
|
|
762
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
763
|
+
|
|
764
|
+
// --- Resources (if seedDataCore.navigationResources exists) ---
|
|
765
|
+
// Module 1 resources (resolved per section)
|
|
766
|
+
foreach (var secEntry in {Module1Pascal}NavigationSeedData.GetSectionEntries(mod1.Id))
|
|
767
|
+
{
|
|
768
|
+
foreach (var resEntry in {Module1Pascal}NavigationSeedData.GetResourceEntries(secEntry.Id))
|
|
769
|
+
{
|
|
770
|
+
var res = NavigationResource.Create(
|
|
771
|
+
resEntry.SectionId, resEntry.Code, resEntry.Label,
|
|
772
|
+
resEntry.EntityType, resEntry.Route, resEntry.DisplayOrder);
|
|
773
|
+
context.NavigationResources.Add(res);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
// Repeat for each module that has resources...
|
|
777
|
+
await ((DbContext)context).SaveChangesAsync(ct);
|
|
545
778
|
}
|
|
546
779
|
|
|
547
780
|
public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
|
|
@@ -648,23 +881,33 @@ When processing multiple modules in the same ralph-loop run:
|
|
|
648
881
|
### Module 1 (first): Creates everything from scratch
|
|
649
882
|
|
|
650
883
|
1. `ApplicationRolesSeedData.cs` (application-level, once per app)
|
|
651
|
-
2. `{Module1}NavigationSeedData.cs`
|
|
884
|
+
2. `{Module1}NavigationSeedData.cs` (module + sections + resources + all translations)
|
|
652
885
|
3. `{Module1}PermissionsSeedData.cs`
|
|
653
886
|
4. `{Module1}RolesSeedData.cs`
|
|
654
|
-
5. `{AppPascalName}SeedDataProvider.cs` (new, with 4 methods)
|
|
887
|
+
5. `{AppPascalName}SeedDataProvider.cs` (new, with 4 methods — including section/resource seeding)
|
|
655
888
|
6. DI registration (new)
|
|
656
889
|
|
|
657
890
|
### Module 2+ (subsequent): Append to existing provider
|
|
658
891
|
|
|
659
892
|
1. `ApplicationRolesSeedData.cs` (already exists — skip)
|
|
660
|
-
2. `{Module2}NavigationSeedData.cs` (new file)
|
|
893
|
+
2. `{Module2}NavigationSeedData.cs` (new file — include sections + resources if defined in feature.json)
|
|
661
894
|
3. `{Module2}PermissionsSeedData.cs` (new file)
|
|
662
895
|
4. `{Module2}RolesSeedData.cs` (new file)
|
|
663
|
-
5. `{AppPascalName}SeedDataProvider.cs` (**modify** — add using, add entries in Navigation/Permissions/RolePermissions methods)
|
|
896
|
+
5. `{AppPascalName}SeedDataProvider.cs` (**modify** — add using, add entries in Navigation/Permissions/RolePermissions methods, **including section/resource seeding for the new module**)
|
|
664
897
|
6. DI registration (already exists — skip)
|
|
665
898
|
|
|
666
899
|
**Detection:** Check if `{AppPascalName}SeedDataProvider.cs` exists. If yes, READ it and ADD the new module's entries to the appropriate methods (Navigation, Permissions, RolePermissions). Do NOT modify SeedRolesAsync().
|
|
667
900
|
|
|
901
|
+
### Section/Resource Conditionality
|
|
902
|
+
|
|
903
|
+
Sections and resources are **optional per module**. When processing a module's feature.json:
|
|
904
|
+
|
|
905
|
+
| Condition | Action |
|
|
906
|
+
|-----------|--------|
|
|
907
|
+
| `seedDataCore.navigationSections` absent or empty | Skip `GetSectionEntries()` / `GetSectionTranslationEntries()` / section seeding in provider |
|
|
908
|
+
| `seedDataCore.navigationResources` absent or empty | Skip `GetResourceEntries()` / resource seeding in provider |
|
|
909
|
+
| Both present | Generate all section + resource methods and provider code |
|
|
910
|
+
|
|
668
911
|
---
|
|
669
912
|
|
|
670
913
|
## 8. Verification Checklist (BLOCKING)
|
|
@@ -680,11 +923,14 @@ Before marking the task as completed, verify ALL:
|
|
|
680
923
|
- [ ] MCP `generate_permissions` called (or fallback used)
|
|
681
924
|
- [ ] Role-permission mappings assigned (Admin, Manager, Contributor, Viewer)
|
|
682
925
|
- [ ] `IClientSeedDataProvider` generated with 4 methods (Navigation, Roles, Permissions, RolePermissions)
|
|
683
|
-
- [ ] Execution order: Navigation → Roles → Permissions → RolePermissions
|
|
926
|
+
- [ ] Execution order: Navigation (modules → sections → resources) → Roles → Permissions → RolePermissions
|
|
684
927
|
- [ ] Each Seed method is idempotent (checks existence before inserting)
|
|
685
928
|
- [ ] Factory methods used throughout (NEVER `new Entity()`)
|
|
686
929
|
- [ ] `SaveChangesAsync` called per group (Navigation → Roles → Permissions → RolePermissions)
|
|
687
930
|
- [ ] DI registration added: `services.AddScoped<IClientSeedDataProvider, ...>()`
|
|
931
|
+
- [ ] NavigationSections seeded (if `seedDataCore.navigationSections` present in feature.json)
|
|
932
|
+
- [ ] NavigationResources seeded (if `seedDataCore.navigationResources` present in feature.json)
|
|
933
|
+
- [ ] Section/Resource translations created (4 languages each, EntityType = Section/Resource)
|
|
688
934
|
- [ ] `dotnet build` passes after generation
|
|
689
935
|
|
|
690
936
|
**If ANY check fails, the task status = 'failed'.**
|