@atlashub/smartstack-cli 3.43.0 → 3.45.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/mcp-entry.mjs +201 -22
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/efcore/conflicts.md +22 -2
- package/templates/agents/efcore/migration.md +11 -0
- package/templates/agents/efcore/rebase-snapshot.md +7 -0
- package/templates/agents/efcore/scan.md +24 -2
- package/templates/agents/efcore/squash.md +7 -0
- package/templates/agents/gitflow/init.md +195 -12
- package/templates/skills/apex/SKILL.md +14 -9
- package/templates/skills/apex/_shared.md +3 -0
- package/templates/skills/apex/references/analysis-methods.md +1 -1
- package/templates/skills/apex/references/challenge-questions.md +21 -0
- package/templates/skills/apex/references/core-seed-data.md +59 -104
- package/templates/skills/apex/references/post-checks.md +289 -225
- package/templates/skills/apex/references/smartstack-api.md +33 -35
- package/templates/skills/apex/references/smartstack-frontend.md +99 -3
- package/templates/skills/apex/references/smartstack-layers.md +145 -23
- package/templates/skills/apex/steps/step-00-init.md +2 -2
- package/templates/skills/apex/steps/step-01-analyze.md +1 -0
- package/templates/skills/apex/steps/step-02-plan.md +4 -3
- package/templates/skills/apex/steps/step-03-execute.md +24 -24
- package/templates/skills/apex/steps/step-04-examine.md +64 -24
- package/templates/skills/apex/steps/step-05-deep-review.md +1 -1
- package/templates/skills/apex/steps/step-08-run-tests.md +21 -13
- package/templates/skills/application/references/application-roles-template.md +10 -15
- package/templates/skills/application/references/backend-entity-seeding.md +6 -5
- package/templates/skills/application/references/backend-seeding-and-dto-output.md +1 -1
- package/templates/skills/application/references/nav-fallback-procedure.md +14 -17
- package/templates/skills/application/references/provider-template.md +5 -5
- package/templates/skills/application/references/roles-client-project-handling.md +1 -1
- package/templates/skills/application/references/roles-fallback-procedure.md +10 -15
- package/templates/skills/application/steps/step-01-navigation.md +1 -1
- package/templates/skills/application/steps/step-02-permissions.md +3 -3
- package/templates/skills/application/steps/step-03b-provider.md +1 -0
- package/templates/skills/application/templates-seed.md +41 -47
- package/templates/skills/business-analyse/references/team-orchestration.md +2 -2
- package/templates/skills/controller/steps/step-04-perms.md +1 -1
- package/templates/skills/efcore/references/troubleshooting.md +2 -2
- package/templates/skills/efcore/steps/rebase-snapshot/step-00-init.md +2 -2
- package/templates/skills/efcore/steps/squash/step-00-init.md +2 -2
- package/templates/skills/apex/references/examine-build-validation.md +0 -82
- package/templates/skills/apex/references/execution-frontend-gates.md +0 -177
- package/templates/skills/apex/references/execution-frontend-patterns.md +0 -105
- package/templates/skills/apex/references/execution-layer1-rules.md +0 -96
- package/templates/skills/apex/references/initialization-challenge-flow.md +0 -110
- package/templates/skills/apex/references/planning-layer-mapping.md +0 -151
|
@@ -7,17 +7,15 @@
|
|
|
7
7
|
## GUID GENERATION RULES
|
|
8
8
|
|
|
9
9
|
```csharp
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// ALWAYS use Guid.NewGuid() for ALL seed data IDs
|
|
11
|
+
// This avoids conflicts between different projects/tenants/environments
|
|
12
|
+
// Idempotence is handled by Code-based lookups, NOT by fixed IDs
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// Format: 11111111-1111-1111-1111-{index:D12}
|
|
16
|
-
return Guid.Parse($"11111111-1111-1111-1111-{index:D12}");
|
|
17
|
-
}
|
|
14
|
+
// For HasData() in Configuration files:
|
|
15
|
+
Id = Guid.NewGuid()
|
|
18
16
|
|
|
19
|
-
// For
|
|
20
|
-
|
|
17
|
+
// For SeedData classes:
|
|
18
|
+
public static readonly Guid Sample1Id = Guid.NewGuid();
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
---
|
|
@@ -49,7 +47,7 @@ new {
|
|
|
49
47
|
```csharp
|
|
50
48
|
// Application translations
|
|
51
49
|
translations.Add(new {
|
|
52
|
-
Id =
|
|
50
|
+
Id = Guid.NewGuid(),
|
|
53
51
|
EntityType = NavigationEntityType.Application,
|
|
54
52
|
EntityId = $APP_GUID,
|
|
55
53
|
LanguageCode = "fr",
|
|
@@ -58,7 +56,7 @@ translations.Add(new {
|
|
|
58
56
|
CreatedAt = seedDate
|
|
59
57
|
});
|
|
60
58
|
translations.Add(new {
|
|
61
|
-
Id =
|
|
59
|
+
Id = Guid.NewGuid(),
|
|
62
60
|
EntityType = NavigationEntityType.Application,
|
|
63
61
|
EntityId = $APP_GUID,
|
|
64
62
|
LanguageCode = "en",
|
|
@@ -67,7 +65,7 @@ translations.Add(new {
|
|
|
67
65
|
CreatedAt = seedDate
|
|
68
66
|
});
|
|
69
67
|
translations.Add(new {
|
|
70
|
-
Id =
|
|
68
|
+
Id = Guid.NewGuid(),
|
|
71
69
|
EntityType = NavigationEntityType.Application,
|
|
72
70
|
EntityId = $APP_GUID,
|
|
73
71
|
LanguageCode = "it",
|
|
@@ -76,7 +74,7 @@ translations.Add(new {
|
|
|
76
74
|
CreatedAt = seedDate
|
|
77
75
|
});
|
|
78
76
|
translations.Add(new {
|
|
79
|
-
Id =
|
|
77
|
+
Id = Guid.NewGuid(),
|
|
80
78
|
EntityType = NavigationEntityType.Application,
|
|
81
79
|
EntityId = $APP_GUID,
|
|
82
80
|
LanguageCode = "de",
|
|
@@ -114,7 +112,7 @@ new {
|
|
|
114
112
|
```csharp
|
|
115
113
|
// Module translations
|
|
116
114
|
translations.Add(new {
|
|
117
|
-
Id =
|
|
115
|
+
Id = Guid.NewGuid(),
|
|
118
116
|
EntityType = NavigationEntityType.Module,
|
|
119
117
|
EntityId = $MODULE_GUID,
|
|
120
118
|
LanguageCode = "fr",
|
|
@@ -123,7 +121,7 @@ translations.Add(new {
|
|
|
123
121
|
CreatedAt = seedDate
|
|
124
122
|
});
|
|
125
123
|
translations.Add(new {
|
|
126
|
-
Id =
|
|
124
|
+
Id = Guid.NewGuid(),
|
|
127
125
|
EntityType = NavigationEntityType.Module,
|
|
128
126
|
EntityId = $MODULE_GUID,
|
|
129
127
|
LanguageCode = "en",
|
|
@@ -132,7 +130,7 @@ translations.Add(new {
|
|
|
132
130
|
CreatedAt = seedDate
|
|
133
131
|
});
|
|
134
132
|
translations.Add(new {
|
|
135
|
-
Id =
|
|
133
|
+
Id = Guid.NewGuid(),
|
|
136
134
|
EntityType = NavigationEntityType.Module,
|
|
137
135
|
EntityId = $MODULE_GUID,
|
|
138
136
|
LanguageCode = "it",
|
|
@@ -141,7 +139,7 @@ translations.Add(new {
|
|
|
141
139
|
CreatedAt = seedDate
|
|
142
140
|
});
|
|
143
141
|
translations.Add(new {
|
|
144
|
-
Id =
|
|
142
|
+
Id = Guid.NewGuid(),
|
|
145
143
|
EntityType = NavigationEntityType.Module,
|
|
146
144
|
EntityId = $MODULE_GUID,
|
|
147
145
|
LanguageCode = "de",
|
|
@@ -179,7 +177,7 @@ new {
|
|
|
179
177
|
```csharp
|
|
180
178
|
// Section translations
|
|
181
179
|
translations.Add(new {
|
|
182
|
-
Id =
|
|
180
|
+
Id = Guid.NewGuid(),
|
|
183
181
|
EntityType = NavigationEntityType.Section,
|
|
184
182
|
EntityId = $SECTION_GUID,
|
|
185
183
|
LanguageCode = "fr",
|
|
@@ -188,7 +186,7 @@ translations.Add(new {
|
|
|
188
186
|
CreatedAt = seedDate
|
|
189
187
|
});
|
|
190
188
|
translations.Add(new {
|
|
191
|
-
Id =
|
|
189
|
+
Id = Guid.NewGuid(),
|
|
192
190
|
EntityType = NavigationEntityType.Section,
|
|
193
191
|
EntityId = $SECTION_GUID,
|
|
194
192
|
LanguageCode = "en",
|
|
@@ -197,7 +195,7 @@ translations.Add(new {
|
|
|
197
195
|
CreatedAt = seedDate
|
|
198
196
|
});
|
|
199
197
|
translations.Add(new {
|
|
200
|
-
Id =
|
|
198
|
+
Id = Guid.NewGuid(),
|
|
201
199
|
EntityType = NavigationEntityType.Section,
|
|
202
200
|
EntityId = $SECTION_GUID,
|
|
203
201
|
LanguageCode = "it",
|
|
@@ -206,7 +204,7 @@ translations.Add(new {
|
|
|
206
204
|
CreatedAt = seedDate
|
|
207
205
|
});
|
|
208
206
|
translations.Add(new {
|
|
209
|
-
Id =
|
|
207
|
+
Id = Guid.NewGuid(),
|
|
210
208
|
EntityType = NavigationEntityType.Section,
|
|
211
209
|
EntityId = $SECTION_GUID,
|
|
212
210
|
LanguageCode = "de",
|
|
@@ -510,9 +508,9 @@ new {
|
|
|
510
508
|
|
|
511
509
|
```
|
|
512
510
|
$APP = sales
|
|
513
|
-
$APP_GUID =
|
|
511
|
+
$APP_GUID = (generated via Guid.NewGuid() at seed time)
|
|
514
512
|
$MODULE = products
|
|
515
|
-
$MODULE_GUID =
|
|
513
|
+
$MODULE_GUID = (generated via Guid.NewGuid() at seed time)
|
|
516
514
|
|
|
517
515
|
$LABEL_FR = Produits
|
|
518
516
|
$LABEL_EN = Products
|
|
@@ -570,12 +568,13 @@ namespace SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
|
|
|
570
568
|
public static class {EntityName}SeedData
|
|
571
569
|
{
|
|
572
570
|
// ============================================================
|
|
573
|
-
//
|
|
571
|
+
// RANDOM IDs - generated at class load, unique per deployment
|
|
572
|
+
// Idempotence is handled by AnyAsync() checks, NOT by fixed IDs
|
|
574
573
|
// ============================================================
|
|
575
574
|
|
|
576
|
-
public static readonly Guid Sample1Id = Guid.
|
|
577
|
-
public static readonly Guid Sample2Id = Guid.
|
|
578
|
-
public static readonly Guid Sample3Id = Guid.
|
|
575
|
+
public static readonly Guid Sample1Id = Guid.NewGuid();
|
|
576
|
+
public static readonly Guid Sample2Id = Guid.NewGuid();
|
|
577
|
+
public static readonly Guid Sample3Id = Guid.NewGuid();
|
|
579
578
|
|
|
580
579
|
/// <summary>
|
|
581
580
|
/// Returns all demo {EntityName} entities.
|
|
@@ -641,7 +640,7 @@ private static IEnumerable<{EntityName}SeedItem> GetTenant1{EntityName}s()
|
|
|
641
640
|
{
|
|
642
641
|
new {EntityName}SeedItem
|
|
643
642
|
{
|
|
644
|
-
Id = Guid.
|
|
643
|
+
Id = Guid.NewGuid(),
|
|
645
644
|
TenantId = tenantId,
|
|
646
645
|
CreatedByUserId = userId,
|
|
647
646
|
// ... properties
|
|
@@ -688,8 +687,8 @@ private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
|
|
|
688
687
|
// Map seedItem properties to factory method parameters
|
|
689
688
|
);
|
|
690
689
|
|
|
691
|
-
//
|
|
692
|
-
|
|
690
|
+
// Factory method already generates a random GUID via Guid.NewGuid()
|
|
691
|
+
// No need to override — each seed run creates fresh unique IDs
|
|
693
692
|
|
|
694
693
|
_context.{EntityName}s.Add(entity);
|
|
695
694
|
createdCount++;
|
|
@@ -707,30 +706,25 @@ private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
|
|
|
707
706
|
### Seed Data GUID Generation
|
|
708
707
|
|
|
709
708
|
```csharp
|
|
710
|
-
//
|
|
711
|
-
public static readonly Guid ProductAlphaId = Guid.
|
|
712
|
-
public static readonly Guid ProductBetaId = Guid.
|
|
709
|
+
// ALWAYS use Guid.NewGuid() — avoids conflicts between projects/tenants/environments
|
|
710
|
+
public static readonly Guid ProductAlphaId = Guid.NewGuid();
|
|
711
|
+
public static readonly Guid ProductBetaId = Guid.NewGuid();
|
|
713
712
|
|
|
714
|
-
//
|
|
715
|
-
|
|
716
|
-
{
|
|
717
|
-
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
|
718
|
-
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{EntityName}-{uniqueKey}"));
|
|
719
|
-
return new Guid(hash.Take(16).ToArray());
|
|
720
|
-
}
|
|
713
|
+
// FORBIDDEN: Deterministic GUIDs (SHA256, sequential, hardcoded)
|
|
714
|
+
// These create conflicts when multiple projects seed into the same database
|
|
721
715
|
```
|
|
722
716
|
|
|
723
717
|
### Best Practices
|
|
724
718
|
|
|
725
719
|
| Practice | Description |
|
|
726
720
|
|----------|-------------|
|
|
727
|
-
|
|
|
728
|
-
| Idempotent | Always check `if exists` before creating |
|
|
721
|
+
| Random IDs | ALWAYS use `Guid.NewGuid()` — avoids cross-project/tenant conflicts |
|
|
722
|
+
| Idempotent | Always check `if exists` before creating (by Code, not by ID) |
|
|
729
723
|
| Use factory methods | Call `Entity.Create(...)` not `new Entity()` |
|
|
730
|
-
|
|
|
724
|
+
| FK by Code lookup | Resolve foreign keys by Code at runtime, not by hardcoded GUID |
|
|
731
725
|
| Realistic data | Use plausible names, descriptions, amounts |
|
|
732
726
|
| Cover all states | Include entities in different statuses (active, closed, etc.) |
|
|
733
|
-
| Reference existing seeds |
|
|
727
|
+
| Reference existing seeds | Resolve FKs via Code/Name lookups, not static GUIDs |
|
|
734
728
|
| SaveChanges per batch | Call SaveChanges after each entity group |
|
|
735
729
|
|
|
736
730
|
---
|
|
@@ -895,7 +889,7 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
|
895
889
|
| Factory methods | `NavigationModule.Create(...)`, `NavigationSection.Create(...)`, `NavigationResource.Create(...)`, `Permission.CreateForModule(...)` - NEVER `new Entity()` |
|
|
896
890
|
| Idempotence | Each Seed method checks existence before inserting |
|
|
897
891
|
| SaveChanges per group | Navigation -> save -> Roles -> save -> Permissions -> save -> RolePermissions -> save |
|
|
898
|
-
|
|
|
892
|
+
| Random GUIDs | ALWAYS use `Guid.NewGuid()` — resolve FKs by Code lookup |
|
|
899
893
|
| FK resolution by Code | Parent modules found by `Code`, not hardcoded GUID |
|
|
900
894
|
| Section/Resource conditionality | Only generate if `seedDataCore.navigationSections` / `seedDataCore.navigationResources` exist in feature.json |
|
|
901
895
|
|
|
@@ -905,9 +899,9 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
|
905
899
|
|
|
906
900
|
| Check | Status |
|
|
907
901
|
|-------|--------|
|
|
908
|
-
| ☐
|
|
902
|
+
| ☐ Random GUIDs via Guid.NewGuid() (no deterministic/sequential/fixed) | |
|
|
909
903
|
| ☐ 4 languages for each navigation entity (modules, sections, resources) | |
|
|
910
|
-
| ☐
|
|
904
|
+
| ☐ Translation IDs use Guid.NewGuid() | |
|
|
911
905
|
| ☐ Route aligned with permission path | |
|
|
912
906
|
| ☐ DisplayOrder consistent | |
|
|
913
907
|
| ☐ CRUD permissions created (Level 2 - Module) | |
|
|
@@ -512,8 +512,8 @@ IF layer.modules.length >= 2 AND all modules approved:
|
|
|
512
512
|
3. i18n keys: Do keys follow consistent patterns? No collisions?
|
|
513
513
|
→ {module1}.labels.name vs {module2}.labels.name (OK — namespaced)
|
|
514
514
|
|
|
515
|
-
4. Seed data IDs:
|
|
516
|
-
→
|
|
515
|
+
4. Seed data IDs: All GUIDs use Guid.NewGuid() (no deterministic/fixed values)
|
|
516
|
+
→ Verify no hardcoded or deterministic GUID patterns
|
|
517
517
|
|
|
518
518
|
5. Entity attribute types: Are shared concepts typed consistently?
|
|
519
519
|
→ If both modules define "Status", is it the same enum type?
|
|
@@ -20,7 +20,7 @@ Add permissions to both Permissions.cs (constants) and PermissionConfiguration.c
|
|
|
20
20
|
|
|
21
21
|
See [references/permission-sync-templates.md](../references/permission-sync-templates.md) for the C# templates:
|
|
22
22
|
- **Permissions.cs**: Nested class with 4 constants (View, Create, Update, Delete) + path-to-class mapping
|
|
23
|
-
- **PermissionConfiguration.cs**: 4 HasData Permission entries with
|
|
23
|
+
- **PermissionConfiguration.cs**: 4 HasData Permission entries with Guid.NewGuid()
|
|
24
24
|
|
|
25
25
|
### 3. Generate Migration (Required)
|
|
26
26
|
|
|
@@ -41,7 +41,7 @@ Option 1: HasData() in Configuration (RECOMMENDED)
|
|
|
41
41
|
--------------------------------------------------
|
|
42
42
|
// In UserConfiguration.cs
|
|
43
43
|
builder.HasData(new User {
|
|
44
|
-
Id = Guid.
|
|
44
|
+
Id = Guid.NewGuid(),
|
|
45
45
|
Name = "Admin",
|
|
46
46
|
Email = "admin@example.com"
|
|
47
47
|
});
|
|
@@ -74,7 +74,7 @@ INSERT INTO Users (Id, Name) VALUES (1, 'Admin');
|
|
|
74
74
|
|
|
75
75
|
// AFTER (CORRECT): UserConfiguration.cs
|
|
76
76
|
builder.HasData(new User {
|
|
77
|
-
Id = Guid.
|
|
77
|
+
Id = Guid.NewGuid(),
|
|
78
78
|
Name = "Admin"
|
|
79
79
|
});
|
|
80
80
|
// Then: dotnet ef migrations add SeedData
|
|
@@ -80,10 +80,10 @@ PARENT_MIGRATIONS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- "$MIGRAT
|
|
|
80
80
|
# Get local migrations
|
|
81
81
|
LOCAL_MIGRATIONS=$(find "$MIGRATIONS_DIR" -name "*.cs" 2>/dev/null | grep -v "Designer\|Snapshot" | xargs -I{} basename {} 2>/dev/null || echo "")
|
|
82
82
|
|
|
83
|
-
# Find branch-only migrations
|
|
83
|
+
# Find branch-only migrations (exact match, not substring)
|
|
84
84
|
BRANCH_MIGRATIONS=()
|
|
85
85
|
for mig in $LOCAL_MIGRATIONS; do
|
|
86
|
-
if ! echo "$PARENT_MIGRATIONS" | grep -
|
|
86
|
+
if ! echo "$PARENT_MIGRATIONS" | grep -qx "$(basename "$mig")"; then
|
|
87
87
|
BRANCH_MIGRATIONS+=("$mig")
|
|
88
88
|
fi
|
|
89
89
|
done
|
|
@@ -38,10 +38,10 @@ BASE_MIGRATIONS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- "$MIGRATIO
|
|
|
38
38
|
# Get local migrations
|
|
39
39
|
LOCAL_MIGRATIONS=$(find "$MIGRATIONS_DIR" -name "*.cs" 2>/dev/null | grep -v "Designer\|Snapshot" | xargs -I{} basename {} 2>/dev/null || echo "")
|
|
40
40
|
|
|
41
|
-
# Find migrations unique to this branch
|
|
41
|
+
# Find migrations unique to this branch (exact match, not substring)
|
|
42
42
|
BRANCH_ONLY_MIGRATIONS=()
|
|
43
43
|
for mig in $LOCAL_MIGRATIONS; do
|
|
44
|
-
if ! echo "$BASE_MIGRATIONS" | grep -
|
|
44
|
+
if ! echo "$BASE_MIGRATIONS" | grep -qx "$(basename "$mig")"; then
|
|
45
45
|
BRANCH_ONLY_MIGRATIONS+=("$mig")
|
|
46
46
|
fi
|
|
47
47
|
done
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# Examine Build & Migration Validation
|
|
2
|
-
|
|
3
|
-
> **Loaded by:** step-04-examine.md (sections 4-5)
|
|
4
|
-
> **Purpose:** Build verification, migration validation, database testing procedures.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Build Verification
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
# Backend
|
|
12
|
-
dotnet clean && dotnet restore && dotnet build
|
|
13
|
-
# Note: WSL bin\Debug cleanup handled by PostToolUse hook (wsl-dotnet-cleanup.sh)
|
|
14
|
-
|
|
15
|
-
# Frontend (if applicable)
|
|
16
|
-
npm run typecheck
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
**BLOCKING:** Both must pass. If failure, classify error per `references/error-classification.md`:
|
|
20
|
-
- Category A (missing package) → `dotnet add package` → rebuild
|
|
21
|
-
- Category B (assembly conflict) → resolve version → rebuild
|
|
22
|
-
- Category C (DI missing) → fix DI registration → rebuild
|
|
23
|
-
- Category D (migration broken) → fix migration → rebuild
|
|
24
|
-
- Category E (config) → fix config → rebuild
|
|
25
|
-
- Category F (source code) → fix code → rebuild
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Migration Validation (if needs_migration)
|
|
30
|
-
|
|
31
|
-
### Pending Model Changes Check
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)
|
|
35
|
-
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
36
|
-
|
|
37
|
-
dotnet ef migrations has-pending-model-changes \
|
|
38
|
-
--project "$INFRA_PROJECT" \
|
|
39
|
-
--startup-project "$API_PROJECT"
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**BLOCKING** if pending changes detected → migration is missing.
|
|
43
|
-
|
|
44
|
-
### Migration Application Test (SQL Server LocalDB)
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
DB_NAME="SmartStack_Apex_Examine_$(date +%s)"
|
|
48
|
-
CONN_STRING="Server=(localdb)\\MSSQLLocalDB;Database=$DB_NAME;Integrated Security=true;TrustServerCertificate=true;Connect Timeout=120;"
|
|
49
|
-
|
|
50
|
-
dotnet ef database update \
|
|
51
|
-
--connection "$CONN_STRING" \
|
|
52
|
-
--project "$INFRA_PROJECT" \
|
|
53
|
-
--startup-project "$API_PROJECT"
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**BLOCKING** if migration fails on SQL Server. Common issues:
|
|
57
|
-
- SQLite-only syntax in migrations (fix: regenerate migration)
|
|
58
|
-
- Column type mismatches (fix: update EF configuration)
|
|
59
|
-
- Missing foreign key targets (fix: reorder migrations)
|
|
60
|
-
|
|
61
|
-
### Integration Tests on Real SQL Server
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
# Integration tests use DatabaseFixture → real SQL Server LocalDB
|
|
65
|
-
# This validates: LINQ→SQL, multi-tenant isolation, soft delete, EF configs
|
|
66
|
-
INT_TEST_PROJECT=$(ls tests/*Tests.Integration*/*.csproj 2>/dev/null | head -1)
|
|
67
|
-
if [ -n "$INT_TEST_PROJECT" ]; then
|
|
68
|
-
dotnet test "$INT_TEST_PROJECT" --no-build --verbosity normal
|
|
69
|
-
fi
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Tests running against SQL Server catch issues that SQLite misses:
|
|
73
|
-
- Case sensitivity in string comparisons
|
|
74
|
-
- Date/time function differences
|
|
75
|
-
- IDENTITY vs AUTOINCREMENT behavior
|
|
76
|
-
- Global query filter translation to T-SQL
|
|
77
|
-
|
|
78
|
-
### Cleanup
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
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
|
|
82
|
-
```
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
# Frontend Compliance Gate — 5 Mandatory Checks
|
|
2
|
-
|
|
3
|
-
> **Loaded by:** step-03-execute.md (FRONTEND COMPLIANCE GATE section)
|
|
4
|
-
> **Condition:** MANDATORY before any frontend commit
|
|
5
|
-
> **Purpose:** Automated checks that catch the most common failures in generated code.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Gate 1: CSS Variables (Theme System)
|
|
10
|
-
|
|
11
|
-
Check for hardcoded Tailwind colors — MUST use CSS variables.
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
ALL_PAGES=$(find src/pages/ src/components/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\.")
|
|
15
|
-
if [ -n "$ALL_PAGES" ]; then
|
|
16
|
-
HARDCODED=$(grep -Pn '(bg|text|border)-(?!\[)(red|blue|green|gray|white|black|slate|zinc|neutral|stone)-' $ALL_PAGES 2>/dev/null)
|
|
17
|
-
if [ -n "$HARDCODED" ]; then
|
|
18
|
-
echo "FAIL: Hardcoded Tailwind colors found — must use CSS variables"
|
|
19
|
-
echo "$HARDCODED"
|
|
20
|
-
else
|
|
21
|
-
echo "PASS: CSS variables"
|
|
22
|
-
fi
|
|
23
|
-
fi
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
**If hardcoded colors found, replace BEFORE committing:**
|
|
27
|
-
|
|
28
|
-
| Old | New |
|
|
29
|
-
|-----|-----|
|
|
30
|
-
| `bg-white` | `bg-[var(--bg-card)]` |
|
|
31
|
-
| `bg-gray-50` | `bg-[var(--bg-primary)]` |
|
|
32
|
-
| `text-gray-900` | `text-[var(--text-primary)]` |
|
|
33
|
-
| `text-gray-500/600` | `text-[var(--text-secondary)]` |
|
|
34
|
-
| `border-gray-200` | `border-[var(--border-color)]` |
|
|
35
|
-
| `bg-blue-600` / `text-blue-600` | `bg-[var(--color-accent-500)]` / `text-[var(--color-accent-500)]` |
|
|
36
|
-
| `hover:bg-blue-700` | `hover:bg-[var(--color-accent-600)]` |
|
|
37
|
-
| `text-red-500` | `text-[var(--error-text)]` |
|
|
38
|
-
| `bg-green-500` | `bg-[var(--success-bg)]` |
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Gate 2: Forms as Pages (ZERO Modals/Drawers/Slide-overs)
|
|
43
|
-
|
|
44
|
-
Check for modal/dialog/drawer/slide-over imports and inline form patterns — FORBIDDEN.
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null)
|
|
48
|
-
if [ -n "$PAGE_FILES" ]; then
|
|
49
|
-
FAIL=false
|
|
50
|
-
|
|
51
|
-
# 2a. Component imports
|
|
52
|
-
MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet|SlideOver|Overlay)" $PAGE_FILES 2>/dev/null)
|
|
53
|
-
if [ -n "$MODAL_IMPORTS" ]; then
|
|
54
|
-
echo "FAIL: Modal/Dialog/Drawer component imports — forms MUST be full pages"
|
|
55
|
-
echo "$MODAL_IMPORTS"
|
|
56
|
-
FAIL=true
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
# 2b. State variables for inline forms (catches drawers/slide-overs without imports)
|
|
60
|
-
MODAL_STATE=$(grep -Pn "useState.*(?:isOpen|showModal|showDialog|showCreate|showEdit|showForm|isCreating|isEditing|showDrawer|showPanel|showSlideOver|selectedEntity|editingEntity)" $PAGE_FILES 2>/dev/null)
|
|
61
|
-
if [ -n "$MODAL_STATE" ]; then
|
|
62
|
-
echo "FAIL: Inline form state detected — forms MUST be separate page components with own routes"
|
|
63
|
-
echo "$MODAL_STATE"
|
|
64
|
-
FAIL=true
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
if [ "$FAIL" = true ]; then
|
|
68
|
-
echo "Fix: Create EntityCreatePage.tsx (route: /create) and EntityEditPage.tsx (route: /:id/edit)"
|
|
69
|
-
echo "See smartstack-frontend.md section 3b"
|
|
70
|
-
else
|
|
71
|
-
echo "PASS: No modals/drawers"
|
|
72
|
-
fi
|
|
73
|
-
fi
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**If modals/drawers found:** Replace with separate `EntityCreatePage.tsx` (route: `/{module}/create`) and `EntityEditPage.tsx` (route: `/{module}/:id/edit`). See `smartstack-frontend.md` section 3b.
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## Gate 3: I18n File Structure
|
|
81
|
-
|
|
82
|
-
Verify translation files exist as separate JSON per language.
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
if [ ! -d "src/i18n/locales" ]; then
|
|
86
|
-
echo "FAIL: Missing src/i18n/locales/ directory — create it with 4 languages"
|
|
87
|
-
else
|
|
88
|
-
for LANG in fr en it de; do
|
|
89
|
-
JSON_FILES=$(find "src/i18n/locales/$LANG" -name "*.json" 2>/dev/null | wc -l)
|
|
90
|
-
if [ "$JSON_FILES" -eq 0 ]; then
|
|
91
|
-
echo "FAIL: No JSON files in src/i18n/locales/$LANG/"
|
|
92
|
-
else
|
|
93
|
-
echo "PASS: $LANG ($JSON_FILES files)"
|
|
94
|
-
fi
|
|
95
|
-
done
|
|
96
|
-
fi
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**If i18n structure wrong:** Create `src/i18n/locales/{fr,en,it,de}/{module}.json` following the template in `smartstack-frontend.md` section 2. NEVER embed translations in a single `.ts` file.
|
|
100
|
-
|
|
101
|
-
**Correct structure:**
|
|
102
|
-
```
|
|
103
|
-
src/i18n/locales/
|
|
104
|
-
├── fr/{module}.json ← French (primary)
|
|
105
|
-
├── en/{module}.json ← English
|
|
106
|
-
├── it/{module}.json ← Italian
|
|
107
|
-
└── de/{module}.json ← German
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Each file MUST contain: `title`, `description`, `actions`, `labels`, `columns`, `form`, `errors`, `validation`, `messages`, `empty`.
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Gate 4: Lazy Loading
|
|
115
|
-
|
|
116
|
-
Check for static page imports in route/App files.
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
120
|
-
ROUTE_FILES=$(find src/routes/ -name "*.tsx" -o -name "*.ts" 2>/dev/null)
|
|
121
|
-
if [ -n "$APP_TSX" ]; then
|
|
122
|
-
STATIC_IMPORTS=$(grep -Pn "^import .+ from '@/pages/" "$APP_TSX" $ROUTE_FILES 2>/dev/null)
|
|
123
|
-
if [ -n "$STATIC_IMPORTS" ]; then
|
|
124
|
-
echo "FAIL: Static page imports in App.tsx/routes — MUST use React.lazy()"
|
|
125
|
-
echo "$STATIC_IMPORTS"
|
|
126
|
-
else
|
|
127
|
-
echo "PASS: Lazy loading"
|
|
128
|
-
fi
|
|
129
|
-
fi
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## Gate 5: useTranslation in Pages
|
|
135
|
-
|
|
136
|
-
Verify pages use i18n.
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v "\.test\." | grep -v node_modules)
|
|
140
|
-
if [ -n "$PAGE_FILES" ]; then
|
|
141
|
-
TOTAL=$(echo "$PAGE_FILES" | wc -l)
|
|
142
|
-
WITH_I18N=$(grep -l "useTranslation" $PAGE_FILES 2>/dev/null | wc -l)
|
|
143
|
-
if [ "$WITH_I18N" -eq 0 ]; then
|
|
144
|
-
echo "FAIL: No pages use useTranslation — all text must be translated"
|
|
145
|
-
else
|
|
146
|
-
echo "PASS: $WITH_I18N/$TOTAL pages use useTranslation"
|
|
147
|
-
fi
|
|
148
|
-
fi
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Explicit I18n File Creation
|
|
154
|
-
|
|
155
|
-
When creating i18n files, generate EXACTLY this structure:
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
src/i18n/locales/
|
|
159
|
-
├── fr/{module}.json ← French (primary)
|
|
160
|
-
├── en/{module}.json ← English
|
|
161
|
-
├── it/{module}.json ← Italian
|
|
162
|
-
└── de/{module}.json ← German
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
Each file MUST contain these keys: `title`, `description`, `actions`, `labels`, `columns`, `form`, `errors`, `validation`, `messages`, `empty`. See `smartstack-frontend.md` section 2 for the complete JSON template.
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## ALL 5 Gates MUST PASS
|
|
170
|
-
|
|
171
|
-
**Before creating the frontend commit:**
|
|
172
|
-
1. Do NOT commit frontend changes until ALL checks pass
|
|
173
|
-
2. If ANY gate fails, fix the issues first
|
|
174
|
-
3. When delegating to `/ui-components` skill, include explicit instructions:
|
|
175
|
-
- "CSS: Use CSS variables ONLY — `bg-[var(--bg-card)]`, `text-[var(--text-primary)]`. NEVER use hardcoded Tailwind colors."
|
|
176
|
-
- "Forms: Create/Edit forms are FULL PAGES with own routes (e.g., `/create`, `/:id/edit`). NEVER use modals/dialogs."
|
|
177
|
-
- "I18n: ALL text must use `t('namespace:key', 'Fallback')`. Generate JSON files in `src/i18n/locales/`."
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# Frontend Patterns — Economy Mode Guidelines
|
|
2
|
-
|
|
3
|
-
> **Loaded by:** step-03-execute.md (Layer 1 section: economy_mode frontend tasks)
|
|
4
|
-
> **Purpose:** Quick reference for frontend creation in sequential (economy) mode.
|
|
5
|
-
> **Detailed patterns:** See `smartstack-frontend.md` (referenced from smartstack-layers.md)
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Frontend Tasks — Sequential Execution (economy_mode)
|
|
10
|
-
|
|
11
|
-
For each frontend task in the plan (Layer 1):
|
|
12
|
-
|
|
13
|
-
1. **API Client:** `MCP scaffold_api_client` → API client + types + React Query hook
|
|
14
|
-
2. **Routes:** `MCP scaffold_routes` with `outputFormat: 'clientRoutes'` for lazy imports
|
|
15
|
-
3. **Pages:** **INVOKE `/ui-components` skill** (read SKILL.md + ALL patterns) — MANDATORY for ALL page types
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Required Page Types Per Module
|
|
20
|
-
|
|
21
|
-
Create ALL 4 page types per module:
|
|
22
|
-
- `ListPage.tsx` — entity list with SmartTable/EntityCard
|
|
23
|
-
- `DetailPage.tsx` — entity detail view
|
|
24
|
-
- `EntityCreatePage.tsx` (route: `/create`) — FULL PAGE form, NEVER modal
|
|
25
|
-
- `EntityEditPage.tsx` (route: `/:id/edit`) — FULL PAGE form, NEVER modal
|
|
26
|
-
|
|
27
|
-
**Wire ALL routes in App.tsx:** `index` (ListPage), `:id` (DetailPage), `create` (CreatePage), `:id/edit` (EditPage)
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Foreign Key (FK) Fields — CRITICAL
|
|
32
|
-
|
|
33
|
-
Any Guid FK property (e.g., EmployeeId, DepartmentId) MUST use `EntityLookup` component:
|
|
34
|
-
- **NEVER** a `<select>` dropdown
|
|
35
|
-
- **NEVER** a `<input type="text">`
|
|
36
|
-
- **A `<select>` loaded from API state is NOT a substitute for EntityLookup**
|
|
37
|
-
|
|
38
|
-
See `smartstack-frontend.md` section 6 for the full EntityLookup pattern. Backend GetAll endpoints MUST support `?search=` parameter (enables EntityLookup on frontend).
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Forms: ZERO Modals/Popups/Drawers/Slide-overs
|
|
43
|
-
|
|
44
|
-
**ALL forms are full pages with their own URL:**
|
|
45
|
-
- Create forms: route `/{module}/create`
|
|
46
|
-
- Edit forms: route `/{module}/:id/edit`
|
|
47
|
-
- NEVER embed forms as drawers, panels, or slide-overs
|
|
48
|
-
- Back button with `navigate(-1)` on every form page
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Detail Pages: Tab Behavior (CRITICAL)
|
|
53
|
-
|
|
54
|
-
**Tabs MUST switch content LOCALLY via `setActiveTab()` — NEVER `navigate()` to another page.**
|
|
55
|
-
|
|
56
|
-
Sub-resource data (e.g., employee's leaves) loads inline via API call filtered by parent entity ID. See `smartstack-frontend.md` section 3 "Tab Behavior Rules".
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## Tests (MANDATORY)
|
|
61
|
-
|
|
62
|
-
Generate form tests as co-located files:
|
|
63
|
-
- `EntityCreatePage.test.tsx` (next to CreatePage.tsx)
|
|
64
|
-
- `EntityEditPage.test.tsx` (next to EditPage.tsx)
|
|
65
|
-
|
|
66
|
-
Cover: rendering, validation, submit, pre-fill, navigation, errors. See `smartstack-frontend.md` section 8 for test templates.
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Section-Level Routes (if sections exist)
|
|
71
|
-
|
|
72
|
-
**SECTION PERMISSIONS:** After calling `MCP generate_permissions` for the module navRoute (2 segments: `{app}.{module}`), also call it for EACH section navRoute (3 segments: `{app}.{module}.{section}`)
|
|
73
|
-
|
|
74
|
-
**SECTION ROUTES:** After generating module routes, add section child routes to the module's `children` array. Wire `PermissionGuard` for section routes with section-level permissions.
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## Sub-Resource Handling
|
|
79
|
-
|
|
80
|
-
If a section controller has sub-resource endpoints (e.g., `[HttpGet("types")]` for LeaveTypes inside LeavesController), you MUST EITHER:
|
|
81
|
-
1. Create dedicated frontend pages for the sub-resource (ListPage, CreatePage, EditPage) with routes wired in App.tsx, OR
|
|
82
|
-
2. NOT include any `navigate()` button that links to those sub-resource pages
|
|
83
|
-
|
|
84
|
-
**Prefer separate controllers** with `[NavRoute(..., Suffix = "types")]` — see `smartstack-api.md` Sub-Resource Pattern. A dead link (navigate to a route with no page) is a BLOCKING issue (POST-CHECK 42).
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## I18n (Translations)
|
|
89
|
-
|
|
90
|
-
Generate i18n JSON files for all 4 languages (fr, en, it, de):
|
|
91
|
-
- Location: `src/i18n/locales/{lang}/{module}.json`
|
|
92
|
-
- Keys: `actions`, `labels`, `errors`, `validation`, `columns`, `form`, `messages`, `empty`
|
|
93
|
-
- ALL `t()` calls MUST use namespace prefix + fallback: `t('ns:key', 'Default text')`
|
|
94
|
-
|
|
95
|
-
**CRITICAL:** After creating i18n JSON files, register EACH new namespace in the i18n config file (config.ts/index.ts/i18n.ts). Unregistered namespaces → `useTranslation(['module'])` returns empty strings at runtime. POST-CHECK 45 validates this.
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Folder Structure
|
|
100
|
-
|
|
101
|
-
MUST use: `src/pages/{App}/{Module}/` hierarchy (NOT flat).
|
|
102
|
-
|
|
103
|
-
All pages must follow: **hooks → useEffect(load) → loading state → error state → content**
|
|
104
|
-
|
|
105
|
-
Use **CSS variables ONLY** for styling — hardcoded Tailwind colors are BLOCKING (POST-CHECK 13).
|