@atlashub/smartstack-cli 4.48.0 → 4.50.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 (41) hide show
  1. package/.documentation/testing-ba-e2e.md +76 -24
  2. package/package.json +1 -1
  3. package/templates/agents/gitflow/init.md +26 -0
  4. package/templates/skills/apex/references/parallel-execution.md +22 -4
  5. package/templates/skills/apex/steps/step-00-init.md +38 -0
  6. package/templates/skills/apex/steps/step-03a-layer0-domain.md +21 -0
  7. package/templates/skills/apex/steps/step-03b-layer1-seed.md +60 -0
  8. package/templates/skills/apex/steps/step-03c-layer2-backend.md +124 -13
  9. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +32 -0
  10. package/templates/skills/application/references/backend-controller-hierarchy.md +14 -4
  11. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +2 -0
  12. package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +2 -0
  13. package/templates/skills/business-analyse/schemas/application-schema.json +36 -1
  14. package/templates/skills/business-analyse/schemas/sections/specification-schema.json +19 -0
  15. package/templates/skills/business-analyse/steps/step-00-init.md +64 -14
  16. package/templates/skills/business-analyse/steps/step-01-cadrage.md +49 -2
  17. package/templates/skills/business-analyse/steps/step-02-structure.md +41 -17
  18. package/templates/skills/business-analyse/steps/step-04-consolidate.md +171 -0
  19. package/templates/skills/business-analyse-develop/references/quality-gates.md +91 -1
  20. package/templates/skills/business-analyse-develop/steps/step-01-task.md +147 -1
  21. package/templates/skills/business-analyse-handoff/references/acceptance-criteria.md +53 -1
  22. package/templates/skills/business-analyse-handoff/references/handoff-file-templates.md +42 -0
  23. package/templates/skills/business-analyse-handoff/references/handoff-mappings.md +15 -1
  24. package/templates/skills/business-analyse-handoff/references/prd-generation.md +59 -0
  25. package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +25 -1
  26. package/templates/skills/business-analyse-handoff/steps/step-02-export.md +32 -4
  27. package/templates/skills/business-analyse-html/html/ba-interactive.html +80 -11
  28. package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +4 -2
  29. package/templates/skills/business-analyse-html/html/src/scripts/02-navigation.js +16 -2
  30. package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +19 -4
  31. package/templates/skills/business-analyse-html/html/src/scripts/06-render-mockups.js +1 -1
  32. package/templates/skills/business-analyse-html/html/src/styles/03-navigation.css +2 -2
  33. package/templates/skills/business-analyse-html/html/src/styles/05-modules.css +38 -0
  34. package/templates/skills/business-analyse-html/references/data-build.md +4 -1
  35. package/templates/skills/business-analyse-html/references/data-mapping.md +4 -1
  36. package/templates/skills/business-analyse-html/steps/step-02-build-data.md +113 -1
  37. package/templates/skills/business-analyse-html/steps/step-04-verify.md +17 -1
  38. package/templates/skills/controller/references/mcp-scaffold-workflow.md +8 -4
  39. package/templates/skills/controller/steps/step-05-validate.md +2 -2
  40. package/templates/skills/controller/templates.md +4 -3
  41. package/templates/skills/feature-full/steps/step-01-implementation.md +18 -5
@@ -330,6 +330,175 @@ Les types possibles : `functional`, `business-rule`, `performance`, `security`.
330
330
 
331
331
  Les critères d'acceptation sont écrits dans `validation.json` au même niveau que `globalRiskAssessment`.
332
332
 
