@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.
Files changed (65) hide show
  1. package/.documentation/agents.html +5 -1
  2. package/.documentation/apex.html +644 -0
  3. package/.documentation/business-analyse.html +81 -1
  4. package/.documentation/cli-commands.html +5 -1
  5. package/.documentation/commands.html +5 -1
  6. package/.documentation/efcore.html +5 -1
  7. package/.documentation/gitflow.html +5 -1
  8. package/.documentation/hooks.html +5 -1
  9. package/.documentation/index.html +60 -2
  10. package/.documentation/init.html +414 -1
  11. package/.documentation/installation.html +5 -1
  12. package/.documentation/ralph-loop.html +365 -216
  13. package/.documentation/test-web.html +5 -1
  14. package/dist/index.js +32 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/mcp-entry.mjs +7 -24
  17. package/dist/mcp-entry.mjs.map +1 -1
  18. package/package.json +1 -2
  19. package/templates/agents/ba-writer.md +142 -15
  20. package/templates/mcp-scaffolding/controller.cs.hbs +5 -1
  21. package/templates/skills/apex/SKILL.md +9 -3
  22. package/templates/skills/apex/_shared.md +49 -4
  23. package/templates/skills/{ralph-loop → apex}/references/core-seed-data.md +20 -11
  24. package/templates/skills/{ralph-loop → apex}/references/error-classification.md +2 -1
  25. package/templates/skills/apex/references/post-checks.md +463 -3
  26. package/templates/skills/apex/references/smartstack-api.md +76 -8
  27. package/templates/skills/apex/references/smartstack-frontend.md +74 -1
  28. package/templates/skills/apex/references/smartstack-layers.md +21 -3
  29. package/templates/skills/apex/steps/step-00-init.md +121 -1
  30. package/templates/skills/apex/steps/step-01-analyze.md +58 -0
  31. package/templates/skills/apex/steps/step-02-plan.md +36 -0
  32. package/templates/skills/apex/steps/step-03-execute.md +114 -7
  33. package/templates/skills/apex/steps/step-04-examine.md +116 -2
  34. package/templates/skills/business-analyse/SKILL.md +31 -20
  35. package/templates/skills/business-analyse/_module-loop.md +68 -9
  36. package/templates/skills/business-analyse/_shared.md +80 -21
  37. package/templates/skills/business-analyse/questionnaire/00-application.md +4 -2
  38. package/templates/skills/business-analyse/questionnaire/00b-project.md +85 -0
  39. package/templates/skills/business-analyse/references/deploy-modes.md +69 -0
  40. package/templates/skills/business-analyse/references/team-orchestration.md +158 -7
  41. package/templates/skills/business-analyse/schemas/application-schema.json +15 -1
  42. package/templates/skills/business-analyse/schemas/project-schema.json +490 -0
  43. package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +2 -1
  44. package/templates/skills/business-analyse/steps/step-00-init.md +220 -38
  45. package/templates/skills/business-analyse/steps/step-01-cadrage.md +184 -5
  46. package/templates/skills/business-analyse/steps/step-01b-applications.md +423 -0
  47. package/templates/skills/business-analyse/steps/step-02-decomposition.md +23 -6
  48. package/templates/skills/business-analyse/steps/step-03c-compile.md +14 -2
  49. package/templates/skills/business-analyse/steps/step-03d-validate.md +32 -7
  50. package/templates/skills/business-analyse/steps/step-04a-collect.md +111 -0
  51. package/templates/skills/business-analyse/steps/step-05a-handoff.md +296 -103
  52. package/templates/skills/business-analyse/steps/step-05b-deploy.md +46 -14
  53. package/templates/skills/documentation/SKILL.md +92 -2
  54. package/templates/skills/ralph-loop/SKILL.md +14 -17
  55. package/templates/skills/ralph-loop/references/category-rules.md +63 -683
  56. package/templates/skills/ralph-loop/references/compact-loop.md +188 -428
  57. package/templates/skills/ralph-loop/references/section-splitting.md +439 -0
  58. package/templates/skills/ralph-loop/references/team-orchestration.md +13 -14
  59. package/templates/skills/ralph-loop/steps/step-01-task.md +27 -0
  60. package/templates/skills/ralph-loop/steps/step-02-execute.md +80 -691
  61. package/templates/skills/ralph-loop/steps/step-03-commit.md +38 -79
  62. package/templates/skills/ralph-loop/steps/step-04-check.md +39 -58
  63. package/templates/skills/ralph-loop/steps/step-05-report.md +31 -123
  64. package/scripts/health-check.sh +0 -168
  65. package/scripts/postinstall.js +0 -18
@@ -1,7 +1,7 @@
1
- # Compact Loop — Inline Execution
1
+ # Compact Loop — Delegate to /apex
2
2
 
3
3
  > **Loaded by:** step-04 section 5 (after first full iteration)
4
- > **Purpose:** Execute ALL remaining tasks autonomously without stopping.
4
+ > **Purpose:** Execute ALL remaining tasks autonomously by delegating to /apex.
5
5
  > **EXECUTION GUARANTEE:** This loop runs until ALL tasks are done, max iterations, or dead-end.
6
6
  > **ABSOLUTE RULES:**
7
7
  > - NEVER stop the loop
@@ -9,6 +9,7 @@
9
9
  > - NEVER ask for confirmation
10
10
  > - NEVER re-read step files
