@atlashub/smartstack-cli 3.17.0 → 3.19.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 (43) hide show
  1. package/dist/index.js +3 -0
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/agents/gitflow/start.md +2 -2
  5. package/templates/skills/business-analyse/SKILL.md +5 -5
  6. package/templates/skills/business-analyse/_shared.md +46 -20
  7. package/templates/skills/business-analyse/html/ba-interactive.html +57 -107
  8. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +13 -0
  9. package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +1 -1
  10. package/templates/skills/business-analyse/html/src/scripts/03-render-cadrage.js +11 -20
  11. package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +1 -3
  12. package/templates/skills/business-analyse/html/src/template.html +31 -83
  13. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +71 -3
  14. package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +11 -8
  15. package/templates/skills/business-analyse/references/cadrage-structure-cards.md +7 -5
  16. package/templates/skills/business-analyse/references/deploy-data-build.md +42 -14
  17. package/templates/skills/business-analyse/references/deploy-modes.md +1 -1
  18. package/templates/skills/business-analyse/references/entity-architecture-decision.md +218 -0
  19. package/templates/skills/business-analyse/references/robustness-checks.md +2 -1
  20. package/templates/skills/business-analyse/references/spec-auto-inference.md +70 -16
  21. package/templates/skills/business-analyse/references/ui-resource-cards.md +149 -0
  22. package/templates/skills/business-analyse/steps/step-00-init.md +23 -5
  23. package/templates/skills/business-analyse/steps/step-01-cadrage.md +220 -32
  24. package/templates/skills/business-analyse/steps/step-02-decomposition.md +35 -26
  25. package/templates/skills/business-analyse/steps/step-03a1-setup.md +122 -32
  26. package/templates/skills/business-analyse/steps/step-03a2-analysis.md +8 -0
  27. package/templates/skills/business-analyse/steps/step-03b-ui.md +68 -5
  28. package/templates/skills/business-analyse/steps/step-03d-validate.md +34 -1
  29. package/templates/skills/business-analyse/steps/step-05a-handoff.md +99 -2
  30. package/templates/skills/business-analyse/steps/step-05b-deploy.md +44 -8
  31. package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +226 -41
  32. package/templates/skills/business-analyse/steps/step-06-review.md +2 -1
  33. package/templates/skills/business-analyse/templates/tpl-handoff.md +5 -4
  34. package/templates/skills/business-analyse/templates/tpl-launch-displays.md +4 -1
  35. package/templates/skills/business-analyse/templates-frd.md +5 -4
  36. package/templates/skills/gitflow/references/start-local-config.md +6 -3
  37. package/templates/skills/gitflow/steps/step-start.md +2 -2
  38. package/templates/skills/ralph-loop/SKILL.md +41 -1
  39. package/templates/skills/ralph-loop/references/category-rules.md +96 -2
  40. package/templates/skills/ralph-loop/references/compact-loop.md +85 -24
  41. package/templates/skills/ralph-loop/steps/step-00-init.md +30 -54
  42. package/templates/skills/ralph-loop/steps/step-01-task.md +102 -1
  43. package/templates/skills/ralph-loop/steps/step-04-check.md +87 -40
@@ -141,13 +141,86 @@ Rules:
141
141
  - DTOs separate from domain entities
142
142
  - Service interfaces in Application, implementations in Infrastructure
143
143
 
144
- **Tenant isolation (BLOCKING):**
144
+ **Tenant isolation (BLOCKING — SECURITY CRITICAL):**
145
+
146
+ > **ROOT CAUSE (test-v4-005):** Services were generated WITHOUT TenantId filtering,
147
+ > creating cross-tenant data leakage on ALL 70+ CRUD endpoints.
148
+ > This is an OWASP A01 (Broken Access Control) vulnerability.
149
+
145
150
  - ALL queries on tenant entities MUST include `.Where(x => x.TenantId == _currentUser.TenantId)`