333
+ ### 7ter. Naming Audit (MANDATORY)
334
+
335
+ > **Challenge all names before final approval.** Every code, label, and route generated
336
+ > during the BA must be reviewed for coherence, convention compliance, and clarity.
337
+
338
+ **7ter-a. Collect all names from the BA:**
339
+
340
+ ```javascript
341
+ const namingRegistry = {
342
+ application: {
343
+ label: application_name,
344
+ code: applicationCode, // PascalCase
345
+ route: toKebabCase(applicationCode) // kebab-case
346
+ },
347
+ modules: completedModules.map(m => ({
348
+ label: m.name,
349
+ code: m.code, // PascalCase
350
+ route: toKebabCase(m.code) // kebab-case
351
+ })),
352
+ entities: completedModules.flatMap(m =>
353
+ m.entities.map(e => ({
354
+ name: e.name, // PascalCase (C# class name)
355
+ module: m.code,
356
+ tableName: e.tableName || pluralize(e.name) // Plural convention
357
+ }))
358
+ ),
359
+ permissionRoots: [...new Set(
360
+ permissionPaths.map(p => p.path.split('.').slice(0, 2).join('.'))
361
+ )]
362
+ };
363
+ ```
364
+
365
+ **7ter-b. Display naming recap table:**
366
+
367
+ ```
368
+ ═══════════════════════════════════════════════════════════════
369
+ NAMING AUDIT — {application_name}
370
+ ═══════════════════════════════════════════════════════════════
371
+
372
+ APPLICATION
373
+ | Label | Code (PascalCase) | Route (kebab-case) |
374
+ |-------|-------------------|--------------------|
375
+ | {application_name} | {applicationCode} | /{route} |
376
+
377
+ MODULES
378
+ | # | Label | Code (PascalCase) | Route (kebab-case) |
379
+ |---|-------|-------------------|--------------------|
380
+ {for each module: index | label | code | /app-route/module-route}
381
+
382
+ ENTITIES
383
+ | Entity (PascalCase) | Module | Table (plural) |
384
+ |----------------------|--------|----------------|
385
+ {for each entity: name | module | tableName}
386
+
387
+ PERMISSION ROOTS
388
+ {for each root: path}
389
+ ═══════════════════════════════════════════════════════════════
390
+ ```
391
+
392
+ **7ter-c. Coherence checks:**
393
+
394
+ ```javascript
395
+ const namingIssues = [];
396
+
397
+ // 1. Duplicate entity names across modules
398
+ const entityNames = namingRegistry.entities.map(e => e.name);
399
+ const duplicates = entityNames.filter((n, i) => entityNames.indexOf(n) !== i);
400
+ if (duplicates.length > 0) {
401
+ namingIssues.push({ severity: "ERROR", issue: `Duplicate entity names: ${[...new Set(duplicates)].join(', ')}` });
402
+ }
403
+
404
+ // 2. Module code vs label coherence (code should derive logically from label)
405
+ for (const mod of namingRegistry.modules) {
406
+ if (!mod.code || mod.code.length < 2) {
407
+ namingIssues.push({ severity: "ERROR", issue: `Module "${mod.label}" has invalid code: "${mod.code}"` });
408
+ }
409
+ }
410
+
411
+ // 3. Permission root alignment with module codes
412
+ for (const root of namingRegistry.permissionRoots) {
413
+ const [appSegment, moduleSegment] = root.split('.');
414
+ const matchingModule = namingRegistry.modules.find(m =>
415
+ m.code.toLowerCase() === moduleSegment.toLowerCase()
416
+ );
417
+ if (!matchingModule) {
418
+ namingIssues.push({ severity: "WARNING", issue: `Permission root "${root}" has no matching module code` });
419
+ }
420
+ }
421
+
422
+ // 4. Route collision detection
423
+ const allRoutes = namingRegistry.modules.map(m =>
424
+ `/${namingRegistry.application.route}/${m.route}`
425
+ );
426
+ const routeDuplicates = allRoutes.filter((r, i) => allRoutes.indexOf(r) !== i);
427
+ if (routeDuplicates.length > 0) {
428
+ namingIssues.push({ severity: "ERROR", issue: `Route collisions: ${routeDuplicates.join(', ')}` });
429
+ }
430
+ ```
431
+
432
+ **7ter-d. MCP validation:**
433
+
434
+ ```
435
+ mcp__smartstack__validate_conventions({
436
+ checks: ["tables"],
437
+ context: {
438
+ applicationCode: applicationCode,
439
+ modules: namingRegistry.modules.map(m => m.code),
440
+ entities: namingRegistry.entities.map(e => e.name)
441
+ }
442
+ })
443
+
444
+ → Merge MCP findings into namingIssues[]
445
+ ```
446
+
447
+ **7ter-e. Present findings and confirm:**
448
+
449
+ ```
450
+ IF namingIssues.length > 0:
451
+ Display issues table:
452
+ | # | Severity | Issue |
453
+ |---|----------|-------|
454
+ {for each issue}
455
+ ```
456
+
457
+ Ask via AskUserQuestion:
458
+ ```
459
+ question: "{language == 'fr'
460
+ ? 'Validez-vous les noms ci-dessus pour l\\'ensemble de l\\'application ?'
461
+ : 'Do you approve all the names above for the entire application?'}"
462
+ header: "Naming Audit"
463
+ options:
464
+ - label: "{language == 'fr' ? 'Approuvé' : 'Approved'}"
465
+ description: "{language == 'fr' ? 'Tous les noms sont corrects' : 'All names are correct'}"
466
+ - label: "{language == 'fr' ? 'Renommer certains éléments' : 'Rename some elements'}"
467
+ description: "{language == 'fr' ? 'Corriger des noms avant de finaliser' : 'Fix names before finalizing'}"
468
+ ```
469
+
470
+ **IF "Renommer certains éléments":**
471
+
472
+ Ask via AskUserQuestion (open-ended):
473
+ ```
474
+ question: "{language == 'fr'
475
+ ? 'Quels éléments souhaitez-vous renommer ? (ex: \"Module Ventes → Commerce\", \"Entity Invoice → BillingDocument\")'
476
+ : 'Which elements do you want to rename? (e.g., \"Module Sales → Commerce\", \"Entity Invoice → BillingDocument\")'}"
477
+ header: "Renaming"
478
+ ```
479
+
480
+ Process user response:
481
+ 1. Parse rename instructions
482
+ 2. Update `applicationCode`, module codes, entity names, permission paths accordingly in JSON files via ba-writer
483
+ 3. Re-run 7ter-c and 7ter-d checks on updated names
484
+ 4. Re-display the naming recap table for final confirmation
485
+
486
+ **IF "Approuvé":**
487
+ → Store naming audit result in `validation.json`:
488
+ ```javascript
489
+ ba-writer.enrichSection({
490
+ featureId: {feature_id},
491
+ section: "namingAudit",
492
+ data: {
493
+ auditedAt: now(),
494
+ issues: namingIssues,
495
+ approved: true,
496
+ renames: [] // or list of applied renames
497
+ }
498
+ });
499
+ ```
500
+ → Continue to section 8
501
+
333
502
  ### 8. Consolidation Summary Display