11
11
  > - NEVER pause between iterations
12
+ > - NEVER generate code directly — delegate to /apex
12
13
  > - Execute A → B → C → D → back to step-04 section 1 → repeat
13
14
 
14
15
  ---
@@ -22,12 +23,122 @@ Display compact progress:
22
23
 
23
24
  ---
24
25
 
25
- ## A. Find Eligible Tasks
26
+ ## A0. Section-Split Mode (checked BEFORE standard batching)
26
27
 
27
28
  ```javascript
28
29
  const prd = readJSON('.ralph/prd.json');
29
30
 
30
- // Block tasks whose dependencies failed
31
+ if (prd._sectionSplit?.enabled) {
32
+ // SECTION-SPLIT: Delegate per-phase instead of per-category batch
33
+
34
+ // Find next pending phase with dependencies met
35
+ const nextPhase = prd._sectionSplit.phases.find(p => p.status === 'pending');
36
+
37
+ if (!nextPhase) {
38
+ // All phases done — check if master PRD tasks are all complete
39
+ goto CHECK_COMPLETION;
40
+ }
41
+
42
+ const depsOk = nextPhase.dependsOn.every(depIdx =>
43
+ prd._sectionSplit.phases[depIdx].status === 'completed'
44
+ );
45
+
46
+ if (!depsOk) {
47
+ // Phase blocked — should not happen with topological sort
48
+ console.warn(`Phase ${nextPhase.phase} blocked — dependencies incomplete`);
49
+ goto CHECK_COMPLETION;
50
+ }
51
+
52
+ const phaseLabel = nextPhase.type === 'foundation'
53
+ ? `Phase 0: Foundation (${nextPhase.entities.length} entities + migration)`
54
+ : `Phase ${nextPhase.phase}: Section "${nextPhase.sectionCode}" (${nextPhase.entities.length} entities)`;
55
+ console.log(`[{iteration}/{max}] SECTION SPLIT: ${phaseLabel}`);
56
+
57
+ // INVOKE /apex -d {nextPhase.prdFile}
58
+ // Apex sees a normal (smaller) PRD and executes normally
59
+
60
+ // After apex returns: merge results back into master PRD
61
+ // Read references/section-splitting.md sections 7-9
62
+ const phasePrd = readJSON(nextPhase.prdFile);
63
+ for (const phaseTask of phasePrd.tasks) {
64
+ const masterTask = prd.tasks.find(t => t.id === phaseTask.id);
65
+ if (masterTask) {
66
+ masterTask.status = phaseTask.status;
67
+ masterTask.completed_at = phaseTask.completed_at;
68
+ masterTask.commit_hash = phaseTask.commit_hash;
69
+ masterTask.files_changed = phaseTask.files_changed;
70
+ masterTask.error = phaseTask.error;
71
+ masterTask.validation = phaseTask.validation;
72
+ }
73
+ }
74
+
75
+ // Handle phase failure
76
+ if (phasePrd.tasks.some(t => t.status === 'failed')) {
77
+ const retryCount = nextPhase._retryCount || 0;
78
+ if (retryCount < 2) {
79
+ nextPhase.status = 'pending';
80
+ nextPhase._retryCount = retryCount + 1;
81
+ console.log(`Phase ${nextPhase.phase} had failures — retry ${retryCount + 1}/2`);
82
+ // Reset failed tasks in phase PRD for retry
83
+ for (const task of phasePrd.tasks) {
84
+ if (task.status === 'failed') { task.status = 'pending'; task.error = null; }
85
+ }
86
+ writeJSON(nextPhase.prdFile, phasePrd);
87
+ } else {
88
+ nextPhase.status = 'failed';
89
+ console.error(`Phase ${nextPhase.phase} failed after 2 retries`);
90
+ }
91
+ } else {
92
+ nextPhase.status = 'completed';
93
+ }
94
+
95
+ prd._sectionSplit.currentPhase = nextPhase.phase;
96
+ writeJSON('.ralph/prd.json', prd);
97
+
98
+ // → Skip to section C (commit PRD state), then D (loop back)
99
+ goto COMMIT_PRD;
100
+ }
101
+ ```
102
+
103
+ > **IMPORTANT:** When `_sectionSplit.enabled`, the standard category-based batching (section A below)
104
+ > is SKIPPED. The loop delegates per-phase to apex instead of per-category-batch.
105
+
106
+ ---
107
+
108
+ ## A. Find Eligible Tasks
109
+
110
+ > **Note:** This section is SKIPPED when `prd._sectionSplit?.enabled` (handled by A0 above).
111
+
112
+ ```javascript
113
+ const MAX_RETRIES = 3;
114
+
115
+ // RETRY: Reset failed tasks to pending if retries remain (max 3 attempts per task)
116
+ for (const task of prd.tasks) {
117
+ if (task.status !== 'failed') continue;
118
+ const retryCount = task._retryCount || 0;
119
+ if (retryCount < MAX_RETRIES) {
120
+ task.status = 'pending';
121
+ task._retryCount = retryCount + 1;
122
+ task.error = null;
123
+ console.log(`RETRY [${task.id}] attempt ${retryCount + 1}/${MAX_RETRIES}: ${task.description.substring(0, 60)}`);
124
+ }
125
+ }
126
+
127
+ // Unblock tasks whose dependencies are no longer failed (due to retries above)
128
+ for (const task of prd.tasks) {
129
+ if (task.status !== 'blocked') continue;
130
+ const stillBlocked = task.dependencies.some(depId => {
131
+ const dep = prd.tasks.find(t => t.id === depId);
132
+ return dep && (dep.status === 'failed' || dep.status === 'blocked');
133
+ });
134
+ if (!stillBlocked) {
135
+ task.status = 'pending';
136
+ task.error = null;
137
+ console.log(`UNBLOCKED [${task.id}]: dependencies resolved`);
138
+ }
139
+ }
140
+
141
+ // Block pending tasks whose dependencies are still failed/blocked
31
142
  for (const task of prd.tasks) {
32
143
  if (task.status !== 'pending') continue;
33
144
  const depsBlocked = task.dependencies.some(depId => {
@@ -37,6 +148,8 @@ for (const task of prd.tasks) {
37
148
  if (depsBlocked) { task.status = 'blocked'; task.error = 'Blocked by failed dependency'; }
38
149
  }
39
150
 
151
+ writeJSON('.ralph/prd.json', prd);
152
+
40
153
  // Find ALL eligible tasks (dependencies met)
41
154
  const eligible = prd.tasks.filter(task => {
42
155
  if (task.status !== 'pending') return false;
@@ -63,463 +176,110 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
63
176
 
64
177
  ---
65
178
 
66
- ## B. Execute Batch
67
-
68
- **For EACH task in batch:**
69
-
70
- 1. Mark `task.status = 'in_progress'`, `task.started_at = now`
71
- 2. **Read `references/category-rules.md`** for category-specific rules
72
- 2bis. **For frontend tasks:** Also read back the original **feature.json** (via `prd.source.featurePath`) to access wireframe `componentMapping`, `columnDefs`, `layout`, `emptyState`, `rowActions`, and `i18n` keys that the PRD does NOT carry forward. The PRD only has the file list — the behavioral spec is in feature.json.
73
- 3. Implement the task following SmartStack conventions
74
- 4. Track `files_created` and `files_modified`
75
-
76
- **Execution cycle per task:**
77
- 1. Generate code following category-rules.md
78
- 2. **BUILD verification (BLOCKING per task):**
79
- - Backend (domain/infra/app/api): `dotnet build --no-restore` → MUST exit 0
80
- - Frontend: `npm run typecheck` → MUST exit 0
81
- 3. If build fails → **CLASSIFY ERROR FIRST** (read `references/error-classification.md`):
82
- - **Category A/B (assembly/package):** Run `dotnet add package {Name}` → `dotnet restore` → rebuild. Do NOT edit source code.
83
- - **Category C (DI):** Edit DI registration file only → rebuild.
84
- - **Category D (migration):** Run migration commands → rebuild.
85
- - **Category E (config):** Edit config file → rebuild.
86
- - **Category F (code):** Fix source code → rebuild → loop until pass.
87
- - **If 2+ rebuild attempts fail on same error and Category F was chosen:** Re-classify — likely wrong category.
88
- 4. **ARTIFACT verification (BLOCKING per task):**
89
- - `files_created` MUST NOT be empty for code-generating tasks
90
- - Category-specific checks (see below)
91
- - IF no artifacts produced → task.status = 'failed', task.error = 'No artifacts created'
92
- 5. Verify acceptance criteria from task description
93
- 6. If failed after 3 attempts: `task.status = 'failed'`, `task.error = reason`, continue to next in batch
94
-
95
- **Category-specific artifact checks (step 4 above):**
96
-
97
- | Category | Artifact Check | FAIL if |
98
- |----------|---------------|---------|
99
- | `domain` | Entity .cs files exist in Domain/ | 0 files created |
100
- | `infrastructure` with migration | `Migrations/` folder has .cs files | No migration files |
101
- | `infrastructure` with seed data | Seed data .cs files exist | 0 files created |
102
- | `application` | Service + DTO + Validator files exist | 0 files created |
103
- | `api` | Controller .cs files exist + GetAll supports `?search=` | 0 files created OR GetAll without search param |
104
- | `frontend` | .tsx page files exist + FK fields use EntityLookup (not plain text) | 0 files created OR FK Guid field as `<input type="text">` |
105
- | `test` | Test project dir exists AND contains test .cs files | No test project or 0 test files |
106
- | `validation` | `dotnet test` exit 0 AND `dotnet build` exit 0 | Either command fails |
107
-
108
- **Category-specific triggers:**
109
-
110
- | Category | Action |
111
- |----------|--------|
112
- | `infrastructure` with `_migrationMeta` | Migration sequence: `suggest_migration` MCP → `dotnet ef migrations add` → cleanup corrupted artifacts (`for d in src/*/bin?Debug; do [ -d "$d" ] && rm -rf "$d"; done`) → `dotnet ef database update` → `dotnet build` |
113
- | `infrastructure` with seed data keywords | **MANDATORY:** Read `references/core-seed-data.md` → implement templates → `dotnet build` |
114
- | `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) → **wire to App.tsx** → **LOAD `/ui-components` skill** before creating ANY page → create pages using skill patterns (SmartTable, EntityCard, CSS variables, StatusBadge) → ALL visible text via `t()` from first line → `npm run typecheck && npm run lint` → **run frontend POST-CHECKs (section 3bis below)** |
115
- | `test` | **Create tests:** `scaffold_tests` MCP → **Run:** `dotnet test --verbosity normal` → **Fix loop:** if fail → fix SOURCE CODE → rebuild → retest → repeat until 100% pass |
116
- | `validation` | `dotnet clean && dotnet restore && dotnet build` → `dotnet test` (full) → `validate_conventions` MCP |
117
-
118
- **Post-batch verification (BLOCKING — ALL must pass before commit):**
119
-
120
- 1. **Backend build:**
121
- ```bash
122
- dotnet build --no-restore
123
- ```
124
- If FAIL → fix → rebuild → DO NOT commit until exit 0
125
-
126
- 2. **Tests (if test tasks in batch OR after infrastructure batch):**
127
- ```bash
128
- TEST_PROJECT="tests/$(basename $(pwd)).Tests.Unit"
129
- dotnet test "$TEST_PROJECT" --no-build --verbosity normal
130
- ```
131
- If FAIL → fix source code (NOT tests) → rebuild → retest → loop until 100% pass
132
-
133
- 2bis. **Runtime assembly check (if api/validation tasks in batch):**
134
- ```bash
135
- API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
136
- if [ -n "$API_PROJECT" ]; then
137
- dotnet run --project "$API_PROJECT" --urls "http://localhost:5098" > /tmp/ralph-startup-check.log 2>&1 &
138
- CHECK_PID=$!
139
- sleep 5
140
- if ! kill -0 $CHECK_PID 2>/dev/null; then
141
- CRASH_OUTPUT=$(cat /tmp/ralph-startup-check.log 2>/dev/null)
142
- echo "RUNTIME ASSEMBLY ERROR DETECTED"
143
- # Classify per references/error-classification.md
144
- # If Category A: dotnet add package → rebuild → DO NOT count as iteration failure
145
- fi
146
- kill $CHECK_PID 2>/dev/null
147
- wait $CHECK_PID 2>/dev/null
148
- rm -f /tmp/ralph-startup-check.log
149
- fi
150
- ```
151
- If FAIL → classify error per `references/error-classification.md` → apply fix → rebuild → verify again
152
-
153
- 2ter. **DI completeness (if application tasks in batch):**
154
- Verify `Application/DependencyInjection.cs` registers all validators and services:
155
- - Count validators in `Validators/` folder
156
- - Verify `AddValidatorsFromAssemblyContaining` is present
157
- - If empty/TODO → FAIL → fix DI registration → rebuild
158
-
159
- 3. **Frontend (if frontend/i18n tasks in batch):**
160
- ```bash
161
- npm run typecheck && npm run lint
162
- ```
163
- If FAIL → fix → re-check → loop until pass
164
-
165
- 3bis. **Frontend POST-CHECKs (BLOCKING — if frontend tasks in batch):**
166
-
167
- > **CRITICAL:** These checks enforce /ui-components compliance, i18n, and route conventions.
168
- > Without them, generated code silently regresses to raw HTML tables, hardcoded colors, and English-only text.
169
-
170
- ```bash
171
- # Resolve paths dynamically (handles web/ prefix)
172
- PAGE_DIR=$(find . -path "*/src/pages" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
173
- HOOK_DIR=$(find . -path "*/src/hooks" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
174
- COMP_DIR=$(find . -path "*/src/components" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
175
- WEB_SRC=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
176
-
177
- if [ -n "$PAGE_DIR" ]; then
178
- # BLOCKING: No raw HTML <table> — MUST use SmartTable/DataTable
179
- RAW_TABLES=$(grep -rPn '<table[\s>]|<thead[\s>]|<tbody[\s>]' "$PAGE_DIR" --include="*.tsx" 2>/dev/null)
180
- if [ -n "$RAW_TABLES" ]; then
181
- echo "BLOCKING: Raw HTML <table> detected — MUST use SmartTable or DataTable"
182
- echo "$RAW_TABLES" | head -5
183
- # FIX REQUIRED before commit
184
- fi
185
-
186
- # BLOCKING: No hardcoded Tailwind colors — MUST use CSS variables
187
- HARDCODED_COLORS=$(grep -rPn '(?:bg|text|border|ring)-(?: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}' "$PAGE_DIR" "$COMP_DIR" --include="*.tsx" 2>/dev/null | head -10)
188
- if [ -n "$HARDCODED_COLORS" ]; then
189
- echo "BLOCKING: Hardcoded Tailwind colors — MUST use CSS variables (bg-[var(--bg-secondary)])"
190
- echo "$HARDCODED_COLORS" | head -5
191
- # FIX REQUIRED before commit
192
- fi
193
-
194
- # BLOCKING: No window.confirm() — use ConfirmDialog component
195
- BAD_CONFIRM=$(grep -rPn 'window\.confirm\s*\(' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | grep -v '//')
196
- if [ -n "$BAD_CONFIRM" ]; then
197
- echo "BLOCKING: window.confirm() detected — use ConfirmDialog component with i18n"
198
- echo "$BAD_CONFIRM"
199
- # FIX REQUIRED before commit
200
- fi
201
-
202
- # BLOCKING: No hardcoded English UI text in JSX
203
- HARDCODED_TEXT=$(grep -rPn '>\s*(Create|Edit|Delete|Save|Cancel|Search|Loading|Error|No .* found|Are you sure|Submit|Back|Actions|Status)\s*<' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | grep -v test | grep -v '//' | head -10)
204
- if [ -n "$HARDCODED_TEXT" ]; then
205
- echo "BLOCKING: Hardcoded English text — ALL visible text MUST use t() from useTranslation()"
206
- echo "$HARDCODED_TEXT" | head -5
207
- # FIX REQUIRED before commit
208
- fi
209
-
210
- # BLOCKING: No modals/drawers for forms — forms are full pages
211
- MODAL_IMPORTS=$(grep -rPn "import.*\b(Modal|Dialog|Drawer|Popup|Sheet)\b" "$PAGE_DIR" --include="*.tsx" 2>/dev/null)
212
- if [ -n "$MODAL_IMPORTS" ]; then
213
- echo "BLOCKING: Form modals/dialogs detected — forms MUST be full pages with own URL"
214
- echo "$MODAL_IMPORTS"
215
- # FIX REQUIRED before commit
216
- fi
217
- fi
218
-
219
- if [ -n "$HOOK_DIR" ]; then
220
- # BLOCKING: Hooks must use i18n for error messages
221
- HARDCODED_ERRORS=$(grep -rPn "(setError|throw new Error)\(['\"][A-Z]" "$HOOK_DIR" --include="*.ts" --include="*.tsx" 2>/dev/null | grep -v "t(" | head -10)
222
- if [ -n "$HARDCODED_ERRORS" ]; then
223
- echo "BLOCKING: Hardcoded error messages in hooks — MUST use t('{mod}:errors.xxx')"
224
- echo "$HARDCODED_ERRORS" | head -5
225
- # FIX REQUIRED before commit
226
- fi
227
- fi
228
-
229
- # BLOCKING: i18n file structure — 4 languages with module-specific JSON files
230
- if [ -n "$WEB_SRC" ]; then
231
- I18N_DIR="$WEB_SRC/i18n/locales"
232
- if [ -d "$I18N_DIR" ]; then
233
- for LANG in fr en it de; do
234
- if [ ! -d "$I18N_DIR/$LANG" ]; then
235
- echo "BLOCKING: Missing i18n locale directory: $I18N_DIR/$LANG"
236
- # FIX REQUIRED before commit
237
- fi
238
- MODULE_JSONS=$(find "$I18N_DIR/$LANG" -name "*.json" ! -name "common.json" ! -name "navigation.json" 2>/dev/null)
239
- if [ -z "$MODULE_JSONS" ]; then
240
- echo "BLOCKING: No module translation files in $I18N_DIR/$LANG/"
241
- # FIX REQUIRED before commit
242
- fi
243
- done
244
- else
245
- echo "BLOCKING: Missing i18n/locales directory"
246
- # FIX REQUIRED before commit
247
- fi
248
- fi
249
-
250
- # Cross-validate route conventions (frontend vs backend)
251
- APP_TSX=$(find . -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
252
- API_CONTROLLERS=$(find . -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
253
- if [ -n "$APP_TSX" ] && [ -n "$API_CONTROLLERS" ]; then
254
- API_SEGMENTS=$(grep -ohP '\[Route\("api/([^"]+)"\)\]' $API_CONTROLLERS | sed 's/\[Route("api\///;s/")\]//' | tr '/' '\n' | sort -u)
255
- CLIENT_PATHS=$(grep -ohP "path:\s*['\"]([^'\"]+)['\"]" "$APP_TSX" | sed "s/path:\s*['\"]//;s/['\"]//" | sort -u)
256
- for SEG in $API_SEGMENTS; do
257
- if echo "$SEG" | grep -qP '[a-z]+-[a-z]+'; then
258
- NO_HYPHEN=$(echo "$SEG" | tr -d '-')
259
- if echo "$CLIENT_PATHS" | grep -q "$NO_HYPHEN"; then
260
- echo "BLOCKING: Frontend uses '$NO_HYPHEN' but backend uses '$SEG' (kebab-case mismatch)"
261
- # FIX REQUIRED before commit
262
- fi
263
- fi
264
- done
265
- fi
266
- ```
267
- # BLOCKING: No custom entity hooks wrapping services (test-v4-014 root cause)
268
- if [ -n "$HOOK_DIR" ]; then
269
- CUSTOM_HOOKS=$(find "$HOOK_DIR" -name "use*.ts" -o -name "use*.tsx" 2>/dev/null | grep -iv "Preferences" | grep -v "node_modules")
270
- if [ -n "$CUSTOM_HOOKS" ]; then
271
- echo "BLOCKING: Custom entity hooks detected — pages MUST call services directly"
272
- echo "$CUSTOM_HOOKS"
273
- echo "Pattern: page useCallback → service.getAll() → setData(result.items)"
274
- echo "FORBIDDEN: useEmployees(), useProjects() etc. wrapping services with useState/useEffect"
275
- # FIX REQUIRED: delete custom hooks, move API calls into page components
276
- fi
277
- fi
278
-
279
- # BLOCKING: No direct array types in services — MUST use PaginatedResult<T>
280
- if [ -n "$WEB_SRC" ]; then
281
- SVC_DIR=$(find "$WEB_SRC" -path "*/services" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
282
- if [ -n "$SVC_DIR" ]; then
283
- DIRECT_ARRAYS=$(grep -rPn 'api\.get<[A-Z]\w+\[\]>' "$SVC_DIR" --include="*.ts" 2>/dev/null)
284
- if [ -n "$DIRECT_ARRAYS" ]; then
285
- echo "BLOCKING: Direct array response types — MUST use PaginatedResult<T>"
286
- echo "$DIRECT_ARRAYS"
287
- echo "Pattern: api.get<PaginatedResult<Employee>>(url) then .items in page"
288
- # FIX REQUIRED before commit
289
- fi
290
- fi
291
- fi
292
-
293
- # BLOCKING: lazy() imports MUST have Suspense boundary in App.tsx
294
- if [ -n "$APP_TSX" ]; then
295
- HAS_LAZY=$(grep -c 'lazy(' "$APP_TSX" 2>/dev/null)
296
- HAS_SUSPENSE=$(grep -c 'Suspense' "$APP_TSX" 2>/dev/null)
297
- if [ "$HAS_LAZY" -gt 0 ] && [ "$HAS_SUSPENSE" -eq 0 ]; then
298
- echo "BLOCKING: lazy() imports without <Suspense> boundary in App.tsx"
299
- echo "Every lazy() page MUST be wrapped: <Suspense fallback={<PageLoader />}>"
300
- # FIX REQUIRED before commit
301
- fi
302
- fi
303
-
304
- If ANY POST-CHECK fails → fix the code → re-run ALL checks → loop until ALL pass.
305
- **NEVER commit with failing POST-CHECKs.**
306
-
307
- 3ter. **Dependency audit (if frontend tasks in batch):**
308
- ```bash
309
- cd {frontend_dir}
310
- npm ls --depth=0 2>&1 | grep "MISSING" && echo "FAIL: missing deps" && exit 1
311
- ```
312
- If FAIL → `npm install {missing_packages}` → re-check → loop until pass
313
-
314
- 4. **MCP validation:**
315
- `mcp__smartstack__validate_conventions` ONCE for the whole batch
316
-
317
- 5. **DB validation (if infrastructure/migration tasks in batch):**
318
- ```bash
319
- # Quick check: pending model changes after batch?
320
- INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)
321
- API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
322
- if [ -n "$INFRA_PROJECT" ] && [ -n "$API_PROJECT" ]; then
323
- dotnet ef migrations has-pending-model-changes \
324
- --project "$INFRA_PROJECT" \
325
- --startup-project "$API_PROJECT"
326
- if [ $? -ne 0 ]; then
327
- echo "MIGRATION MISSING: Model has uncommitted changes"
328
- # Create fix task for missing migration
329
- fi
330
-
331
- # If batch included migration tasks: verify migration applies on real SQL Server
332
- DB_NAME="SmartStack_Ralph_Check_${iteration}"
333
- CONN_STRING="Server=(localdb)\\MSSQLLocalDB;Database=$DB_NAME;Integrated Security=true;TrustServerCertificate=true;Connect Timeout=120;"
334
- dotnet ef database update \
335
- --connection "$CONN_STRING" \
336
- --project "$INFRA_PROJECT" \
337
- --startup-project "$API_PROJECT"
338
- if [ $? -ne 0 ]; then
339
- echo "MIGRATION BROKEN: Cannot apply migrations on SQL Server"
340
- # Fix migration → rebuild → DO NOT commit until this passes
341
- fi
342
-
343
- # Cleanup temp database
344
- 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
345
- fi
346
- ```
347
- If FAIL → migration is broken → fix → rebuild → DO NOT commit
348
-
349
- 5bis. **Navigation section route CRUD suffix check (BLOCKING — if seed data tasks in batch):**
350
- ```bash
351
- # Detect /list or /detail/:id in navigation section routes
352
- SEED_NAV_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" 2>/dev/null)
353
- if [ -n "$SEED_NAV_FILES" ]; then
354
- CRUD_ROUTES=$(grep -rn 'Route.*=.*\/list\b\|Route.*=.*\/detail' $SEED_NAV_FILES 2>/dev/null | grep -v '//.*Route')
355
- if [ -n "$CRUD_ROUTES" ]; then
356
- echo "BLOCKING: Navigation section routes contain /list or /detail/:id suffixes"
357
- echo "Convention: list → module route (no suffix), detail → module route + /:id"
358
- echo "$CRUD_ROUTES"
359
- # FIX REQUIRED before commit
360
- fi
361
- fi
362
- ```
363
-
364
- 5ter. **Core Seed Data Integrity (BLOCKING — if seed data tasks in batch):**
365
- ```bash
366
- # Verify NavigationApplicationSeedData.cs exists
367
- APP_SEED=$(find . -path "*/Seeding/Data/NavigationApplicationSeedData.cs" 2>/dev/null | head -1)
368
- if [ -z "$APP_SEED" ]; then
369
- echo "❌ BLOCKING: NavigationApplicationSeedData.cs NOT FOUND"
370
- echo "Without this: nav_Applications empty → menu invisible → modules inaccessible"
371
- echo "Fix: Generate from core-seed-data.md section 1b"
372
- # Create fix task
373
- fi
374
-
375
- # Verify no hardcoded placeholders in provider
376
- PROVIDER=$(find . -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
377
- if [ -n "$PROVIDER" ]; then
378
- if grep -qE '\{appLabel_|\{appDesc_|\{appIcon\}|\{ApplicationGuid\}' "$PROVIDER" 2>/dev/null; then
379
- echo "❌ BLOCKING: SeedDataProvider has unresolved placeholders"
380
- echo "Fix: Use NavigationApplicationSeedData.GetApplicationEntry()"
381
- fi
382
- fi
383
-
384
- # Verify ApplicationRolesSeedData references real GUID
385
- ROLES_SEED=$(find . -path "*/Seeding/Data/ApplicationRolesSeedData.cs" 2>/dev/null | head -1)
386
- if [ -n "$ROLES_SEED" ]; then
387
- if grep -q '{ApplicationGuid}' "$ROLES_SEED" 2>/dev/null; then
388
- echo "❌ BLOCKING: ApplicationRolesSeedData still has {ApplicationGuid} placeholder"
389
- echo "Fix: Replace with NavigationApplicationSeedData.ApplicationId"
390
- fi
391
- fi
392
-
393
- # Quick startup test to verify seed data runs without exceptions
394
- API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
395
- if [ -n "$API_PROJECT" ] && [ -n "$APP_SEED" ]; then
396
- echo "Running seed data startup test..."
397
- dotnet run --project "$API_PROJECT" --urls "http://localhost:0" -- --environment Development > /tmp/ralph-seed-check.log 2>&1 &
398
- SEED_PID=$!
399
- sleep 10
400
- if ! kill -0 $SEED_PID 2>/dev/null; then
401
- SEED_LOG=$(cat /tmp/ralph-seed-check.log 2>/dev/null | tail -20)
402
- echo "❌ BLOCKING: Application crashed during startup — seed data likely failed"
403
- echo "Last 20 lines: $SEED_LOG"
404
- # Do NOT continue to frontend/test — fix seed data first
405
- else
406
- echo "✓ Seed data startup test passed"
407
- kill $SEED_PID 2>/dev/null
408
- wait $SEED_PID 2>/dev/null
409
- fi
410
- rm -f /tmp/ralph-seed-check.log
411
- fi
412
- ```
413
- If FAIL → fix seed data → rebuild → DO NOT continue to frontend/test tasks
414
-
415
- **Error resolution cycle:**
416
- 1. Read the FULL error output (not just first line)
417
- 2. Identify root cause: file path + line number
418
- 3. Fix the issue in source code
419
- 4. Rebuild/retest
420
- 5. If still failing → repeat from step 1
421
- 6. **NEVER** commit with failing build or tests
179
+ ## B. Delegate Batch to /apex
422
180
 
423
- ---
181
+ > **Ralph NEVER generates code.** All code generation, MCP calls, POST-CHECKs,
182
+ > and build verification are handled by apex.
183
+
184
+ ### B1. Mark Batch In-Progress
185
+
186
+ ```javascript
187
+ for (const task of batch) {
188
+ task.status = 'in_progress';
189
+ task.started_at = new Date().toISOString();
190
+ }
191
+ writeJSON('.ralph/prd.json', prd);
192
+ ```
193
+
194
+ ### B2. Invoke /apex
195
+
196
+ **INVOKE `/apex -d .ralph/prd.json`**
424
197
 
425
- ## C. Commit Batch
198
+ Apex handles everything for the current module:
199
+ - Reads PRD, extracts context (context_code, app_name, module_code, entities, sections)
200
+ - Executes ALL layers: domain → infrastructure → migration → application → api → seed data → frontend → tests
201
+ - Runs full POST-CHECKs (50 checks from `references/post-checks.md`)
202
+ - Commits per layer (atomic commits)
203
+ - Validates with MCP (`validate_conventions`)
204
+ - Updates task statuses in the PRD file directly
426
205
 
427
- ### C1. Update PRD Status (MANDATORY BEFORE git commit)
206
+ > **FLAGS:** `-d` implies `-a` (auto, no user confirmation) and `-e` (economy, no nested teams).
428
207
 
429
- > **CRITICAL:** PRD status MUST be updated BEFORE committing.
430
- > In test-v4-005, the compact loop executed 44 tasks but NEVER updated prd.json status.
431
- > This broke ALL downstream guardrails (module completeness check never triggered).
208
+ ### B3. Verify Post-Apex Results
432
209
 
433
210
  ```javascript
434
- const now = new Date().toISOString();
435
- const COMMIT_HASH_PENDING = 'pending-commit'; // Updated after git commit
211
+ // Re-read PRD after apex execution
212
+ const updatedPrd = readJSON('.ralph/prd.json');
213
+ const batchIds = batch.map(t => t.id);
436
214
 
437
- for (const task of batch) {
438
- if (task.status !== 'failed') task.status = 'completed';
439
- task.completed_at = now;
440
- task.iteration = prd.config.current_iteration;
441
- task.commit_hash = COMMIT_HASH_PENDING;
215
+ const completed = updatedPrd.tasks.filter(t => batchIds.includes(t.id) && t.status === 'completed').length;
216
+ const failed = updatedPrd.tasks.filter(t => batchIds.includes(t.id) && t.status === 'failed').length;
217
+ const pending = updatedPrd.tasks.filter(t => batchIds.includes(t.id) && t.status === 'pending').length;
218
+
219
+ if (failed > 0) {
220
+ console.log(`WARNING: ${failed} tasks failed during apex execution`);
221
+ // Failed tasks will be retried in next loop iteration if retries remain
442
222
  }
443
- prd.config.current_iteration++;
444
- prd.updated_at = now;
445
- writeJSON('.ralph/prd.json', prd);
223
+
224
+ if (pending > 0) {
225
+ console.log(`INFO: ${pending} tasks still pending — apex may not have reached them`);
226
+ }
227
+
228
+ console.log(`Apex completed: ${completed}/${batchIds.length} tasks`);
446
229
  ```
447
230
 
448
- ### C2. Update Progress File (MANDATORY)
231
+ ---
232
+
233
+ ## C. Commit PRD State
234
+
235
+ ### C1. Update Progress File (MANDATORY)
449
236
 
450
237
  ```javascript
451
- const completed = prd.tasks.filter(t => t.status === 'completed').length;
452
- const total = prd.tasks.length;
453
- const moduleName = prd.project?.module || 'unknown';
238
+ const prdCheck = readJSON('.ralph/prd.json');
239
+ const totalCompleted = prdCheck.tasks.filter(t => t.status === 'completed').length;
240
+ const total = prdCheck.tasks.length;
454
241
 
455
- // Append to progress.txt (NOT overwrite)
456
242
  appendFile('.ralph/progress.txt',
457
- `Iteration ${prd.config.current_iteration - 1}: ${batch.map(t => t.id).join('/')} COMPLETED — ` +
458
- `${batch[0].category} (${batch.length} tasks). ${completed}/${total} done.\n` +
459
- ` Files: ${batch.reduce((acc, t) => acc + (t.files_changed?.created?.length || 0), 0)} created\n`
243
+ `Iteration ${prdCheck.config.current_iteration}: Delegated to /apex — ` +
244
+ `${completed}/${batchIds.length} batch tasks completed. ${totalCompleted}/${total} overall.\n`
460
245
  );
461
246
  ```
462
247
 
463
- ### C3. Git Commit
248
+ ### C2. Increment Iteration
249
+
250
+ ```javascript
251
+ prdCheck.config.current_iteration++;
252
+ prdCheck.updated_at = new Date().toISOString();
253
+ writeJSON('.ralph/prd.json', prdCheck);
254
+ ```
255
+
256
+ ### C3. Git Commit (PRD state only — apex already committed code)
464
257
 
465
258
  ```bash
466
- # Stage all changed files from batch
467
- git add {all_files_from_batch}
468
259
  git add .ralph/prd.json .ralph/progress.txt
469
260
  [ -f .ralph/modules-queue.json ] && git add .ralph/modules-queue.json
470
261
 
471
262
  git commit -m "$(cat <<'EOF'
472
- feat({scope}): [{category}] {batch.length} tasks — {short summary}
263
+ chore(ralph): update PRD stateiteration {iteration}
473
264
 
474
- Tasks: {task_ids} / {total}
475
- Iteration: {iteration}
476
- {module ? "Module: " + module : ""}
265
+ Tasks: {completed}/{batch_size} completed via /apex
266
+ Overall: {totalCompleted}/{total}
477
267
 
478
268
  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
479
269
  EOF
480
270
  )"
481
-
482
- COMMIT_HASH=$(git rev-parse --short HEAD)
483
271
  ```
484
272
 
485
- ### C4. Finalize PRD with Commit Hash
273
+ ### C4. PRD Sync Verification (HARD CHECK)
486
274
 
487
275
  ```javascript
488
- // Update commit hash now that we have the real one
489
- for (const task of batch) {
490
- if (task.commit_hash === 'pending-commit') task.commit_hash = COMMIT_HASH;
491
- }
492
- prd.history.push({
493
- iteration: prd.config.current_iteration - 1,
494
- task_ids: batch.map(t => t.id),
495
- action: 'batch-completed',
496
- timestamp: now,
497
- commit_hash: COMMIT_HASH,
498
- notes: "{summary}"
499
- });
500
- writeJSON('.ralph/prd.json', prd);
501
- ```
502
-
503
- ### C5. PRD Sync Verification (HARD CHECK)
504
-
505
- > **MANDATORY:** Verify prd.json reflects reality before looping back.
276
+ const prdVerify = readJSON('.ralph/prd.json');
277
+ const actuallyCompleted = prdVerify.tasks.filter(t => batchIds.includes(t.id) && t.status === 'completed');
506
278
 
507
- ```javascript
508
- const prdCheck = readJSON('.ralph/prd.json');
509
- const batchIds = batch.map(t => t.id);
510
- const actuallyCompleted = prdCheck.tasks.filter(t => batchIds.includes(t.id) && t.status === 'completed');
511
-
512
- if (actuallyCompleted.length !== batch.filter(t => t.status !== 'failed').length) {
513
- console.error('PRD SYNC ERROR: Tasks executed but not marked completed in prd.json');
514
- console.error(`Expected: ${batch.filter(t => t.status !== 'failed').length} completed, Got: ${actuallyCompleted.length}`);
515
- // Force re-write
516
- for (const task of batch) {
517
- const prdTask = prdCheck.tasks.find(t => t.id === task.id);
518
- if (prdTask && task.status === 'completed') prdTask.status = 'completed';
519
- if (prdTask) prdTask.commit_hash = COMMIT_HASH;
520
- }
521
- writeJSON('.ralph/prd.json', prdCheck);
522
- console.log('PRD SYNC REPAIRED');
279
+ if (actuallyCompleted.length !== completed) {
280
+ console.error('PRD SYNC ERROR: Mismatch between apex results and prd.json');
281
+ // Force re-read apex may have written after our read
282
+ // DO NOT overwrite apex is the source of truth for task statuses
523
283
  }
524
284
  ```
525
285