146
151
  - ALL entity creation MUST pass `_currentUser.TenantId` as first parameter to `Entity.Create(tenantId, ...)`
147
152
  - NEVER use `new Entity { }` without `TenantId =` — always prefer factory method `Entity.Create()`
148
153
  - NEVER use `Guid.Empty` as a placeholder for userId, tenantId, or any business identifier
149
154
  - Service constructor MUST inject `ICurrentUserService _currentUser` to access TenantId
150
155
 
156
+ **MANDATORY Service Template (use as skeleton for ALL services):**
157
+
158
+ ```csharp
159
+ public class {Entity}Service : I{Entity}Service
160
+ {
161
+ private readonly IExtensionsDbContext _db;
162
+ private readonly ICurrentUserService _currentUser;
163
+
164
+ public {Entity}Service(IExtensionsDbContext db, ICurrentUserService currentUser)
165
+ {
166
+ _db = db;
167
+ _currentUser = currentUser;
168
+ }
169
+
170
+ public async Task<PagedResult<{Entity}Response>> GetAllAsync(/* filters */, CancellationToken ct)
171
+ {
172
+ var tenantId = _currentUser.TenantId;
173
+ var query = _db.{Entities}
174
+ .Where(x => x.TenantId == tenantId) // ← MANDATORY tenant filter
175
+ .AsQueryable();
176
+ // ... apply filters, pagination, projection to Response DTO
177
+ }
178
+
179
+ public async Task<{Entity}Response?> GetByIdAsync(Guid id, CancellationToken ct)
180
+ {
181
+ var tenantId = _currentUser.TenantId;
182
+ var entity = await _db.{Entities}
183
+ .Where(x => x.TenantId == tenantId) // ← MANDATORY tenant filter
184
+ .FirstOrDefaultAsync(x => x.Id == id, ct);
185
+ // ...
186
+ }
187
+
188
+ public async Task<{Entity}Response> CreateAsync(Create{Entity}Dto dto, CancellationToken ct)
189
+ {
190
+ var entity = {Entity}.Create(_currentUser.TenantId, /* dto fields */);
191
+ // ← TenantId as FIRST parameter
192
+ _db.{Entities}.Add(entity);
193
+ await _db.SaveChangesAsync(ct);
194
+ return MapToResponse(entity);
195
+ }
196
+
197
+ public async Task<{Entity}Response> UpdateAsync(Guid id, Update{Entity}Dto dto, CancellationToken ct)
198
+ {
199
+ var entity = await _db.{Entities}
200
+ .Where(x => x.TenantId == _currentUser.TenantId) // ← MANDATORY
201
+ .FirstOrDefaultAsync(x => x.Id == id, ct)
202
+ ?? throw new NotFoundException(nameof({Entity}), id);
203
+ // ... update fields
204
+ }
205
+
206
+ public async Task DeleteAsync(Guid id, CancellationToken ct)
207
+ {
208
+ var entity = await _db.{Entities}
209
+ .Where(x => x.TenantId == _currentUser.TenantId) // ← MANDATORY
210
+ .FirstOrDefaultAsync(x => x.Id == id, ct)
211
+ ?? throw new NotFoundException(nameof({Entity}), id);
212
+ // ...
213
+ }
214
+
215
+ private static {Entity}Response MapToResponse({Entity} entity) => new()
216
+ {
217
+ // Map entity fields to response DTO
218
+ };
219
+ }
220
+ ```
221
+
222
+ **POST-CHECK after writing ANY service:** Grep the file for `TenantId`. If 0 occurrences → FAIL, rewrite with tenant filtering.
223
+
151
224
  **Lifecycle-aware services:**
152
225
  - Services operating on entities with a `lifeCycle` (status field) MUST validate entity state before mutations
153
226
  - Example: `if (entity.Status == EmployeeStatus.Terminated) throw new BusinessException("Cannot update terminated employee")`