334
503
 
335
504
  ```
@@ -449,6 +618,7 @@ ba-writer.enrichSection({
449
618
  `E2E flows: ${e2eFlows.length} identified`,
450
619
  `Global risk: ${risks.length > 0 ? 'MEDIUM' : 'LOW'}`,
451
620
  `Semantic checks: PASSED`,
621
+ `Naming audit: ${namingIssues.length} issues found, APPROVED`,
452
622
  `Client approval: APPROVED`
453
623
  ]
454
624
  }
@@ -508,6 +678,7 @@ BA workflow complete. Next steps:
508
678
  - ✓ No circular dependencies
509
679
  - ✓ Permission coherence validated
510
680
  - ✓ Semantic checks: 0 errors
681
+ - ✓ Naming audit completed and approved
511
682
  - ✓ Client approval obtained (or auto-approved for single module)
512
683
  - ✓ Consolidation section written to validation.json
513
684
  - ✓ Status updated to "consolidated"
@@ -14,8 +14,9 @@
14
14
  | MCP code review | `review_code` | YES (if perm mismatch) | After security |
15
15
  | MCP conventions | `validate_conventions` | NO (report) | After code review |
16
16
  | MCP test conventions | `validate_test_conventions` | NO (report) | After conventions |
17
- | BR coverage | grep implementation | YES (100%) | After MCP gates |
17
+ | BR coverage | grep implementation (enriched) | YES (100%) | After MCP gates |
18
18
  | File reconciliation | compare expectedFiles vs disk | YES | After BR coverage |
19
+ | Spec fidelity | compare specs vs code | NO (report) | After file reconciliation |
19
20
  | Stub test detection | grep stub patterns | YES | During test gate |
20
21
 
21
22
  ## Stub Test Patterns (AUTO-REJECT)
@@ -66,5 +67,94 @@ BR coverage check
66
67
  ↓ (100%)
67
68
  File reconciliation
68
69
  ↓ (all present)
70
+ Spec fidelity (non-blocking report)
71
+ ↓ (report)
69
72
  MODULE COMPLETE
70
73
  ```
