@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,28 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: step-02-execute
|
|
3
|
-
description:
|
|
3
|
+
description: Delegate module execution to /apex
|
|
4
4
|
next_step: steps/step-03-commit.md
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Step 2:
|
|
7
|
+
# Step 2: Delegate to /apex
|
|
8
8
|
|
|
9
9
|
> **CONTEXT OPTIMIZATION:** Read ONCE (first iteration). Subsequent iterations use compact-loop.md.
|
|
10
10
|
|
|
11
11
|
## YOUR TASK:
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Delegate the current module's tasks to `/apex -d` for code generation. Ralph handles orchestration only.
|
|
14
14
|
|
|
15
|
-
**ULTRA THINK about the
|
|
15
|
+
**ULTRA THINK about the delegation context.**
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
## EXECUTION RULES:
|
|
20
20
|
|
|
21
|
-
1. **
|
|
22
|
-
2. **
|
|
23
|
-
3. **TRACK
|
|
24
|
-
4. **
|
|
25
|
-
5. **BATCH ALLOWED** — Same category, max 5 tasks
|
|
21
|
+
1. **VERIFY DEPENDENCIES** — All task dependencies must be completed
|
|
22
|
+
2. **DELEGATE TO /apex** — All code generation via `/apex -d`
|
|
23
|
+
3. **TRACK RESULTS** — Verify PRD state after apex returns
|
|
24
|
+
4. **NEVER GENERATE CODE** — Ralph orchestrates, apex generates
|
|
26
25
|
|
|
27
26
|
---
|
|
28
27
|
|
|
@@ -51,723 +50,113 @@ task.started_at = new Date().toISOString();
|
|
|
51
50
|
writeJSON('.ralph/prd.json', prd);
|
|
52
51
|
```
|
|
53
52
|
|
|
54
|
-
### 3.
|
|
53
|
+
### 3. Delegate to /apex
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
#### 3a. Section-Split Mode
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
| Category | Special Action |
|
|
63
|
-
|----------|---------------|
|
|
64
|
-
| `infrastructure` with `_migrationMeta` | Migration sequence: MCP suggest → add → update → build |
|
|
65
|
-
| `infrastructure` with seed data keywords | **MANDATORY**: Read `references/core-seed-data.md` |
|
|
66
|
-
| `frontend` | MCP-first: scaffold_api_client → scaffold_routes → `/ui-components` skill (pages) → form pages (create/edit = full pages, ZERO modals) → form tests → i18n (4 langues) |
|
|
67
|
-
| `test` | Generate via MCP → run → fix loop → 100% pass required |
|
|
68
|
-
| `validation` | Clean build → full test suite → MCP validate |
|
|
69
|
-
|
|
70
|
-
### 4. Implement
|
|
71
|
-
|
|
72
|
-
- Create/modify files following SmartStack conventions
|
|
73
|
-
- Track: `{files_created} = []`, `{files_modified} = []`
|
|
74
|
-
- Use MCP tools per category (see category-rules.md)
|
|
75
|
-
|
|
76
|
-
### 5. Post-Infrastructure Build Gate (BLOCKING)
|
|
77
|
-
|
|
78
|
-
**After completing ALL infrastructure tasks (configs + migration + seed data):**
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
dotnet build --no-restore
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
MUST pass before proceeding to application/api/test/frontend. Fix if fails.
|
|
85
|
-
|
|
86
|
-
**POST-CHECK: Route kebab-case validation (BLOCKING)**
|
|
87
|
-
|
|
88
|
-
After generating NavigationSeedData files, verify routes follow kebab-case convention:
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
# Search for routes with uppercase in NavigationSeedData files
|
|
92
|
-
UPPERCASE_ROUTES=$(grep -E 'Route = "?/[^"]*[A-Z]' Infrastructure/Persistence/Seeding/Data/*/NavigationSeedData.cs 2>/dev/null)
|
|
93
|
-
|
|
94
|
-
if [ -n "$UPPERCASE_ROUTES" ]; then
|
|
95
|
-
echo "❌ ERROR: Routes must be kebab-case lowercase. Found PascalCase in NavigationSeedData routes:"
|
|
96
|
-
echo "$UPPERCASE_ROUTES"
|
|
97
|
-
echo ""
|
|
98
|
-
echo "Expected format: /business/human-resources/projects"
|
|
99
|
-
echo "Fix: Use ToKebabCase() helper in route generation (see core-seed-data.md)"
|
|
100
|
-
exit 1
|
|
101
|
-
fi
|
|
102
|
-
|
|
103
|
-
# Search for multi-word route segments missing hyphens (e.g., "humanresources" instead of "human-resources")
|
|
104
|
-
MULTI_WORD_ROUTES=$(grep -oP 'Route\s*=\s*"([^"]+)"' Infrastructure/Persistence/Seeding/Data/*/NavigationSeedData.cs 2>/dev/null | grep -P '"[^"]*(?:human(?:resources|resource)|time(?:management|tracking)|project(?:management|member)|leave(?:balance|request))[^"]*"')
|
|
105
|
-
|
|
106
|
-
if [ -n "$MULTI_WORD_ROUTES" ]; then
|
|
107
|
-
echo "❌ BLOCKING: Route segments contain concatenated multi-word names without hyphens"
|
|
108
|
-
echo "Found: $MULTI_WORD_ROUTES"
|
|
109
|
-
echo ""
|
|
110
|
-
echo "Convention: multi-word segments MUST use kebab-case"
|
|
111
|
-
echo " humanresources → human-resources"
|
|
112
|
-
echo " timemanagement → time-management"
|
|
113
|
-
echo "Fix: Apply ToKebabCase() to all route segments in NavigationSeedData"
|
|
114
|
-
exit 1
|
|
115
|
-
fi
|
|
116
|
-
```
|
|
57
|
+
```javascript
|
|
58
|
+
if (prd._sectionSplit?.enabled) {
|
|
59
|
+
// Find next pending phase with dependencies met
|
|
60
|
+
const nextPhase = prd._sectionSplit.phases.find(p => p.status === 'pending');
|
|
117
61
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
- Convention: `HumanResources` (C# code) → `human-resources` (web URL)
|
|
123
|
-
|
|
124
|
-
**Fix:** If check fails, routes were generated without `ToKebabCase()` transformation. Regenerate NavigationSeedData with the helper.
|
|
125
|
-
|
|
126
|
-
**POST-CHECK: Core Seed Data Integrity (BLOCKING)**
|
|
127
|
-
|
|
128
|
-
After generating ALL seed data files (NavigationApplicationSeedData, NavigationModuleSeedData, PermissionsSeedData, RolesSeedData, ApplicationRolesSeedData, IClientSeedDataProvider), verify the complete chain:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
# 1. NavigationApplicationSeedData.cs exists
|
|
132
|
-
APP_SEED=$(find . -path "*/Seeding/Data/NavigationApplicationSeedData.cs" 2>/dev/null | head -1)
|
|
133
|
-
if [ -z "$APP_SEED" ]; then
|
|
134
|
-
echo "❌ BLOCKING: NavigationApplicationSeedData.cs NOT FOUND"
|
|
135
|
-
echo "Without this, nav_Applications is empty → navigation menu invisible"
|
|
136
|
-
echo "Fix: Generate from core-seed-data.md section 1b using seedDataCore.navigationApplications"
|
|
137
|
-
exit 1
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
# 2. ApplicationRolesSeedData.cs references NavigationApplicationSeedData.ApplicationId (no placeholder)
|
|
141
|
-
if grep -q '{ApplicationGuid}' Infrastructure/Persistence/Seeding/Data/ApplicationRolesSeedData.cs 2>/dev/null; then
|
|
142
|
-
echo "❌ BLOCKING: ApplicationRolesSeedData still has {ApplicationGuid} placeholder"
|
|
143
|
-
echo "Fix: Replace with NavigationApplicationSeedData.ApplicationId"
|
|
144
|
-
exit 1
|
|
145
|
-
fi
|
|
146
|
-
|
|
147
|
-
# 3. IClientSeedDataProvider has no hardcoded app placeholders
|
|
148
|
-
PROVIDER=$(find . -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
|
|
149
|
-
if [ -n "$PROVIDER" ]; then
|
|
150
|
-
if grep -qE '\{appLabel_|appDesc_|appIcon\}' "$PROVIDER" 2>/dev/null; then
|
|
151
|
-
echo "❌ BLOCKING: SeedDataProvider has hardcoded {appLabel_xx}/{appIcon} placeholders"
|
|
152
|
-
echo "Fix: Use NavigationApplicationSeedData.GetApplicationEntry() and GetTranslationEntries()"
|
|
153
|
-
exit 1
|
|
154
|
-
fi
|
|
155
|
-
fi
|
|
156
|
-
|
|
157
|
-
# 4. Quick startup test — verify seed data doesn't crash at runtime
|
|
158
|
-
echo "Running startup test to verify seed data..."
|
|
159
|
-
dotnet run --project "$API_PROJECT" --urls "http://localhost:0" -- --environment Development &
|
|
160
|
-
RUN_PID=$!
|
|
161
|
-
sleep 8
|
|
162
|
-
|
|
163
|
-
if ! kill -0 $RUN_PID 2>/dev/null; then
|
|
164
|
-
echo "❌ BLOCKING: Application crashed during startup (seed data likely failed)"
|
|
165
|
-
echo "Check logs for: navigation, role, or permission seed data errors"
|
|
166
|
-
wait $RUN_PID 2>/dev/null
|
|
167
|
-
exit 1
|
|
168
|
-
else
|
|
169
|
-
echo "✓ Startup test passed — seed data executed without crash"
|
|
170
|
-
kill $RUN_PID 2>/dev/null
|
|
171
|
-
wait $RUN_PID 2>/dev/null
|
|
172
|
-
fi
|
|
173
|
-
```
|
|
62
|
+
if (!nextPhase) {
|
|
63
|
+
// All phases done — skip to step-04 completion check
|
|
64
|
+
goto STEP_04;
|
|
65
|
+
}
|
|
174
66
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
- If `auth_Permissions` is empty → all authorization checks reject → 403 on every endpoint
|
|
179
|
-
- These are **foundational** — without them, ALL subsequent features (frontend, API, tests) are useless
|
|
180
|
-
|
|
181
|
-
**POST-CHECK: Section route/permission completeness (BLOCKING — when sections defined)**
|
|
182
|
-
|
|
183
|
-
After generating section seed data, verify completeness:
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
# 1. Verify every NavigationSection seed route has a frontend route match
|
|
187
|
-
SECTION_SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" -exec grep -l 'GetSectionEntries' {} \; 2>/dev/null)
|
|
188
|
-
if [ -n "$SECTION_SEED_FILES" ]; then
|
|
189
|
-
echo "Sections defined — verifying frontend route matches..."
|
|
190
|
-
SECTION_ROUTES=$(grep -ohP 'Route\s*=\s*ToKebabCase\(\$?"([^"]+)"\)' $SECTION_SEED_FILES 2>/dev/null | grep -i section)
|
|
191
|
-
APP_TSX="$WEB_SRC/App.tsx"
|
|
192
|
-
if [ -n "$APP_TSX" ] && [ -n "$SECTION_ROUTES" ]; then
|
|
193
|
-
while IFS= read -r ROUTE; do
|
|
194
|
-
ROUTE_PATH=$(echo "$ROUTE" | grep -oP '"([^"]+)"' | tr -d '"')
|
|
195
|
-
if [ -n "$ROUTE_PATH" ] && ! grep -q "$ROUTE_PATH" "$APP_TSX" 2>/dev/null; then
|
|
196
|
-
echo "WARNING: Section route '$ROUTE_PATH' not found in App.tsx — add frontend route"
|
|
197
|
-
fi
|
|
198
|
-
done <<< "$SECTION_ROUTES"
|
|
199
|
-
fi
|
|
200
|
-
|
|
201
|
-
# 2. Verify section-level permissions exist when sections are defined
|
|
202
|
-
PERM_SEED=$(find . -path "*/Seeding/Data/*" -name "PermissionsSeedData.cs" 2>/dev/null)
|
|
203
|
-
if [ -n "$PERM_SEED" ]; then
|
|
204
|
-
for PS in $PERM_SEED; do
|
|
205
|
-
HAS_SECTION_PERMS=$(grep -c 'sectionCode\|SectionPascal\|Level = PermissionLevel.Section' "$PS" 2>/dev/null)
|
|
206
|
-
if [ "$HAS_SECTION_PERMS" -eq 0 ]; then
|
|
207
|
-
echo "BLOCKING: Sections defined but PermissionsSeedData missing section-level permissions: $PS"
|
|
208
|
-
echo "Fix: Add section-level permission GUIDs and entries per core-seed-data.md Step C2"
|
|
209
|
-
exit 1
|
|
210
|
-
fi
|
|
211
|
-
done
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
# 3. Verify section-level role mappings exist
|
|
215
|
-
ROLE_SEED=$(find . -path "*/Seeding/Data/*" -name "RolesSeedData.cs" 2>/dev/null)
|
|
216
|
-
if [ -n "$ROLE_SEED" ]; then
|
|
217
|
-
for RS in $ROLE_SEED; do
|
|
218
|
-
HAS_SECTION_ROLES=$(grep -c 'sectionCode' "$RS" 2>/dev/null)
|
|
219
|
-
if [ "$HAS_SECTION_ROLES" -eq 0 ]; then
|
|
220
|
-
echo "BLOCKING: Sections defined but RolesSeedData missing section-level role mappings: $RS"
|
|
221
|
-
echo "Fix: Add section-level role-permission entries per core-seed-data.md section 5"
|
|
222
|
-
exit 1
|
|
223
|
-
fi
|
|
224
|
-
done
|
|
225
|
-
fi
|
|
226
|
-
fi
|
|
227
|
-
```
|
|
67
|
+
const depsOk = nextPhase.dependsOn.every(depIdx =>
|
|
68
|
+
prd._sectionSplit.phases[depIdx].status === 'completed'
|
|
69
|
+
);
|
|
228
70
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
```bash
|
|
234
|
-
# Detect CRUD suffixes in navigation section routes
|
|
235
|
-
CRUD_ROUTES=$(grep -rn 'Route.*=.*\/list\b\|Route.*=.*\/detail' Infrastructure/Persistence/Seeding/Data/ 2>/dev/null | grep -v '//.*Route')
|
|
236
|
-
|
|
237
|
-
if [ -n "$CRUD_ROUTES" ]; then
|
|
238
|
-
echo "BLOCKING: Navigation section routes contain CRUD suffixes"
|
|
239
|
-
echo "Convention:"
|
|
240
|
-
echo " - 'list' section route = module route (NO /list suffix)"
|
|
241
|
-
echo " - 'detail' section route = module route + /:id (NOT /detail/:id)"
|
|
242
|
-
echo " - Other sections = module route + /{section-kebab}"
|
|
243
|
-
echo ""
|
|
244
|
-
echo "Found:"
|
|
245
|
-
echo "$CRUD_ROUTES"
|
|
246
|
-
echo ""
|
|
247
|
-
echo "Fix: Remove /list suffix (use module route), replace /detail/:id with /:id"
|
|
248
|
-
exit 1
|
|
249
|
-
fi
|
|
250
|
-
```
|
|
71
|
+
if (!depsOk) {
|
|
72
|
+
console.warn(`Phase ${nextPhase.phase} blocked by incomplete dependencies`);
|
|
73
|
+
goto STEP_04;
|
|
74
|
+
}
|
|
251
75
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
**POST-CHECK: Permission path segment count (WARNING — when permissions defined)**
|
|
258
|
-
|
|
259
|
-
```bash
|
|
260
|
-
PERM_FILES=$(find Infrastructure/Persistence/Seeding/Data/ -name "PermissionsSeedData.cs" 2>/dev/null)
|
|
261
|
-
if [ -n "$PERM_FILES" ]; then
|
|
262
|
-
MALFORMED=$(grep -oP 'Path\s*=\s*"([^"]+)"' $PERM_FILES | grep -oP '"[^"]+"' | tr -d '"' | while read -r path; do
|
|
263
|
-
DOTS=$(echo "$path" | tr -cd '.' | wc -c)
|
|
264
|
-
if echo "$path" | grep -qP '\.\*$'; then continue; fi
|
|
265
|
-
if [ "$DOTS" -lt 3 ] || [ "$DOTS" -gt 5 ]; then
|
|
266
|
-
echo " Unexpected segment count ($((DOTS+1))): $path"
|
|
267
|
-
fi
|
|
268
|
-
done)
|
|
269
|
-
if [ -n "$MALFORMED" ]; then
|
|
270
|
-
echo "WARNING: Permission paths with unexpected segment count:"
|
|
271
|
-
echo "$MALFORMED"
|
|
272
|
-
echo " Expected: 4 segments (module-level) or 5 segments (section-level)"
|
|
273
|
-
fi
|
|
274
|
-
fi
|
|
275
|
-
```
|
|
76
|
+
const phaseLabel = nextPhase.type === 'foundation'
|
|
77
|
+
? 'Phase 0: Foundation (all entities + migration + module seed data)'
|
|
78
|
+
: `Phase ${nextPhase.phase}: Section "${nextPhase.sectionCode}" (services + controllers + pages + tests)`;
|
|
79
|
+
console.log(`SECTION SPLIT: ${phaseLabel}`);
|
|
276
80
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
After generating SeedConstants.cs and DevDataSeeder, verify that `DefaultTenantId` is not a phantom GUID that doesn't exist in `core.tenant_Tenants`:
|
|
280
|
-
|
|
281
|
-
```bash
|
|
282
|
-
# Find SeedConstants.cs
|
|
283
|
-
SEED_CONSTANTS=$(find . -path "*/SeedConstants.cs" 2>/dev/null | head -1)
|
|
284
|
-
if [ -n "$SEED_CONSTANTS" ]; then
|
|
285
|
-
# Extract the DefaultTenantId GUID value
|
|
286
|
-
TENANT_GUID=$(grep -oP 'DefaultTenantId\s*=\s*Guid\.Parse\("([^"]+)"\)' "$SEED_CONSTANTS" | grep -oP '"[^"]+"' | tr -d '"')
|
|
287
|
-
|
|
288
|
-
if [ -z "$TENANT_GUID" ]; then
|
|
289
|
-
echo "⚠️ WARNING: SeedConstants.cs found but DefaultTenantId not defined"
|
|
290
|
-
echo "DevDataSeeder requires SeedConstants.DefaultTenantId for business seed data"
|
|
291
|
-
echo "Fix: Add 'public static readonly Guid DefaultTenantId = Guid.Parse(\"...\");' to SeedConstants.cs"
|
|
292
|
-
else
|
|
293
|
-
# Verify the GUID is referenced somewhere in platform config (not an invented value)
|
|
294
|
-
GUID_FOUND=$(grep -r "$TENANT_GUID" appsettings*.json src/ 2>/dev/null | grep -v SeedConstants)
|
|
295
|
-
|
|
296
|
-
if [ -z "$GUID_FOUND" ]; then
|
|
297
|
-
echo "⚠️ WARNING: SeedConstants.DefaultTenantId ($TENANT_GUID) not found in platform config"
|
|
298
|
-
echo "This GUID must exist in core.tenant_Tenants at runtime"
|
|
299
|
-
echo "Verify: The SmartStack platform seeds this tenant during InitializeSmartStackAsync()"
|
|
300
|
-
echo "If using a custom TenantId, ensure it's created before DevDataSeeder runs (Order >= 200)"
|
|
301
|
-
echo "validate-feature step-05 will verify this FK exists in real database"
|
|
302
|
-
else
|
|
303
|
-
echo "✓ SeedConstants.DefaultTenantId ($TENANT_GUID) found in platform config"
|
|
304
|
-
fi
|
|
305
|
-
fi
|
|
306
|
-
fi
|
|
307
|
-
```
|
|
81
|
+
// INVOKE /apex -d {nextPhase.prdFile}
|
|
82
|
+
// Apex sees a normal (smaller) PRD and executes normally
|
|
308
83
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
- Business entities with invalid `TenantId` FK cause runtime 500 errors
|
|
312
|
-
- The tenant must be seeded by `InitializeSmartStackAsync()` BEFORE DevDataSeeder (Order >= 200) runs
|
|
84
|
+
// After apex returns: merge results into master PRD
|
|
85
|
+
// Read references/section-splitting.md sections 7-9 (execution, merging, failure handling)
|
|
313
86
|
|
|
314
|
-
|
|
87
|
+
nextPhase.status = 'completed';
|
|
88
|
+
prd._sectionSplit.currentPhase = nextPhase.phase;
|
|
89
|
+
writeJSON('.ralph/prd.json', prd);
|
|
315
90
|
|
|
91
|
+
// Continue to step-03 (commit), then step-04 loops back for next phase
|
|
92
|
+
}
|
|
316
93
|
```
|
|
317
|
-
mcp__smartstack__validate_conventions: checks: ["all"]
|
|
318
94
|
|
|
319
|
-
|
|
320
|
-
infrastructure → mcp__smartstack__check_migrations
|
|
321
|
-
api → mcp__smartstack__validate_security
|
|
322
|
-
frontend → mcp__smartstack__validate_frontend_routes
|
|
323
|
-
test → mcp__smartstack__analyze_test_coverage
|
|
324
|
-
```
|
|
95
|
+
> See `references/section-splitting.md` for full detection, mapping, PRD construction, and merge logic.
|
|
325
96
|
|
|
326
|
-
|
|
97
|
+
#### 3b. Standard Mode (no split)
|
|
327
98
|
|
|
328
|
-
|
|
99
|
+
**INVOKE `/apex -d .ralph/prd.json`**
|
|
329
100
|
|
|
330
|
-
|
|
101
|
+
This delegates ALL remaining tasks for the current module to apex:
|
|
331
102
|
|
|
332
|
-
|
|
103
|
+
- Apex reads the PRD, extracts context (`context_code`, `app_name`, `module_code`, `entities`, `sections`)
|
|
104
|
+
- Apex executes ALL layers: domain → infrastructure → migration → application → api → seed data → frontend → tests
|
|
105
|
+
- Apex runs full POST-CHECKs (50 checks from `references/post-checks.md`)
|
|
106
|
+
- Apex commits per layer (atomic commits)
|
|
107
|
+
- Apex validates with MCP (`validate_conventions`)
|
|
108
|
+
- Apex updates task statuses in the PRD file directly
|
|
333
109
|
|
|
334
|
-
**
|
|
335
|
-
|
|
336
|
-
dotnet build --no-restore
|
|
337
|
-
```
|
|
338
|
-
If FAIL → read error output → identify file:line → fix code → rebuild → loop until exit code 0.
|
|
110
|
+
> **FLAGS:** `-d` implies `-a` (auto, no user confirmation) and `-e` (economy, no nested teams).
|
|
111
|
+
> Ralph manages parallelism via team orchestration, not apex.
|
|
339
112
|
|
|
340
|
-
|
|
341
|
-
```bash
|
|
342
|
-
TEST_PROJECT="tests/$(basename $(pwd)).Tests.Unit"
|
|
343
|
-
dotnet build "$TEST_PROJECT" --no-restore
|
|
344
|
-
dotnet test "$TEST_PROJECT" --no-build --verbosity normal
|
|
345
|
-
```
|
|
346
|
-
If FAIL → analyze failing test names → fix SOURCE CODE (not tests) → rebuild → retest.
|
|
347
|
-
**Loop until 100% pass — NEVER proceed with failing tests.**
|
|
113
|
+
### 4. Verify Post-Apex Results
|
|
348
114
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
```
|
|
354
|
-
If FAIL → read errors → fix TSX/TS files → re-run → loop until pass.
|
|
115
|
+
```javascript
|
|
116
|
+
// Re-read PRD after apex execution
|
|
117
|
+
const updatedPrd = readJSON('.ralph/prd.json');
|
|
118
|
+
const moduleTasks = updatedPrd.tasks.filter(t => t.module === {current_module});
|
|
355
119
|
|
|
356
|
-
|
|
120
|
+
const completed = moduleTasks.filter(t => t.status === 'completed').length;
|
|
121
|
+
const failed = moduleTasks.filter(t => t.status === 'failed').length;
|
|
122
|
+
const pending = moduleTasks.filter(t => t.status === 'pending').length;
|
|
357
123
|
|
|
358
|
-
|
|
359
|
-
|
|
124
|
+
if (failed > 0) {
|
|
125
|
+
console.log(`WARNING: ${failed} tasks failed during apex execution`);
|
|
126
|
+
// Failed tasks will be retried in compact loop
|
|
127
|
+
}
|
|
360
128
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
HOOK_DIR=$(find . -path "*/src/hooks" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
365
|
-
COMP_DIR=$(find . -path "*/src/components" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
366
|
-
WEB_SRC=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
|
|
367
|
-
# All POST-CHECKs below use $PAGE_DIR, $HOOK_DIR, $COMP_DIR, $WEB_SRC instead of hardcoded paths
|
|
368
|
-
```
|
|
129
|
+
if (pending > 0) {
|
|
130
|
+
console.log(`INFO: ${pending} tasks still pending — will be handled in compact loop`);
|
|
131
|
+
}
|
|
369
132
|
|
|
370
|
-
|
|
371
|
-
# POST-CHECK: Forms must be full pages — ZERO modals/popups/drawers
|
|
372
|
-
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" 2>/dev/null)
|
|
373
|
-
if [ -n "$PAGE_FILES" ]; then
|
|
374
|
-
MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet)" $PAGE_FILES 2>/dev/null)
|
|
375
|
-
if [ -n "$MODAL_IMPORTS" ]; then
|
|
376
|
-
echo "BLOCKING: Form pages must NOT use Modal/Dialog/Drawer/Popup components"
|
|
377
|
-
echo "Create/Edit forms MUST be full pages with own URL routes (/create, /:id/edit)"
|
|
378
|
-
echo "$MODAL_IMPORTS"
|
|
379
|
-
exit 1
|
|
380
|
-
fi
|
|
381
|
-
fi
|
|
382
|
-
|
|
383
|
-
# POST-CHECK: No raw HTML <table> in page files — MUST use SmartTable/DataTable (BLOCKING)
|
|
384
|
-
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" 2>/dev/null)
|
|
385
|
-
if [ -n "$PAGE_FILES" ]; then
|
|
386
|
-
RAW_TABLES=$(grep -Pn '<table[\s>]|<thead[\s>]|<tbody[\s>]' $PAGE_FILES 2>/dev/null)
|
|
387
|
-
if [ -n "$RAW_TABLES" ]; then
|
|
388
|
-
echo "BLOCKING: Raw HTML <table> detected in page files — MUST use SmartTable or DataTable"
|
|
389
|
-
echo "SmartStack convention: Lists MUST use SmartTable + SmartFilter (see /ui-components skill)"
|
|
390
|
-
echo "Import: import { DataTable } from '@/components/ui/DataTable'"
|
|
391
|
-
echo ""
|
|
392
|
-
echo "Found:"
|
|
393
|
-
echo "$RAW_TABLES"
|
|
394
|
-
exit 1
|
|
395
|
-
fi
|
|
396
|
-
fi
|
|
397
|
-
|
|
398
|
-
# POST-CHECK: No hardcoded Tailwind colors — MUST use CSS variables (BLOCKING)
|
|
399
|
-
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" -o -name "*.tsx" 2>/dev/null | sort -u)
|
|
400
|
-
COMPONENT_FILES=$(find "$COMP_DIR" -name "*.tsx" 2>/dev/null)
|
|
401
|
-
ALL_TSX="$PAGE_FILES $COMPONENT_FILES"
|
|
402
|
-
if [ -n "$ALL_TSX" ]; then
|
|
403
|
-
# Match bg-{color}-{shade} or text-{color}-{shade} patterns (excluding bg-white, bg-black, bg-transparent, bg-current)
|
|
404
|
-
HARDCODED_COLORS=$(grep -Pn '(?:bg|text|border|ring|from|to|via)-(?:red|blue|green|yellow|orange|purple|pink|indigo|teal|cyan|emerald|violet|fuchsia|rose|amber|lime|sky|slate|gray|zinc|neutral|stone)-\d{2,3}' $ALL_TSX 2>/dev/null | head -20)
|
|
405
|
-
if [ -n "$HARDCODED_COLORS" ]; then
|
|
406
|
-
echo "BLOCKING: Hardcoded Tailwind colors detected — MUST use CSS variables"
|
|
407
|
-
echo "SmartStack convention: ALL colors via CSS variables for theming support"
|
|
408
|
-
echo " bg-blue-600 → bg-[var(--color-accent-600)]"
|
|
409
|
-
echo " text-red-500 → text-[var(--error-text)]"
|
|
410
|
-
echo " bg-green-100 → bg-[var(--success-bg)]"
|
|
411
|
-
echo " bg-gray-50 → bg-[var(--bg-secondary)]"
|
|
412
|
-
echo ""
|
|
413
|
-
echo "Found:"
|
|
414
|
-
echo "$HARDCODED_COLORS"
|
|
415
|
-
exit 1
|
|
416
|
-
fi
|
|
417
|
-
fi
|
|
418
|
-
|
|
419
|
-
# POST-CHECK: List pages must import SmartTable/DataTable OR EntityCard (WARNING)
|
|
420
|
-
LIST_PAGES=$(find "$PAGE_DIR" -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
|
|
421
|
-
if [ -n "$LIST_PAGES" ]; then
|
|
422
|
-
for LP in $LIST_PAGES; do
|
|
423
|
-
HAS_TABLE=$(grep -c 'SmartTable\|DataTable' "$LP" 2>/dev/null)
|
|
424
|
-
HAS_CARD=$(grep -c 'EntityCard' "$LP" 2>/dev/null)
|
|
425
|
-
if [ "$HAS_TABLE" -eq 0 ] && [ "$HAS_CARD" -eq 0 ]; then
|
|
426
|
-
echo "WARNING: List page missing SmartTable/DataTable or EntityCard: $LP"
|
|
427
|
-
echo "List pages MUST use SmartStack UI components from /ui-components skill"
|
|
428
|
-
echo " Tables: import { DataTable } from '@/components/ui/DataTable'"
|
|
429
|
-
echo " Cards: import { EntityCard } from '@/components/ui/EntityCard'"
|
|
430
|
-
fi
|
|
431
|
-
done
|
|
432
|
-
fi
|
|
433
|
-
|
|
434
|
-
# POST-CHECK: No window.confirm() — MUST use confirmation component (BLOCKING)
|
|
435
|
-
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" 2>/dev/null)
|
|
436
|
-
if [ -n "$PAGE_FILES" ]; then
|
|
437
|
-
BAD_CONFIRM=$(grep -Pn 'window\.confirm\s*\(|(?<!\w)confirm\s*\(' $PAGE_FILES 2>/dev/null | grep -v '//' | grep -v test)
|
|
438
|
-
if [ -n "$BAD_CONFIRM" ]; then
|
|
439
|
-
echo "BLOCKING: window.confirm() detected — MUST use a SmartStack confirmation component"
|
|
440
|
-
echo "Native browser dialogs break theming and i18n"
|
|
441
|
-
echo "Use a ConfirmDialog component with translated messages"
|
|
442
|
-
echo ""
|
|
443
|
-
echo "Found:"
|
|
444
|
-
echo "$BAD_CONFIRM"
|
|
445
|
-
exit 1
|
|
446
|
-
fi
|
|
447
|
-
fi
|
|
448
|
-
|
|
449
|
-
# POST-CHECK: Pages must handle loading, error, and empty states (WARNING)
|
|
450
|
-
PAGE_FILES=$(find "$PAGE_DIR" -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
|
|
451
|
-
if [ -n "$PAGE_FILES" ]; then
|
|
452
|
-
for LP in $PAGE_FILES; do
|
|
453
|
-
HAS_LOADING=$(grep -c 'loading\|isLoading\|skeleton\|Skeleton\|PageLoader\|spinner' "$LP" 2>/dev/null)
|
|
454
|
-
HAS_ERROR=$(grep -c 'error\|Error' "$LP" 2>/dev/null)
|
|
455
|
-
HAS_EMPTY=$(grep -c 'empty\|Empty\|no.*found\|length === 0' "$LP" 2>/dev/null)
|
|
456
|
-
MISSING=""
|
|
457
|
-
[ "$HAS_LOADING" -eq 0 ] && MISSING="${MISSING}loading "
|
|
458
|
-
[ "$HAS_ERROR" -eq 0 ] && MISSING="${MISSING}error "
|
|
459
|
-
[ "$HAS_EMPTY" -eq 0 ] && MISSING="${MISSING}empty "
|
|
460
|
-
if [ -n "$MISSING" ]; then
|
|
461
|
-
echo "WARNING: List page missing state handling ($MISSING): $LP"
|
|
462
|
-
echo "All list pages MUST handle: loading skeleton, error with retry, empty state"
|
|
463
|
-
fi
|
|
464
|
-
done
|
|
465
|
-
fi
|
|
466
|
-
|
|
467
|
-
# POST-CHECK: Create/Edit pages must exist for each module with a list page
|
|
468
|
-
LIST_PAGES=$(find "$PAGE_DIR" -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
|
|
469
|
-
if [ -n "$LIST_PAGES" ]; then
|
|
470
|
-
for LP in $LIST_PAGES; do
|
|
471
|
-
DIR=$(dirname "$LP"); MOD=$(basename "$DIR")
|
|
472
|
-
if [ -z "$(find "$DIR" -name "*CreatePage.tsx" 2>/dev/null)" ]; then
|
|
473
|
-
echo "WARNING: Module $MOD has list page but no CreatePage"
|
|
474
|
-
fi
|
|
475
|
-
if [ -z "$(find "$DIR" -name "*EditPage.tsx" 2>/dev/null)" ]; then
|
|
476
|
-
echo "WARNING: Module $MOD has list page but no EditPage"
|
|
477
|
-
fi
|
|
478
|
-
done
|
|
479
|
-
fi
|
|
480
|
-
|
|
481
|
-
# First check: at least one test file must exist in pages/
|
|
482
|
-
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null)
|
|
483
|
-
if [ -n "$PAGE_FILES" ]; then
|
|
484
|
-
TEST_FILES=$(find "$PAGE_DIR" -name "*.test.tsx" 2>/dev/null)
|
|
485
|
-
if [ -z "$TEST_FILES" ]; then
|
|
486
|
-
echo "BLOCKING: No frontend test files found in src/pages/"
|
|
487
|
-
echo "Every module with pages MUST have at least one .test.tsx file"
|
|
488
|
-
exit 1
|
|
489
|
-
fi
|
|
490
|
-
fi
|
|
491
|
-
|
|
492
|
-
# POST-CHECK: Form pages must have companion test files
|
|
493
|
-
FORM_PAGES=$(find "$PAGE_DIR" -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test)
|
|
494
|
-
if [ -n "$FORM_PAGES" ]; then
|
|
495
|
-
for FP in $FORM_PAGES; do
|
|
496
|
-
TEST="${FP%.tsx}.test.tsx"
|
|
497
|
-
if [ ! -f "$TEST" ]; then
|
|
498
|
-
echo "BLOCKING: Form page missing test file: $FP → expected $TEST"
|
|
499
|
-
exit 1
|
|
500
|
-
fi
|
|
501
|
-
done
|
|
502
|
-
fi
|
|
503
|
-
|
|
504
|
-
# POST-CHECK: FK fields must NOT be plain text inputs — use EntityLookup
|
|
505
|
-
FORM_PAGES=$(find "$PAGE_DIR" -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test | grep -v node_modules)
|
|
506
|
-
if [ -n "$FORM_PAGES" ]; then
|
|
507
|
-
# Match any input with name ending in "Id" (except hidden inputs)
|
|
508
|
-
FK_TEXT_INPUTS=$(grep -Pn '<input[^>]*name=["\x27][a-zA-Z]*Id["\x27]' $FORM_PAGES 2>/dev/null | grep -v 'type=["\x27]hidden["\x27]')
|
|
509
|
-
if [ -n "$FK_TEXT_INPUTS" ]; then
|
|
510
|
-
echo "BLOCKING: FK Guid fields rendered as plain text inputs — MUST use EntityLookup"
|
|
511
|
-
echo "See smartstack-frontend.md section 6 for the EntityLookup pattern"
|
|
512
|
-
echo "$FK_TEXT_INPUTS"
|
|
513
|
-
exit 1
|
|
514
|
-
fi
|
|
515
|
-
FK_PLACEHOLDER=$(grep -Pn 'placeholder=["\x27].*[Ee]nter.*[Ii][Dd]|placeholder=["\x27].*[Gg][Uu][Ii][Dd]' $FORM_PAGES 2>/dev/null)
|
|
516
|
-
if [ -n "$FK_PLACEHOLDER" ]; then
|
|
517
|
-
echo "BLOCKING: Form placeholder asks user to enter ID/GUID — use EntityLookup instead"
|
|
518
|
-
echo "$FK_PLACEHOLDER"
|
|
519
|
-
exit 1
|
|
520
|
-
fi
|
|
521
|
-
fi
|
|
522
|
-
|
|
523
|
-
# POST-CHECK: Backend GetAll endpoints must support ?search= for EntityLookup
|
|
524
|
-
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
525
|
-
if [ -n "$CTRL_FILES" ]; then
|
|
526
|
-
for f in $CTRL_FILES; do
|
|
527
|
-
if grep -q "\[HttpGet\]" "$f" && grep -q "GetAll" "$f"; then
|
|
528
|
-
if ! grep -q "search" "$f"; then
|
|
529
|
-
echo "WARNING: Controller missing search parameter on GetAll: $f"
|
|
530
|
-
echo "GetAll MUST accept ?search= to enable EntityLookup on frontend"
|
|
531
|
-
fi
|
|
532
|
-
fi
|
|
533
|
-
done
|
|
534
|
-
fi
|
|
535
|
-
|
|
536
|
-
# POST-CHECK: Route seed data vs frontend cross-validation (CONVENTION + EXISTENCE)
|
|
537
|
-
SEED_NAV_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" 2>/dev/null)
|
|
538
|
-
APP_TSX="$WEB_SRC/App.tsx"
|
|
539
|
-
if [ -n "$SEED_NAV_FILES" ] && [ -n "$APP_TSX" ]; then
|
|
540
|
-
SEED_ROUTES=$(grep -ohP 'Route\s*=\s*"([^"]+)"' $SEED_NAV_FILES | sed 's/Route\s*=\s*"//' | sed 's/"//' | sort -u)
|
|
541
|
-
CLIENT_PATHS=$(grep -ohP "path:\s*['\"]([^'\"]+)['\"]" "$APP_TSX" | sed "s/path:\s*['\"]//;s/['\"]//" | sort -u)
|
|
542
|
-
if [ -n "$SEED_ROUTES" ] && [ -z "$CLIENT_PATHS" ]; then
|
|
543
|
-
echo "WARNING: Seed data has navigation routes but App.tsx has no client routes defined"
|
|
544
|
-
fi
|
|
545
|
-
# Cross-check: frontend paths must use same kebab-case segments as backend API routes
|
|
546
|
-
API_CONTROLLERS=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
547
|
-
if [ -n "$API_CONTROLLERS" ] && [ -n "$CLIENT_PATHS" ]; then
|
|
548
|
-
API_SEGMENTS=$(grep -ohP '\[Route\("api/([^"]+)"\)\]' $API_CONTROLLERS | sed 's/\[Route("api\///;s/")\]//' | tr '/' '\n' | sort -u)
|
|
549
|
-
for SEG in $API_SEGMENTS; do
|
|
550
|
-
# Check if frontend path contains same segment (kebab-case match)
|
|
551
|
-
if echo "$SEG" | grep -qP '[a-z]+-[a-z]+'; then
|
|
552
|
-
# Multi-word segment like "human-resources" — verify frontend uses same convention
|
|
553
|
-
NO_HYPHEN=$(echo "$SEG" | tr -d '-')
|
|
554
|
-
if echo "$CLIENT_PATHS" | grep -q "$NO_HYPHEN"; then
|
|
555
|
-
echo "BLOCKING: Frontend route uses '$NO_HYPHEN' but backend API uses '$SEG' (kebab-case)"
|
|
556
|
-
echo "Fix: Frontend paths MUST match backend route convention"
|
|
557
|
-
echo " Backend: api/business/$SEG/..."
|
|
558
|
-
echo " Frontend: /business/$SEG/... (NOT /business/$NO_HYPHEN/...)"
|
|
559
|
-
exit 1
|
|
560
|
-
fi
|
|
561
|
-
fi
|
|
562
|
-
done
|
|
563
|
-
fi
|
|
564
|
-
fi
|
|
565
|
-
|
|
566
|
-
# POST-CHECK: HasQueryFilter anti-pattern
|
|
567
|
-
CONFIG_FILES=$(find src/ -path "*/Configurations/*" -name "*Configuration.cs" 2>/dev/null)
|
|
568
|
-
if [ -n "$CONFIG_FILES" ]; then
|
|
569
|
-
BAD_FILTER=$(grep -Pn 'HasQueryFilter.*Guid\.Empty' $CONFIG_FILES 2>/dev/null)
|
|
570
|
-
if [ -n "$BAD_FILTER" ]; then
|
|
571
|
-
echo "BLOCKING: HasQueryFilter uses Guid.Empty — must use runtime tenant filter"
|
|
572
|
-
echo "$BAD_FILTER"
|
|
573
|
-
exit 1
|
|
574
|
-
fi
|
|
575
|
-
fi
|
|
576
|
-
|
|
577
|
-
# POST-CHECK: PaginatedResult<T> for GetAll
|
|
578
|
-
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
579
|
-
if [ -n "$SERVICE_FILES" ]; then
|
|
580
|
-
BAD_GETALL=$(grep -Pn 'Task<(List|IEnumerable|IList|ICollection)<' $SERVICE_FILES 2>/dev/null | grep -i "getall")
|
|
581
|
-
if [ -n "$BAD_GETALL" ]; then
|
|
582
|
-
echo "BLOCKING: GetAll methods must return PaginatedResult<T>, not List/IEnumerable"
|
|
583
|
-
echo "$BAD_GETALL"
|
|
584
|
-
exit 1
|
|
585
|
-
fi
|
|
586
|
-
fi
|
|
587
|
-
|
|
588
|
-
# POST-CHECK: i18n file structure — per-module JSON files MUST exist (BLOCKING)
|
|
589
|
-
WEB_DIR=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
|
|
590
|
-
if [ -n "$WEB_DIR" ]; then
|
|
591
|
-
I18N_DIR="$WEB_DIR/i18n/locales"
|
|
592
|
-
if [ -d "$I18N_DIR" ]; then
|
|
593
|
-
for LANG in fr en it de; do
|
|
594
|
-
LANG_DIR="$I18N_DIR/$LANG"
|
|
595
|
-
if [ ! -d "$LANG_DIR" ]; then
|
|
596
|
-
echo "BLOCKING: Missing i18n locale directory: $LANG_DIR"
|
|
597
|
-
echo "All 4 languages (fr, en, it, de) MUST have a dedicated directory"
|
|
598
|
-
exit 1
|
|
599
|
-
fi
|
|
600
|
-
# Check for at least one module-specific JSON (not just common.json)
|
|
601
|
-
MODULE_JSONS=$(find "$LANG_DIR" -name "*.json" ! -name "common.json" ! -name "navigation.json" 2>/dev/null)
|
|
602
|
-
if [ -z "$MODULE_JSONS" ]; then
|
|
603
|
-
echo "BLOCKING: No module translation files in $LANG_DIR/"
|
|
604
|
-
echo "Each module MUST have src/i18n/locales/$LANG/{moduleLower}.json"
|
|
605
|
-
exit 1
|
|
606
|
-
fi
|
|
607
|
-
done
|
|
608
|
-
else
|
|
609
|
-
echo "BLOCKING: Missing i18n/locales directory — i18n file structure not created"
|
|
610
|
-
echo "Expected: src/i18n/locales/{fr,en,it,de}/{module}.json"
|
|
611
|
-
exit 1
|
|
612
|
-
fi
|
|
613
|
-
fi
|
|
614
|
-
|
|
615
|
-
# POST-CHECK: i18n required key structure (BLOCKING — upgraded from WARNING)
|
|
616
|
-
if [ -d "$I18N_DIR/fr" ]; then
|
|
617
|
-
for f in $(find "$I18N_DIR/fr/" -name "*.json" 2>/dev/null); do
|
|
618
|
-
BASENAME=$(basename "$f")
|
|
619
|
-
case "$BASENAME" in common.json|navigation.json) continue;; esac
|
|
620
|
-
for KEY in "actions" "labels" "errors" "messages" "empty"; do
|
|
621
|
-
if ! grep -q "\"$KEY\"" "$f"; then
|
|
622
|
-
echo "BLOCKING: i18n file missing required key group '$KEY': $f"
|
|
623
|
-
echo "All module i18n files MUST contain: actions, labels, errors, messages, empty"
|
|
624
|
-
exit 1
|
|
625
|
-
fi
|
|
626
|
-
done
|
|
627
|
-
done
|
|
628
|
-
fi
|
|
629
|
-
|
|
630
|
-
# POST-CHECK: Pages must NOT contain hardcoded English UI text (BLOCKING)
|
|
631
|
-
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null)
|
|
632
|
-
if [ -n "$PAGE_FILES" ]; then
|
|
633
|
-
# Detect common hardcoded English patterns in JSX (excluding imports/comments/code)
|
|
634
|
-
HARDCODED_TEXT=$(grep -Pn '>\s*(Create|Edit|Delete|Save|Cancel|Search|Loading|Error|No .* found|Are you sure|Submit|Back|Actions|Status|Name|Description|Total)\s*<' $PAGE_FILES 2>/dev/null | grep -v '//' | grep -v 'test' | head -20)
|
|
635
|
-
if [ -n "$HARDCODED_TEXT" ]; then
|
|
636
|
-
echo "BLOCKING: Hardcoded English UI text found in page files"
|
|
637
|
-
echo "ALL visible text MUST use t() from useTranslation() — NEVER hardcode strings"
|
|
638
|
-
echo "Pattern: t('{module}:actions.create', 'Create') — namespace + fallback"
|
|
639
|
-
echo ""
|
|
640
|
-
echo "Found:"
|
|
641
|
-
echo "$HARDCODED_TEXT"
|
|
642
|
-
exit 1
|
|
643
|
-
fi
|
|
644
|
-
fi
|
|
645
|
-
|
|
646
|
-
# POST-CHECK: Hooks must NOT contain hardcoded error messages (BLOCKING)
|
|
647
|
-
HOOK_FILES=$(find "$HOOK_DIR" -name "*.ts" -o -name "*.tsx" 2>/dev/null)
|
|
648
|
-
if [ -n "$HOOK_FILES" ]; then
|
|
649
|
-
HARDCODED_ERRORS=$(grep -Pn "(setError|throw new Error)\(['\"](?!t\()[A-Z][a-z]+" $HOOK_FILES 2>/dev/null | head -15)
|
|
650
|
-
if [ -n "$HARDCODED_ERRORS" ]; then
|
|
651
|
-
echo "BLOCKING: Hardcoded error messages in hooks — MUST use i18n t() function"
|
|
652
|
-
echo "Hooks must import useTranslation and use t('{module}:errors.loadFailed') for all messages"
|
|
653
|
-
echo ""
|
|
654
|
-
echo "Found:"
|
|
655
|
-
echo "$HARDCODED_ERRORS"
|
|
656
|
-
exit 1
|
|
657
|
-
fi
|
|
658
|
-
fi
|
|
659
|
-
|
|
660
|
-
# POST-CHECK: SeedConstants must NOT contain ContextId (pre-seeded by SmartStack core)
|
|
661
|
-
SEED_CONST_FILES=$(find src/ -path "*/Seeding/*" -name "SeedConstants.cs" 2>/dev/null)
|
|
662
|
-
if [ -n "$SEED_CONST_FILES" ]; then
|
|
663
|
-
BAD_CTX=$(grep -Pn 'ContextId\s*=' $SEED_CONST_FILES 2>/dev/null)
|
|
664
|
-
if [ -n "$BAD_CTX" ]; then
|
|
665
|
-
echo "BLOCKING: SeedConstants must NOT contain a ContextId constant"
|
|
666
|
-
echo "Context IDs are pre-seeded by SmartStack core — look up by code at runtime"
|
|
667
|
-
echo "$BAD_CTX"
|
|
668
|
-
exit 1
|
|
669
|
-
fi
|
|
670
|
-
fi
|
|
671
|
-
SEED_ALL_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
|
|
672
|
-
if [ -n "$SEED_ALL_FILES" ]; then
|
|
673
|
-
BAD_CTX_GUID=$(grep -Pn 'DeterministicGuid\("nav:(business|platform|personal)"\)' $SEED_ALL_FILES 2>/dev/null)
|
|
674
|
-
if [ -n "$BAD_CTX_GUID" ]; then
|
|
675
|
-
echo "BLOCKING: Deterministic GUID for NavigationContext detected"
|
|
676
|
-
echo "Fix: Look up context by code at runtime in SeedDataProvider.SeedNavigationAsync()"
|
|
677
|
-
echo "$BAD_CTX_GUID"
|
|
678
|
-
exit 1
|
|
679
|
-
fi
|
|
680
|
-
fi
|
|
681
|
-
|
|
682
|
-
# POST-CHECK: RolePermission seed data must NOT use deterministic role GUIDs
|
|
683
|
-
SEED_ALL_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
|
|
684
|
-
SEED_CONST_FILES=$(find src/ -path "*/Seeding/*" -name "SeedConstants.cs" 2>/dev/null)
|
|
685
|
-
if [ -n "$SEED_ALL_FILES" ]; then
|
|
686
|
-
BAD_ROLE_GUID=$(grep -Pn 'DeterministicGuid\("role:' $SEED_ALL_FILES $SEED_CONST_FILES 2>/dev/null)
|
|
687
|
-
if [ -n "$BAD_ROLE_GUID" ]; then
|
|
688
|
-
echo "BLOCKING: Deterministic GUID for role detected"
|
|
689
|
-
echo "System roles are pre-seeded by SmartStack core — look up by Code at runtime"
|
|
690
|
-
echo "$BAD_ROLE_GUID"
|
|
691
|
-
exit 1
|
|
692
|
-
fi
|
|
693
|
-
fi
|
|
694
|
-
|
|
695
|
-
# POST-CHECK: Services must NOT use TenantId!.Value (crashes with 500 instead of 401)
|
|
696
|
-
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
697
|
-
if [ -n "$SERVICE_FILES" ]; then
|
|
698
|
-
BAD_PATTERN=$(grep -Pn 'TenantId!\s*\.Value|TenantId!\s*\.ToString|\.TenantId!' $SERVICE_FILES 2>/dev/null)
|
|
699
|
-
if [ -n "$BAD_PATTERN" ]; then
|
|
700
|
-
echo "BLOCKING: TenantId!.Value causes 500 when tenant context is missing"
|
|
701
|
-
echo "Fix: var tenantId = _currentTenant.TenantId ?? throw new TenantContextRequiredException();"
|
|
702
|
-
echo "NEVER use UnauthorizedAccessException for tenant context — it returns 401 which clears the frontend token."
|
|
703
|
-
echo "$BAD_PATTERN"
|
|
704
|
-
exit 1
|
|
705
|
-
fi
|
|
706
|
-
fi
|
|
707
|
-
|
|
708
|
-
# POST-CHECK: Services must NOT use UnauthorizedAccessException for tenant context (causes token clearing)
|
|
709
|
-
if [ -n "$SERVICE_FILES" ]; then
|
|
710
|
-
BAD_UNAUTH=$(grep -Pn 'UnauthorizedAccessException.*[Tt]enant' $SERVICE_FILES 2>/dev/null)
|
|
711
|
-
if [ -n "$BAD_UNAUTH" ]; then
|
|
712
|
-
echo "BLOCKING: Services use UnauthorizedAccessException for tenant context — causes 401 which clears the frontend token"
|
|
713
|
-
echo "$BAD_UNAUTH"
|
|
714
|
-
echo "Fix: var tenantId = _currentTenant.TenantId ?? throw new TenantContextRequiredException();"
|
|
715
|
-
exit 1
|
|
716
|
-
fi
|
|
717
|
-
fi
|
|
718
|
-
|
|
719
|
-
# POST-CHECK: IAuditableEntity + Validator pairing
|
|
720
|
-
ENTITY_FILES=$(find src/ -path "*/Domain/Entities/Business/*" -name "*.cs" 2>/dev/null)
|
|
721
|
-
if [ -n "$ENTITY_FILES" ]; then
|
|
722
|
-
for f in $ENTITY_FILES; do
|
|
723
|
-
if grep -q "ITenantEntity" "$f" && ! grep -q "IAuditableEntity" "$f"; then
|
|
724
|
-
echo "WARNING: Entity has ITenantEntity but missing IAuditableEntity: $f"
|
|
725
|
-
fi
|
|
726
|
-
done
|
|
727
|
-
fi
|
|
728
|
-
CREATE_VALIDATORS=$(find src/ -path "*/Validators/*" -name "Create*Validator.cs" 2>/dev/null)
|
|
729
|
-
if [ -n "$CREATE_VALIDATORS" ]; then
|
|
730
|
-
for CV in $CREATE_VALIDATORS; do
|
|
731
|
-
UV=$(echo "$CV" | sed 's/Create/Update/')
|
|
732
|
-
if [ ! -f "$UV" ]; then
|
|
733
|
-
echo "BLOCKING: CreateValidator without matching UpdateValidator: $CV"
|
|
734
|
-
echo "Expected: $UV"
|
|
735
|
-
exit 1
|
|
736
|
-
fi
|
|
737
|
-
done
|
|
738
|
-
fi
|
|
133
|
+
console.log(`Apex completed: ${completed}/${moduleTasks.length} tasks`);
|
|
739
134
|
```
|
|
740
135
|
|
|
741
|
-
|
|
742
|
-
1. Read the FULL error output (not just first line)
|
|
743
|
-
2. Identify root cause: file path + line number
|
|
744
|
-
3. Fix the issue in source code
|
|
745
|
-
4. Rebuild: `dotnet build --no-restore` or `npm run typecheck`
|
|
746
|
-
5. If still failing → repeat from step 1
|
|
747
|
-
6. **NEVER** mark task completed with failing build or tests
|
|
748
|
-
7. **NEVER** skip the verification — it is BLOCKING
|
|
749
|
-
|
|
750
|
-
### 9. Update Task State
|
|
136
|
+
### 5. Update Progress
|
|
751
137
|
|
|
752
138
|
```javascript
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
prd.
|
|
757
|
-
|
|
139
|
+
// Append to progress file
|
|
140
|
+
const progressEntry = `
|
|
141
|
+
[Iteration ${prd.config.current_iteration}] Delegated to /apex
|
|
142
|
+
Module: ${prd.project.module}
|
|
143
|
+
Completed: ${completed}/${moduleTasks.length}
|
|
144
|
+
Failed: ${failed}
|
|
145
|
+
Pending: ${pending}
|
|
146
|
+
`;
|
|
147
|
+
appendFile('.ralph/progress.txt', progressEntry);
|
|
758
148
|
```
|
|
759
149
|
|
|
760
|
-
Note: `task.status` stays `"in_progress"` until step-03 confirms commit. Only `"failed"` is set here.
|
|
761
|
-
|
|
762
150
|
---
|
|
763
151
|
|
|
764
152
|
## CONSTRAINTS:
|
|
765
153
|
|
|
766
|
-
**DO:**
|
|
767
|
-
**DON'T:**
|
|
154
|
+
- **DO:** Delegate to apex, verify results, maintain PRD state
|
|
155
|
+
- **DON'T:** Generate code directly, run inline POST-CHECKs, invoke MCP tools for generation
|
|
156
|
+
- **DON'T:** Stop to ask the user anything — fully autonomous
|
|
768
157
|
|
|
769
158
|
---
|
|
770
159
|
|
|
771
160
|
## NEXT STEP:
|
|
772
161
|
|
|
773
|
-
|
|
162
|
+
Proceed to `./step-03-commit.md`
|