@@ -161,6 +234,22 @@ Rules:
161
234
  - DependencyInjection.cs MUST NOT be empty or contain only TODO comments
162
235
  - After writing validators, VERIFY DI registration exists — if missing, add it immediately
163
236
 
237
+ **POST-CHECK after writing validators:**
238
+ ```bash
239
+ # Count Create validators vs Update validators
240
+ CREATE_COUNT=$(find . -path "*/Validators/*" -name "Create*Validator.cs" | wc -l)
241
+ UPDATE_COUNT=$(find . -path "*/Validators/*" -name "Update*Validator.cs" | wc -l)
242
+ if [ "$CREATE_COUNT" -ne "$UPDATE_COUNT" ]; then
243
+ echo "VALIDATOR MISMATCH: $CREATE_COUNT Create vs $UPDATE_COUNT Update → MUST be equal"
244
+ # List missing UpdateValidators and CREATE them
245
+ fi
246
+ ```
247
+
248
+ **Mapper pattern (DRY):**
249
+ - Each service MUST include a `private static {Entity}Response MapToResponse({Entity} entity)` method
250
+ - For complex mappings with related entities, use an extension method in `Application/Mappings/{Module}Mappings.cs`
251
+ - NEVER duplicate mapping logic between GetAll, GetById, Create, Update — always call MapToResponse
252
+
164
253
  **FORBIDDEN:**
165
254
  - Empty DependencyInjection.cs with `// TODO` placeholder
166
255
  - CreateValidator without matching UpdateValidator
@@ -169,6 +258,7 @@ Rules:
169
258
  - `new Entity { }` without TenantId assignment
170
259
  - `Guid.Empty` as a business value in services or controllers
171
260
  - Entity.Create() without tenantId as first parameter
261
+ - Duplicated mapping logic (entity→response) in multiple methods
172
262
 
173
263
  ---
174
264
 
@@ -280,9 +370,13 @@ useXxx with raw axios inside hooks → hooks MUST use the shared apiCl
280
370
 
281
371
  **MCP:** `scaffold_tests`, `analyze_test_coverage`
282
372
 
373
+ > **CRITICAL:** Test generation is a MANDATORY category. If the PRD has no test tasks,
374
+ > the category completeness check (step-01 section 4b) will inject a guardrail task.
375
+ > Test projects MUST be created as the FIRST action in this category — before generating any test files.
376
+
283
377
  **Execution sequence:**
284
378
 