74
+
75
+ ## BR Coverage — Enriched Verification
76
+
77
+ When `prd.specificationFiles` is present and companion files are available, the BR coverage gate uses the enriched `brToCodeMapping` (with `statement`, `formula`, `example`) for verification.
78
+
79
+ **Standard check:** grep each `brToCodeMapping[].ruleId` in the codebase to confirm implementation.
80
+
81
+ **Enriched fix context:** When a BR is NOT covered, provide full context for the fix:
82
+
83
+ ```
84
+ MISSING BR IMPLEMENTATION:
85
+ Rule: {ruleId}
86
+ Statement: {statement}
87
+ Example: {example}
88
+ Formula: {formula}
89
+ Entities: {entities[]}
90
+ Category: {category}
91
+ Severity: {severity}
92
+ Expected in: {implementationPoints[].component} ({implementationPoints[].layer})
93
+ ```
94
+
95
+ This gives `/apex` or the developer the complete context to implement the missing BR without re-reading the BA artifacts.
96
+
97
+ ## Gate: Specification Fidelity (non-blocking report)
98
+
99
+ > **When:** After file reconciliation. **Blocking:** NO (report only).
100
+ > **Requires:** `prd.specificationFiles` present with companion files.
101
+
102
+ Compares generated code against companion specification files to measure implementation fidelity.
103
+
104
+ **Checks:**
105
+
106
+ ```javascript
107
+ if (prd.specificationFiles) {
108
+ const specsDir = '.ralph';
109
+ const entitiesSpec = readJSON(`${specsDir}/${prd.specificationFiles.entities}`);
110
+ const rulesSpec = readJSON(`${specsDir}/${prd.specificationFiles.rules}`);
111
+ let totalChecks = 0, passedChecks = 0;
112
+
113
+ // 1. Entity property coverage
114
+ for (const entity of (entitiesSpec.entities || [])) {
115
+ const entityFile = findFile(`src/**/Domain/**/${entity.name}.cs`);
116
+ if (entityFile) {
117
+ const content = readFile(entityFile);
118
+ for (const attr of (entity.attributes || [])) {
119
+ totalChecks++;
120
+ if (content.includes(attr.name)) passedChecks++;
121
+ else console.warn(`SPEC DRIFT: ${entity.name}.${attr.name} not found in ${entityFile}`);
122
+ }
123
+ }
124
+ }
125
+
126
+ // 2. Validator coverage for BR-VAL-*
127
+ for (const rule of (rulesSpec.rules || [])) {
128
+ if (rule.id?.startsWith('BR-VAL-')) {
129
+ totalChecks++;
130
+ const validatorFiles = findFiles(`src/**/Validators/**/*Validator.cs`);
131
+ const found = validatorFiles.some(f => readFile(f).includes(rule.id));
132
+ if (found) passedChecks++;
133
+ else console.warn(`SPEC DRIFT: ${rule.id} (${rule.statement?.slice(0, 50)}...) has no Validator implementation`);
134
+ }
135
+ }
136
+
137
+ // 3. Calculation coverage for BR-CALC-*
138
+ for (const rule of (rulesSpec.rules || [])) {
139
+ if (rule.id?.startsWith('BR-CALC-') && rule.formula) {
140
+ totalChecks++;
141
+ const serviceFiles = findFiles(`src/**/Services/**/*Service.cs`);
142
+ const found = serviceFiles.some(f => readFile(f).includes(rule.id));
143
+ if (found) passedChecks++;
144
+ else console.warn(`SPEC DRIFT: ${rule.id} formula "${rule.formula}" has no Service implementation`);
145
+ }
146
+ }
147
+
148
+ const fidelityScore = totalChecks > 0 ? Math.round((passedChecks / totalChecks) * 100) : 100;
149
+ console.log(`SPEC FIDELITY: ${fidelityScore}% (${passedChecks}/${totalChecks} checks passed)`);
150
+ }
151
+ ```
152
+
153
+ **Output:**
154
+ ```
155
+ SPEC FIDELITY: 92% (23/25 checks passed)
156
+ DRIFT: Employee.TerminationDate not found in Employee.cs
157
+ DRIFT: BR-CALC-002 formula "netSalary = grossSalary - deductions" has no Service implementation
158
+ ```
159
+
160
+ This gate does NOT block — it provides visibility into specification coverage gaps.
@@ -66,6 +66,149 @@ If `{currentPrdPath}` exists:
66
66
  - Write back to `{currentPrdPath}`
67
67
  - **Run CATEGORY COMPLETENESS CHECK (section 4b) before proceeding**
68
68
  - Skip directly to section 5 (find next task)
69
+ 2b. **v3 filesToCreate PATH:** If `$version === "3.0.0"` AND `implementation.filesToCreate` exists AND NO `tasks[]`:
70
+ → Transform filesToCreate into tasks[]:
71
+
72
+ ```javascript
73
+ const ftc = prd.implementation.filesToCreate;
74
+ const tasks = [];
75
+ let taskId = 1;
76
+
77
+ // CATEGORY ORDER: domain → infrastructure → application → api → seedData → frontend → test → documentation
78
+ const categoryOrder = ['domain', 'infrastructure', 'application', 'api', 'seedData', 'frontend', 'tests', 'documentation'];
79
+
80
+ // Load companion specs if available (for rich acceptance criteria)
81
+ const specFiles = prd.specificationFiles || {};
82
+ const specsDir = '.ralph';
83
+ let specEntities = null, specRules = null, specUsecases = null, specScreens = null, specPermissions = null;
84
+ try {
85
+ if (specFiles.entities) specEntities = readJSON(`${specsDir}/${specFiles.entities}`);
86
+ if (specFiles.rules) specRules = readJSON(`${specsDir}/${specFiles.rules}`);
87
+ if (specFiles.usecases) specUsecases = readJSON(`${specsDir}/${specFiles.usecases}`);
88
+ if (specFiles.screens) specScreens = readJSON(`${specsDir}/${specFiles.screens}`);
89
+ if (specFiles.permissions) specPermissions = readJSON(`${specsDir}/${specFiles.permissions}`);
90
+ } catch (e) { /* companion files absent — fallback to generic AC */ }
91
+
92
+ function deriveAcceptanceCriteria(file, category, prd) {
93
+ const fileName = (file.path || file).split('/').pop().replace(/\.\w+$/, '');
94
+
95
+ // If no companion specs available, fallback to generic AC
96
+ if (!specEntities && !specRules) {
97
+ return `File ${file.path || file} exists and compiles`;
98
+ }
99
+
100
+ switch (category) {
101
+ case 'domain': {
102
+ const entityName = fileName;
103
+ const entity = (specEntities?.entities || []).find(e => e.name === entityName);
104
+ if (entity && entity.attributes) {
105
+ const attrs = entity.attributes.map(a => `${a.name}:${a.type}`).join(', ');
106
+ const rels = (entity.relationships || []).map(r => `${r.type} ${r.target}`).join(', ');
107
+ return `Entity ${entityName} with attributes [${attrs}]${rels ? '. Relations: [' + rels + ']' : ''}`;
108
+ }
109
+ break;
110
+ }
111
+ case 'application': {
112
+ const linkedBRs = (prd.brToCodeMapping || [])
113
+ .filter(br => br.implementationPoints?.some(ip => ip.component?.includes(fileName)))
114
+ .map(br => `${br.ruleId}: ${br.statement || br.title}`);
115
+ const linkedUCs = (file.linkedUCs || []).join(', ');
116
+ if (linkedBRs.length > 0 || linkedUCs) {
117
+ const parts = [];
118
+ if (linkedBRs.length > 0) parts.push(`Implements BRs: [${linkedBRs.join('; ')}]`);
119
+ if (linkedUCs) parts.push(`Handles UCs: [${linkedUCs}]`);
120
+ return parts.join('. ');
121
+ }
122
+ break;
123
+ }
124
+ case 'infrastructure': {
125
+ const entityName = fileName.replace('Configuration', '');
126
+ const entity = (specEntities?.entities || []).find(e => e.name === entityName);
127
+ if (entity && entity.attributes) {
128
+ const props = entity.attributes.map(a => a.name).join(', ');
129
+ return `EF Config for ${entityName}. Attributes: [${props}]`;
130
+ }
131
+ break;
132
+ }
133
+ case 'api': {
134
+ const entityName = fileName.replace('Controller', '');
135
+ const endpoints = (prd.apiEndpointSummary || [])
136
+ .filter(ep => ep.operation?.includes(entityName))
137
+ .map(ep => `${ep.method} ${ep.route}`);
138
+ const perms = (specPermissions?.permissionPaths || [])
139
+ .filter(p => p.toLowerCase().includes(entityName.toLowerCase()))
140
+ .slice(0, 5);
141
+ const parts = [`Controller ${entityName}`];
142
+ if (endpoints.length > 0) parts.push(`Endpoints: [${endpoints.join(', ')}]`);
143
+ if (perms.length > 0) parts.push(`Permissions: [${perms.join(', ')}]`);
144
+ return parts.join('. ');
145
+ }
146
+ case 'frontend': {
147
+ const screen = (specScreens?.screens || []).find(s =>
148
+ fileName.toLowerCase().includes(s.code?.toLowerCase() || s.name?.toLowerCase() || '')
149
+ );
150
+ if (screen) {
151
+ const parts = [file.type || 'Page'];
152
+ if (screen.columns) parts.push(`columns [${screen.columns.map(c => c.key || c.name || c).join(', ')}]`);
153
+ if (screen.filters) parts.push(`filters [${screen.filters.map(f => f.key || f.name || f).join(', ')}]`);
154
+ if (screen.actions) parts.push(`actions [${screen.actions.map(a => a.key || a.name || a).join(', ')}]`);
155
+ if (screen.kpis) parts.push(`KPIs [${screen.kpis.map(k => k.label || k.name || k).join(', ')}]`);
156
+ return `${parts.join(' with ')}`;
157
+ }
158
+ break;
159
+ }
160
+ case 'seedData': {
161
+ if (file.category === 'business') {
162
+ const entityName = fileName.replace('SeedData', '');
163
+ const entity = (specEntities?.entities || []).find(e => e.name === entityName);
164
+ if (entity?.seedValues) {
165
+ const names = entity.seedValues.map(v => v.name || v.label || v.code).filter(Boolean).slice(0, 5);
166
+ return `Seeds ${entityName} with ${entity.seedValues.length} values: [${names.join(', ')}]`;
167
+ }
168
+ }
169
+ break;
170
+ }
171
+ case 'test': case 'tests': {
172
+ const linkedBRs = (prd.brToCodeMapping || [])
173
+ .filter(br => br.implementationPoints?.some(ip =>
174
+ ip.layer === 'Domain' || ip.layer === 'Application'
175
+ ))
176
+ .map(br => br.ruleId);
177
+ const linkedUCs = (file.linkedUCs || []);
178
+ const parts = [];
179
+ if (linkedBRs.length > 0) parts.push(`Covers BRs: [${linkedBRs.join(', ')}]`);
180
+ if (linkedUCs.length > 0) parts.push(`Covers UCs: [${linkedUCs.join(', ')}]`);
181
+ if (parts.length > 0) return parts.join('. ');
182
+ break;
183
+ }
184
+ }
185
+ // Fallback: generic AC
186
+ return `File ${file.path || file} exists and compiles`;
187
+ }
188
+
189
+ for (const category of categoryOrder) {
190
+ const files = ftc[category] || [];
191
+ for (const file of files) {
192
+ tasks.push({
193
+ id: `T${String(taskId++).padStart(3, '0')}`,
194
+ description: `Create ${file.type || category}: ${(file.path || file).split('/').pop()}`,
195
+ status: 'pending',
196
+ category: category === 'tests' ? 'test' : category, // normalize
197
+ dependencies: [], // implicit ordering via category
198
+ acceptance_criteria: deriveAcceptanceCriteria(file, category === 'tests' ? 'test' : category, prd),
199
+ path: file.path || file,
200
+ started_at: null, completed_at: null, iteration: null,
201
+ commit_hash: null, files_changed: [], validation: null, error: null
202
+ });
203
+ }
204
+ }
205
+
206
+ prd.tasks = tasks;
207
+ prd.config = { current_iteration: 1, max_iterations: 50 };
208
+ writeJSON(currentPrdPath, prd);
209
+ ```
210
+ → Run CATEGORY COMPLETENESS CHECK (section 4b) — will detect if seedData is missing
211
+ → Skip to section 5 (find next task)
69
212
  3. **v2 legacy:** If `$version === "2.0.0"` → find next eligible task (section 5)