285
- 1. **Ensure test projects exist:**
379
+ 1. **Ensure test projects exist (FIRST — before any test generation):**
286
380
  ```bash
287
381
  # Unit test project
288
382
  UNIT_TEST_PROJECT="tests/${PROJECT_NAME}.Tests.Unit"
@@ -1,8 +1,15 @@
1
1
  # Compact Loop — Inline Execution
2
2
 
3
3
  > **Loaded by:** step-04 section 5 (after first full iteration)
4
- > **Purpose:** Execute all subsequent iterations inline without re-reading step files.
5
- > **Rule:** NEVER stop the loop. NEVER wait for user input. NEVER re-read step files.
4
+ > **Purpose:** Execute ALL remaining tasks autonomously without stopping.
5
+ > **EXECUTION GUARANTEE:** This loop runs until ALL tasks are done, max iterations, or dead-end.
6
+ > **ABSOLUTE RULES:**
7
+ > - NEVER stop the loop
8
+ > - NEVER wait for user input
9
+ > - NEVER ask for confirmation
10
+ > - NEVER re-read step files
11
+ > - NEVER pause between iterations
12
+ > - Execute A → B → C → D → back to step-04 section 1 → repeat
6
13
 
7
14
  ---
8
15
 
@@ -208,10 +215,49 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
208
215
 
209
216
  ## C. Commit Batch
210
217
 
218
+ ### C1. Update PRD Status (MANDATORY — BEFORE git commit)
219
+
220
+ > **CRITICAL:** PRD status MUST be updated BEFORE committing.
221
+ > In test-v4-005, the compact loop executed 44 tasks but NEVER updated prd.json status.
222
+ > This broke ALL downstream guardrails (module completeness check never triggered).
223
+
224
+ ```javascript
225
+ const now = new Date().toISOString();
226
+ const COMMIT_HASH_PENDING = 'pending-commit'; // Updated after git commit
227
+
228
+ for (const task of batch) {
229
+ if (task.status !== 'failed') task.status = 'completed';
230
+ task.completed_at = now;
231
+ task.iteration = prd.config.current_iteration;
232
+ task.commit_hash = COMMIT_HASH_PENDING;
233
+ }
234
+ prd.config.current_iteration++;
235
+ prd.updated_at = now;
236
+ writeJSON('.ralph/prd.json', prd);
237
+ ```
238
+
239
+ ### C2. Update Progress File (MANDATORY)
240
+
241
+ ```javascript
242
+ const completed = prd.tasks.filter(t => t.status === 'completed').length;
243
+ const total = prd.tasks.length;
244
+ const moduleName = prd.project?.module || 'unknown';
245
+
246
+ // Append to progress.txt (NOT overwrite)
247
+ appendFile('.ralph/progress.txt',
248
+ `Iteration ${prd.config.current_iteration - 1}: ${batch.map(t => t.id).join('/')} COMPLETED — ` +
249
+ `${batch[0].category} (${batch.length} tasks). ${completed}/${total} done.\n` +
250
+ ` Files: ${batch.reduce((acc, t) => acc + (t.files_changed?.created?.length || 0), 0)} created\n`
251
+ );
252
+ ```
253
+
254
+ ### C3. Git Commit
255
+
211
256
  ```bash
212
257
  # Stage all changed files from batch
213
258
  git add {all_files_from_batch}
214
- git add .ralph/prd.json
259
+ git add .ralph/prd.json .ralph/progress.txt
260
+ [ -f .ralph/modules-queue.json ] && git add .ralph/modules-queue.json
215
261
 
216
262
  git commit -m "$(cat <<'EOF'
217
263
  feat({scope}): [{category}] {batch.length} tasks — {short summary}
@@ -227,44 +273,59 @@ EOF
227
273
  COMMIT_HASH=$(git rev-parse --short HEAD)
228
274
  ```
229
275
 
230
- **Finalize tasks in prd.json:**
276
+ ### C4. Finalize PRD with Commit Hash
277
+
231
278
  ```javascript
232
- const now = new Date().toISOString();
279
+ // Update commit hash now that we have the real one
233
280
  for (const task of batch) {
234
- if (task.status !== 'failed') task.status = 'completed';
235
- task.completed_at = now;
236
- task.iteration = prd.config.current_iteration;
237
- task.commit_hash = COMMIT_HASH;
281
+ if (task.commit_hash === 'pending-commit') task.commit_hash = COMMIT_HASH;
238
282
  }
239
283
  prd.history.push({
240
- iteration: prd.config.current_iteration,
284
+ iteration: prd.config.current_iteration - 1,
241
285
  task_ids: batch.map(t => t.id),
242
286
  action: 'batch-completed',
243
287
  timestamp: now,
244
288
  commit_hash: COMMIT_HASH,
245
289
  notes: "{summary}"
246
290
  });
247
- prd.config.current_iteration++;
248
- prd.updated_at = now;
249
291
  writeJSON('.ralph/prd.json', prd);