70
213
  4. **FORMAT A (deprecated):** If `.project && .requirements && !.$version` → run `transformPrdJsonToRalphV2()` → section 5
71
214
 
@@ -120,7 +263,10 @@ Check for BA handoff source (priority):
120
263
  3. Markdown handoff: `find . -path "*development-handoff*" -name "*.md"`
121
264
  4. Direct task from `{task_description}`
122
265
 
123
- Generate **3-30 subtasks** by category (domain → infrastructure → application → api → frontend → i18n → test → validation).
266
+ Generate **3-30 subtasks** by category (domain → infrastructure → application → api → seedData → frontend → i18n → test → validation).
267
+
268
+ **seedData category is MANDATORY** — generates navigation entries, permissions, roles, and the IClientSeedDataProvider.
269
+ Without seedData tasks, modules are invisible in the UI (no menu, no permissions, no RBAC).
124
270
 
125
271
  ## 3. Create prd.json
126
272
 
@@ -51,6 +51,12 @@ These verify data that `/business-analyse-handoff` step-01 generates:
51
51
  | AC-09 | BR-to-code mapping present | 1 | `handoff.brToCodeMapping[]` | YES |
52
52
  | AC-10 | API endpoint summary present | 1 | `handoff.apiEndpointSummary[]` | YES |
53
53
  | AC-11 | SeedData entries have category | ALL | `handoff.filesToCreate.seedData[].category` | YES |
54
+ | AC-12 | `specificationFiles` present with 5 paths | 5 | `handoff.specificationFiles` | YES |
55
+ | AC-13 | Companion entities file exists and count matches source | match | `.ralph/prd-{module}.entities.json` | YES |
56
+ | AC-14 | Companion rules file exists and count matches source | match | `.ralph/prd-{module}.rules.json` | YES |
57
+ | AC-15 | Companion usecases file exists and count matches source | match | `.ralph/prd-{module}.usecases.json` | YES |
58
+ | AC-16 | Companion screens file exists (if screens.json exists) | exists | `.ralph/prd-{module}.screens.json` | YES |
59
+ | AC-17 | Each `brToCodeMapping[].statement` non-empty | ALL | `handoff.brToCodeMapping[].statement` | YES |
54
60
 
55
61
  ---
56
62
 
@@ -240,11 +246,51 @@ if (noCat.length > 0) {
240
246
  console.error('FAIL: AC-11: ' + noCat.length + ' seedData entries missing category field');
241
247
  }
242
248
 
249
+ // AC-12: specificationFiles present with 5 paths
250
+ const specFiles = handoff.specificationFiles || {};
251
+ const specKeys = ['entities', 'rules', 'usecases', 'screens', 'permissions'];
252
+ const missingSpecs = specKeys.filter(k => !specFiles[k]);
253
+ if (missingSpecs.length > 0) {
254
+ fails.push('AC-12: specificationFiles missing keys: ' + missingSpecs.join(', '));
255
+ console.error('FAIL: AC-12: specificationFiles missing keys: ' + missingSpecs.join(', '));
256
+ }
257
+
258
+ // AC-13 to AC-16: Companion files exist and counts match
259
+ const ralphDir = path.dirname(process.argv[1]);
260
+ const companionChecks = [
261
+ { ac: 'AC-13', key: 'entities', arrayKey: 'entities' },
262
+ { ac: 'AC-14', key: 'rules', arrayKey: 'rules' },
263
+ { ac: 'AC-15', key: 'usecases', arrayKey: 'useCases' },
264
+ { ac: 'AC-16', key: 'screens', arrayKey: 'screens' }
265
+ ];
266
+ for (const check of companionChecks) {
267
+ const companionPath = specFiles[check.key] ? path.join(ralphDir, specFiles[check.key]) : null;
268
+ if (companionPath && fs.existsSync(companionPath)) {
269
+ const companionData = JSON.parse(fs.readFileSync(companionPath, 'utf-8'));
270
+ const arr = companionData[check.arrayKey] || companionData[check.arrayKey.toLowerCase()] || [];
271
+ if (arr.length === 0 && check.ac !== 'AC-16') {
272
+ fails.push(check.ac + ': companion ' + check.key + ' file is empty');
273
+ console.error('FAIL: ' + check.ac + ': companion ' + check.key + ' file has 0 entries');
274
+ }
275
+ } else if (companionPath) {
276
+ fails.push(check.ac + ': companion file missing: ' + specFiles[check.key]);
277
+ console.error('FAIL: ' + check.ac + ': companion file not found: ' + specFiles[check.key]);
278
+ }
279
+ }
280
+
281
+ // AC-17: Each brToCodeMapping[].statement non-empty
282
+ const brMapping = handoff.brToCodeMapping || [];
283
+ const emptyStatements = brMapping.filter(br => !br.statement || br.statement.trim() === '');
284
+ if (emptyStatements.length > 0) {
285
+ fails.push('AC-17: ' + emptyStatements.length + ' BRs with empty statement');
286
+ console.error('FAIL: AC-17: ' + emptyStatements.length + ' brToCodeMapping entries have empty statement: ' + emptyStatements.slice(0, 3).map(b => b.ruleId).join(', '));
287
+ }
288
+
243
289
  if (fails.length > 0) {
244
290
  console.error('\\nBLOCKING: ' + fails.length + ' output acceptance criteria FAILED');
245
291
  process.exit(1);
246
292
  }
247
- console.log('PASS: All output acceptance criteria met (AC-08 to AC-11)');
293
+ console.log('PASS: All output acceptance criteria met (AC-08 to AC-17)');
248
294
  " "$MODULE_JSON"