250
292
  ```
251
293
 
252
- **Commit progress:**
253
- ```bash
254
- git add .ralph/prd.json .ralph/progress.txt
255
- [ -f .ralph/modules-queue.json ] && git add .ralph/modules-queue.json
256
- git commit -m "chore(ralph): progress — iteration {iteration}"
294
+ ### C5. PRD Sync Verification (HARD CHECK)
295
+
296
+ > **MANDATORY:** Verify prd.json reflects reality before looping back.
297
+
298
+ ```javascript
299
+ const prdCheck = readJSON('.ralph/prd.json');
300
+ const batchIds = batch.map(t => t.id);
301
+ const actuallyCompleted = prdCheck.tasks.filter(t => batchIds.includes(t.id) && t.status === 'completed');
302
+
303
+ if (actuallyCompleted.length !== batch.filter(t => t.status !== 'failed').length) {
304
+ console.error('PRD SYNC ERROR: Tasks executed but not marked completed in prd.json');
305
+ console.error(`Expected: ${batch.filter(t => t.status !== 'failed').length} completed, Got: ${actuallyCompleted.length}`);
306
+ // Force re-write
307
+ for (const task of batch) {
308
+ const prdTask = prdCheck.tasks.find(t => t.id === task.id);
309
+ if (prdTask && task.status === 'completed') prdTask.status = 'completed';
310
+ if (prdTask) prdTask.commit_hash = COMMIT_HASH;
311
+ }
312
+ writeJSON('.ralph/prd.json', prdCheck);
313
+ console.log('PRD SYNC REPAIRED');
314
+ }
257
315
  ```
258
316
 
259
317
  ---
260
318
 
261
- ## D. Loop Back
319
+ ## D. Loop Back (MANDATORY — NO PAUSE)
262
320
 
263
321
  **IMMEDIATELY return to step-04 section 1 (Read Current State).**
264
- DO NOT stop. DO NOT wait for user. DO NOT re-read step files.
265
322
 
266
- The only exit conditions:
267
- - ALL tasks complete (all modules if multi-module)
268
- - Max iterations reached
269
- - Dead-end (all remaining blocked/failed)
270
- - User Ctrl+C
323
+ DO NOT stop. DO NOT wait for user. DO NOT ask "should I continue?". DO NOT re-read step files.
324
+ DO NOT display a summary and pause. DO NOT output partial results and wait.
325
+ **Go directly to step-04 section 1. NOW.**
326
+
327
+ The ONLY exit conditions (enforced by step-04 sections 2-4):
328
+ - ALL tasks complete (all modules if multi-module) → step-05
329
+ - Max iterations reached → step-05
330
+ - Dead-end (all remaining blocked/failed) → step-05
331
+ - User Ctrl+C (external interrupt)
@@ -8,9 +8,12 @@ next_step: steps/step-01-task.md
8
8
 
9
9
  ## MANDATORY RULES:
10
10
 
11
+ - **FULLY AUTONOMOUS** — execute ALL tasks without stopping or asking the user
11
12
  - NEVER skip MCP verification
12
13
  - ALWAYS parse ALL flags before any action
13
14
  - ONLY check resume if -r flag is set
15
+ - **NEVER ask the user** to choose a mode, confirm execution, or select options
16
+ - **NEVER set max_iterations = 1** — unless the user explicitly passed `-m 1`
14
17
  - CONTEXT BUDGET: Keep output COMPACT
15
18
  - **NEVER DELEGATE** the main Ralph loop to a sub-agent (single module)
16
19
  - **Multi-module (2+ modules):** Use Agent Teams for parallel execution
@@ -48,69 +51,36 @@ mcp__context7__resolve-library-id: libraryName: "test" → {mcp_context7} = tru
48
51
 
49
52
  If ANY fails: show error, suggest `smartstack check-mcp`, STOP.
50
53
 
51
- ## 3. Human-in-the-Loop Checkpoint (RECOMMENDED)
54
+ ## 3. Execution Mode (FULLY AUTONOMOUS)
52
55
 
53
- > **Best Practice:** Commencer en mode supervisé avant le mode autonome complet.
56
+ > **CRITICAL:** Ralph-loop is FULLY AUTONOMOUS by default.
57
+ > It executes ALL tasks from start to finish WITHOUT stopping.
58
+ > The user launched `/ralph-loop` — this IS the instruction to execute everything.
54
59
 