249
295
  ```
250
296
 
@@ -264,3 +310,9 @@ console.log('PASS: All output acceptance criteria met (AC-08 to AC-11)');
264
310
  | AC-09 | Re-run `/business-analyse-handoff` step-01 transform (BR mapping) |
265
311
  | AC-10 | Re-run `/business-analyse-handoff` step-01 transform (API endpoints) |
266
312
  | AC-11 | Re-run `/business-analyse-handoff` step-01 transform (seedData categories) |
313
+ | AC-12 | Re-run `/business-analyse-handoff` step-01 transform (specificationFiles missing) |
314
+ | AC-13 | Re-run `/business-analyse-handoff` step-01 transform (entities companion missing/empty) |
315
+ | AC-14 | Re-run `/business-analyse-handoff` step-01 transform (rules companion missing/empty) |
316
+ | AC-15 | Re-run `/business-analyse-handoff` step-01 transform (usecases companion missing/empty) |
317
+ | AC-16 | Re-run `/business-analyse-handoff` step-01 transform (screens companion missing) |
318
+ | AC-17 | Re-run `/business-analyse-handoff` step-01 transform (BR statements empty — copy from rules.json) |
@@ -153,3 +153,45 @@ From `screens.json > screens[]` and `usecases.json > useCases[]`:
153
153
 
154
154
  Include: Technical documentation data, API specification files, user guides.
155
155
  This category can be an empty array `[]` if no documentation is planned at this stage. It will be populated by the `/documentation` skill after `/business-analyse-develop` completes.
156
+
157
+ ## 4.9 Specification Files (Companion Files)
158
+
159
+ > **Purpose:** Enable `/apex` and `/business-analyse-develop` to load full BA specifications per layer, avoiding monolithic context loading.
160
+
161
+ Each PRD MUST include a `specificationFiles` object referencing 5 companion files:
162
+
163
+ ```json
164
+ "specificationFiles": {
165
+ "entities": "prd-{moduleCode}.entities.json",
166
+ "rules": "prd-{moduleCode}.rules.json",
167
+ "usecases": "prd-{moduleCode}.usecases.json",
168
+ "screens": "prd-{moduleCode}.screens.json",
169
+ "permissions": "prd-{moduleCode}.permissions.json"
170
+ }
171
+ ```
172
+
173
+ ### Naming Convention
174
+
175
+ `prd-{moduleCode}.{section}.json` — all files colocated in `.ralph/` alongside the PRD.
176
+
177
+ | File | Source | Content |
178
+ |------|--------|---------|
179
+ | `prd-{moduleCode}.entities.json` | `{moduleDir}/entities.json` | VERBATIM copy |
180
+ | `prd-{moduleCode}.rules.json` | `{moduleDir}/rules.json` | VERBATIM copy |
181
+ | `prd-{moduleCode}.usecases.json` | `{moduleDir}/usecases.json` | VERBATIM copy |
182
+ | `prd-{moduleCode}.screens.json` | `{moduleDir}/screens.json` | VERBATIM copy |
183
+ | `prd-{moduleCode}.permissions.json` | `{moduleDir}/permissions.json` | VERBATIM copy |
184
+
185
+ ### Rules
186
+
187
+ - **VERBATIM copy:** Each companion file is a 1:1 copy of the BA source file. Zero transformation, zero filtering.
188
+ - **Colocated:** All companion files live in `.ralph/` alongside the PRD file.
189
+ - **Mandatory:** All 5 companion files MUST be generated for every module PRD.
190
+ - **Layer loading:** `/apex` loads only the companion files needed per layer (see loading plan below).
191
+
192
+ | Layer | Files loaded | Estimated context |
193
+ |-------|-------------|-------------------|
194
+ | Layer 0 (domain) | `entities.json` | ~200-400 lines |
195
+ | Layer 1 (seed) | `entities.json` + `permissions.json` | ~300-500 lines |
196
+ | Layer 2 (backend) | `rules.json` + `usecases.json` | ~300-600 lines |
197
+ | Layer 3 (frontend) | `screens.json` + `usecases.json` | ~300-500 lines |
@@ -12,6 +12,13 @@ Generate complete mapping for each BR:
12
12
  {
13
13
  "ruleId": "BR-VAL-001",
14
14
  "title": "Order total must equal sum of item prices",
15
+ "statement": "The order total amount must equal the sum of all order item prices multiplied by their quantities",
16
+ "example": "Order with 3 items at $10, $20, $30 → total must be $60. If total = $50 → validation error",
17
+ "formula": null,
18
+ "conditions": [],
19
+ "entities": ["Order", "OrderItem"],
20
+ "category": "validation",
21
+ "sectionCode": "orders",
15
22
  "module": "{moduleCode}",
16
23
  "severity": "critical",
17
24
  "implementationPoints": [
@@ -27,7 +34,14 @@ Generate complete mapping for each BR:
27
34
 
28
35
  For each BR include:
29
36
  - **ruleId**: Reference to `rules.json > rules[].id`
30
- - **title**: The rule statement
37
+ - **title**: Short descriptive title
38
+ - **statement**: VERBATIM copy of `rules.json > rules[].statement` — the full rule text, NOT a paraphrase
39
+ - **example**: VERBATIM copy of `rules.json > rules[].example` — concrete example illustrating the rule (null if absent)
40
+ - **formula**: VERBATIM copy of `rules.json > rules[].formula` — formula for BR-CALC-* rules (null if absent)
41
+ - **conditions**: VERBATIM copy of `rules.json > rules[].conditions[]` — conditions of applicability (empty array if absent)
42
+ - **entities**: Array of entity names this rule applies to (from `rules.json > rules[].entities[]` or inferred from rule context)
43
+ - **category**: Rule category from `rules.json > rules[].category` — one of: validation, calculation, workflow, security, data
44
+ - **sectionCode**: Section this rule belongs to (from `rules.json > rules[].sectionCode` or inferred)
31
45
  - **module**: Which module it belongs to
32
46
  - **severity**: From `rule.severity` (blocking, info, etc.) mapped to "critical", "high", "medium", "low"
33
47
  - **implementationPoints**: Array of {layer, component, method, implementation}