55
- ### Why HITL First?
60
+ **DO NOT** ask the user to confirm, choose a mode, or approve execution.
61
+ **DO NOT** set `max_iterations = 1` or any reduced value.
62
+ **DO NOT** stop after the first task to "check quality".
56
63
 
57
- Ralph-loop est puissant mais peut diverger si :
58
- - Les requirements sont ambigus
59
- - Les tests sont lents (>30s)
60
- - Le code généré ne compile pas immédiatement
61
-
62
- **Stratégie recommandée :**
63
-
64
- 1. **Première itération supervisée** - Lancer manuellement la première tâche pour vérifier :
65
- - La qualité du code généré
66
- - Le temps d'exécution des tests
67
- - La clarté des messages d'erreur
68
-
69
- 2. **Mode autonome limité** - Si première itération OK, relancer avec `--max-iterations 5-10`
70
-
71
- 3. **Mode autonome complet** - Une fois confiant, augmenter à `--max-iterations 50`
72
-
73
- ### Checkpoint Prompt (Optional)
74
-
75
- Si vous détectez que c'est la première utilisation de ralph-loop sur ce projet (pas de `.ralph/logs/`), proposer :
64
+ The loop runs autonomously until:
65
+ - ALL tasks are completed (success)
66
+ - `max_iterations` is reached (user explicitly set via `-m N`)
67
+ - Dead-end detected (all remaining tasks blocked/failed)
68
+ - User Ctrl+C (external interrupt)
76
69
 
77
70
  ```javascript
78
- if (!dirExists('.ralph/logs') && !resume_mode) {
79
- AskUserQuestion({
80
- questions: [{
81
- question: "Premier usage de ralph-loop détecté. Démarrer en mode supervisé ou autonome ?",
82
- header: "Mode",
83
- multiSelect: false,
84
- options: [
85
- { label: "Supervisé (recommandé)", description: "Exécuter 1 tâche, puis demander confirmation" },
86
- { label: "Autonome limité", description: "Max 10 itérations automatiques" },
87
- { label: "Autonome complet", description: "Jusqu'à 50 itérations (mode AFK)" }
88
- ]
89
- }]
90
- });
91
-
92
- if (answer === "Supervisé") {
93
- max_iterations = 1;
94
- console.log("Mode supervisé : 1 tâche sera exécutée. Relancez avec -r pour continuer.");
95
- } else if (answer === "Autonome limité") {
96
- max_iterations = Math.min(max_iterations, 10);
97
- }
98
- }
71
+ // NO user prompt. NO mode selection. JUST EXECUTE.
72
+ // max_iterations is already set from flags (default: 50).
73
+ // If user wants fewer iterations, they use -m N explicitly.
74
+ console.log(`Mode: AUTONOMOUS | Max iterations: ${max_iterations}`);
99
75
  ```
100
76
 
101
- ### Feedback Speed Warning
102
-
103
- ⚠️ **IMPORTANT :** Si vos tests prennent >30 secondes, ralph-loop peut devenir inefficace.
77
+ ### Speed Warning (informational only — does NOT block)
104
78
 
79
+ If tests are slow (>30s detected in previous logs), display a one-line warning but **continue execution**:
105
80
  ```
106
- Temps test recommandés :
107
- - Unitaires : <5s
108
- - Intégration : <15s
109
- - E2E : <30s (à exécuter en dehors du loop)
81
+ Slow tests detected. Consider disabling E2E during ralph-loop.
110
82
  ```
111
83
 
112
- Si tests lents détectés (via logs), avertir l'utilisateur et suggérer de désactiver tests E2E pendant le loop.
113
-
114
84
  ## 4. Resume Mode
115
85
 
116
86
  If `{resume_mode} = true`:
@@ -213,9 +183,15 @@ if (PRD_COUNT > 1) {
213
183
  }
214
184
  ```
215
185
 
216
- ## 5. Completion Promise
186
+ ## 5. Completion Promise (auto-set)
217
187
 
218
- If `{completion_promise}` is null: ask user to choose (COMPLETE, ALL TESTS PASS, DONE, or custom).
188
+ ```javascript
189
+ // Auto-set if not provided — NEVER ask the user
190
+ if (!completion_promise) {
191
+ completion_promise = "COMPLETE";
192
+ }
193
+ // The promise is output ONLY when ALL tasks are done. No user interaction needed.
194
+ ```
219
195
 
220
196
  ## 6. Collect Metadata
221
197
 
@@ -94,11 +94,62 @@ If `.ralph/prd.json` exists:
94
94
  2. **v3 FAST PATH:** If `$version === "3.0.0"` AND `tasks[]` exists:
95
95
  - Inject runtime fields if missing (status, config, feature, created, updated_at, history)
96
96
  - Write back to `.ralph/prd.json`
97
+ - **Run CATEGORY COMPLETENESS CHECK (section 4b) before proceeding**
97
98
  - Skip directly to section 5 (find next task)
98
99
  3. **v2 legacy:** If `$version === "2.0.0"` → find next eligible task (section 5), skip to section 5
99
100
  4. **FORMAT A (deprecated):** If `.project && .requirements && !.$version` → run `transformPrdJsonToRalphV2()` → section 5
100
101
 
101
- If `.ralph/prd.json` does not exist: continue to section 2.
102
+ If `.ralph/prd.json` does not exist: continue to section 1b.
103
+
104
+ ### 1b. BA Handoff Quality Gate (BLOCKING)
105
+
106
+ > **CRITICAL:** Before generating ANY tasks, verify the BA handoff is complete.
107
+ > A missing handoff means no frontend files, no test plan, no BR-to-code mapping.
108
+ > Generating tasks without handoff produces an INCOMPLETE PRD (backend-only).
109
+
110
+ ```javascript
111
+ // Find the source feature.json for the current module
112
+ const sourceFeatureJson = prd?.metadata?.sourceFeatureJson
113
+ || findFile('docs/business/**/business-analyse/**/feature.json');
114
+
115
+ if (sourceFeatureJson) {
116
+ const feature = readJSON(sourceFeatureJson);
117
+ const handoff = feature.handoff || {};
118
+
119
+ // CHECK 1: Handoff status must be "handed-off"
120
+ if (handoff.status !== 'handed-off') {
121
+ console.error(`
122
+ ╔══════════════════════════════════════════════════════════════╗
123
+ ║ BLOCKING: BA HANDOFF INCOMPLETE ║
124
+ ║ ║
125
+ ║ feature.json handoff.status = "${handoff.status || 'missing'}"
126
+ ║ Expected: "handed-off" ║
127
+ ║ ║
128
+ ║ The /business-analyse did not complete steps 04a→05c. ║
129
+ ║ Without handoff, the PRD will be MISSING: ║
130
+ ║ - Frontend tasks (pages, components, routes) ║
131
+ ║ - Test tasks (unit, integration, security) ║
132
+ ║ - BR-to-code mappings ║
133
+ ║ - API endpoint summary ║
134
+ ║ ║
135
+ ║ ACTION: Run /business-analyse to complete the handoff, ║
136
+ ║ then re-run /ralph-loop. ║
137
+ ╚══════════════════════════════════════════════════════════════╝`);
138
+ STOP;
139
+ }
140
+
141
+ // CHECK 2: filesToCreate must have all 7 categories
142
+ const filesToCreate = handoff.filesToCreate || {};
143
+ const requiredCategories = ['domain', 'application', 'infrastructure', 'api', 'frontend', 'seedData', 'tests'];
144
+ const missingInHandoff = requiredCategories.filter(c => !filesToCreate[c] || filesToCreate[c].length === 0);
145
+
146
+ if (missingInHandoff.length > 0) {
147
+ console.warn(`⚠ Handoff has empty categories: ${missingInHandoff.join(', ')}`);
148
+ console.warn('PRD generation may produce incomplete tasks. Consider re-running /business-analyse step-05a.');
149
+ // WARNING only — allow proceeding if handoff.status is correct
150
+ }
151
+ }
152
+ ```
102
153
 
103
154
  ## 2. Analyze Task Description
104
155
 
@@ -128,6 +179,56 @@ Initialize `.ralph/progress.txt`:
128
179
  - Dependency references are valid (exist, no forward/circular)
129
180
  - Task count 3-30
130
181
 
182
+ ### 4b. Category Completeness Check (BLOCKING)
183
+
184
+ > **CRITICAL:** The PRD MUST contain tasks for ALL required categories.
185
+ > A PRD with only backend categories (domain, infrastructure, application, api) is INCOMPLETE.
186
+ > This check prevents the "no frontend generated" failure mode.
187
+
188
+ ```javascript
189
+ const REQUIRED_CATEGORIES = ['domain', 'infrastructure', 'application', 'api', 'frontend', 'test'];
190
+ const presentCategories = new Set(prd.tasks.map(t => t.category));
191
+ const missingCategories = REQUIRED_CATEGORIES.filter(c => !presentCategories.has(c));
192
+
193
+ if (missingCategories.length > 0) {
194
+ console.warn(`⚠ PRD MISSING CATEGORIES: ${missingCategories.join(', ')}`);
195
+
196
+ // AUTO-INJECT guardrail tasks for missing categories
197
+ let maxIdNum = Math.max(...prd.tasks.map(t => {
198
+ const num = parseInt(t.id.replace(/[^0-9]/g, ''), 10);
199
+ return isNaN(num) ? 0 : num;
200
+ }));
201
+ const prefix = prd.tasks[0]?.id?.replace(/[0-9]+$/, '') || 'GUARD-';
202
+ const lastBackendTask = prd.tasks.filter(t => t.category === 'api').pop()?.id || prd.tasks[prd.tasks.length - 1]?.id;
203
+
204
+ for (const cat of missingCategories) {
205
+ maxIdNum++;
206
+ const taskId = `${prefix}${String(maxIdNum).padStart(3, '0')}`;
207
+ const guardrailTask = {
208
+ id: taskId,
209
+ description: `[GUARDRAIL] Generate ${cat} layer for ${prd.project?.module || 'module'}`,
210
+ status: 'pending',
211
+ category: cat,
212
+ dependencies: cat === 'test' ? [lastBackendTask] : (cat === 'frontend' ? [lastBackendTask] : []),
213
+ acceptance_criteria: [
214
+ cat === 'frontend' ? 'React pages created via MCP scaffold_api_client + scaffold_routes, wired to App.tsx' :
215
+ cat === 'test' ? 'Unit + Integration test projects created, scaffold_tests MCP called, dotnet test passes' :
216
+ `${cat} layer fully implemented per category-rules.md`
217
+ ],
218
+ started_at: null, completed_at: null, iteration: null,
219
+ commit_hash: null, files_changed: [], validation: null, error: null
220
+ };
221
+ prd.tasks.push(guardrailTask);
222
+ console.log(` → Injected guardrail: [${taskId}] ${cat}`);
223
+ }
224
+
225
+ writeJSON('.ralph/prd.json', prd);
226
+ console.log(`PRD updated: ${prd.tasks.length} tasks (${missingCategories.length} guardrails added)`);
227
+ }
228
+ ```
229
+
230
+ **Why this matters:** In test-v4-005, the PRD was generated with only backend categories (domain, infrastructure, application, api, seedData, validation). The frontend and test categories were entirely absent, resulting in 0 frontend pages and 0 tests generated across 3 modules.
231
+
131
232
  ## 5. Find Current Task
132
233
 
133
234
  ```javascript