@atlashub/smartstack-cli 4.48.0 → 4.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.documentation/testing-ba-e2e.md +76 -24
- package/package.json +1 -1
- package/templates/agents/gitflow/init.md +26 -0
- package/templates/skills/apex/references/parallel-execution.md +22 -4
- package/templates/skills/apex/steps/step-00-init.md +38 -0
- package/templates/skills/apex/steps/step-03a-layer0-domain.md +21 -0
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +60 -0
- package/templates/skills/apex/steps/step-03c-layer2-backend.md +124 -13
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +32 -0
- package/templates/skills/application/references/backend-controller-hierarchy.md +14 -4
- package/templates/skills/business-analyse-develop/references/quality-gates.md +91 -1
- package/templates/skills/business-analyse-develop/steps/step-01-task.md +147 -1
- package/templates/skills/business-analyse-handoff/references/acceptance-criteria.md +53 -1
- package/templates/skills/business-analyse-handoff/references/handoff-file-templates.md +42 -0
- package/templates/skills/business-analyse-handoff/references/handoff-mappings.md +15 -1
- package/templates/skills/business-analyse-handoff/references/prd-generation.md +59 -0
- package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +25 -1
- package/templates/skills/business-analyse-handoff/steps/step-02-export.md +32 -4
- package/templates/skills/business-analyse-html/html/ba-interactive.html +64 -5
- package/templates/skills/business-analyse-html/html/src/scripts/02-navigation.js +13 -1
- package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +10 -1
- package/templates/skills/business-analyse-html/html/src/scripts/06-render-mockups.js +1 -1
- package/templates/skills/business-analyse-html/html/src/styles/03-navigation.css +2 -2
- package/templates/skills/business-analyse-html/html/src/styles/05-modules.css +38 -0
- package/templates/skills/controller/references/mcp-scaffold-workflow.md +8 -4
- package/templates/skills/controller/steps/step-05-validate.md +2 -2
- package/templates/skills/controller/templates.md +4 -3
- package/templates/skills/feature-full/steps/step-01-implementation.md +18 -5
|
@@ -9,6 +9,38 @@ parent_step: steps/step-03-execute.md
|
|
|
9
9
|
|
|
10
10
|
# Layer 3 — Frontend (Pages + I18n + Documentation)
|
|
11
11
|
|
|
12
|
+
### Companion Specs Loading (delegate mode)
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
if (delegate_mode && specification_loading_plan) {
|
|
16
|
+
const specsDir = path.dirname(delegate_prd_path);
|
|
17
|
+
// Layer 3 loads: screens + usecases companions
|
|
18
|
+
for (const file of specification_loading_plan.layer3_frontend) {
|
|
19
|
+
const specPath = path.join(specsDir, file);
|
|
20
|
+
if (fileExists(specPath)) {
|
|
21
|
+
const specData = readJSON(specPath);
|
|
22
|
+
if (specData.screens) {
|
|
23
|
+
// Full screen definitions: columns[], fields[], filters[], actions[], kpis[]
|
|
24
|
+
// Use for: DataTable columns, form fields, filter configs, action buttons, dashboard KPIs
|
|
25
|
+
console.log(`Loaded screens: ${specData.screens.length} screen specs with columns/fields/filters`);
|
|
26
|
+
}
|
|
27
|
+
if (specData.useCases || specData.usecases) {
|
|
28
|
+
const ucs = specData.useCases || specData.usecases;
|
|
29
|
+
// UC steps inform form validation, navigation flows, error states
|
|
30
|
+
console.log(`Loaded usecases: ${ucs.length} use cases for form flows`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> When companion specs are loaded:
|
|
38
|
+
> - **List pages**: use `screen.columns[]` for exact DataTable column definitions (key, label, type, sortable)
|
|
39
|
+
> - **Form pages**: use `screen.fields[]` for exact form field definitions (name, type, required, validation)
|
|
40
|
+
> - **Filter configs**: use `screen.filters[]` for search/filter UI generation
|
|
41
|
+
> - **Dashboard pages**: use `screen.kpis[]` for KPI card and chart definitions
|
|
42
|
+
> - **Actions**: use `screen.actions[]` for action buttons (create, export, bulk operations)
|
|
43
|
+
|
|
12
44
|
### ⛔ HARD RULE — /ui-components is NON-NEGOTIABLE (read BEFORE any Layer 3 action)
|
|
13
45
|
|
|
14
46
|
> **VIOLATION CHECK:** If ANY .tsx page file was created by Write tool WITHOUT
|
|
@@ -38,14 +38,24 @@ Api/Controllers/
|
|
|
38
38
|
The generated controller uses NavRoute attribute:
|
|
39
39
|
|
|
40
40
|
```csharp
|
|
41
|
-
[NavRoute("{full_path}")]
|
|
42
41
|
[ApiController]
|
|
43
|
-
[
|
|
42
|
+
[NavRoute("{full_path}")]
|
|
43
|
+
[Microsoft.AspNetCore.Authorization.Authorize]
|
|
44
|
+
[Produces("application/json")]
|
|
45
|
+
[Tags("{EntityNamePlural}")]
|
|
44
46
|
public class {EntityName}Controller : ControllerBase
|
|
45
47
|
{
|
|
48
|
+
private readonly ISender _mediator;
|
|
49
|
+
|
|
50
|
+
public {EntityName}Controller(ISender mediator) => _mediator = mediator;
|
|
51
|
+
|
|
46
52
|
[HttpGet]
|
|
47
|
-
[RequirePermission(Permissions.{Application}.{Module}.
|
|
48
|
-
|
|
53
|
+
[RequirePermission(Permissions.{Application}.{Module}.View)]
|
|
54
|
+
[ProducesResponseType(typeof(List<{EntityName}ListDto>), StatusCodes.Status200OK)]
|
|
55
|
+
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
56
|
+
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
|
57
|
+
public async Task<ActionResult<List<{EntityName}ListDto>>> GetAll(CancellationToken ct)
|
|
58
|
+
=> Ok(await _mediator.Send(new Get{EntityNamePlural}Query(), ct));
|
|
49
59
|
|
|
50
60
|
// ... other CRUD methods
|
|
51
61
|
}
|
|
@@ -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-
|
|
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**:
|
|
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}
|
|
@@ -208,6 +208,64 @@ Each file entry in a category array must have:
|
|
|
208
208
|
|
|
209
209
|
---
|
|
210
210
|
|
|
211
|
+
### 6. Specification Files (MANDATORY)
|
|
212
|
+
|
|
213
|
+
Each PRD MUST include a `specificationFiles` object and 5 companion files in `.ralph/`:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"$version": "3.0.0",
|
|
218
|
+
"implementation": { "filesToCreate": { /* 8 categories */ } },
|
|
219
|
+
"brToCodeMapping": [ /* enriched with statement, example, formula */ ],
|
|
220
|
+
"apiEndpointSummary": [ /* ... */ ],
|
|
221
|
+
"specificationFiles": {
|
|
222
|
+
"entities": "prd-{moduleCode}.entities.json",
|
|
223
|
+
"rules": "prd-{moduleCode}.rules.json",
|
|
224
|
+
"usecases": "prd-{moduleCode}.usecases.json",
|
|
225
|
+
"screens": "prd-{moduleCode}.screens.json",
|
|
226
|
+
"permissions": "prd-{moduleCode}.permissions.json"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Companion files** are VERBATIM copies of BA source files, colocated in `.ralph/`:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
.ralph/
|
|
235
|
+
prd-Employees.json # PRD manifest (compact)
|
|
236
|
+
prd-Employees.entities.json # VERBATIM copy of entities.json
|
|
237
|
+
prd-Employees.rules.json # VERBATIM copy of rules.json
|
|
238
|
+
prd-Employees.usecases.json # VERBATIM copy of usecases.json
|
|
239
|
+
prd-Employees.screens.json # VERBATIM copy of screens.json
|
|
240
|
+
prd-Employees.permissions.json # VERBATIM copy of permissions.json
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Validation:**
|
|
244
|
+
```javascript
|
|
245
|
+
const specFiles = prd.specificationFiles;
|
|
246
|
+
if (!specFiles) {
|
|
247
|
+
BLOCKING_ERROR('specificationFiles missing from PRD');
|
|
248
|
+
}
|
|
249
|
+
const specKeys = ['entities', 'rules', 'usecases', 'screens', 'permissions'];
|
|
250
|
+
for (const key of specKeys) {
|
|
251
|
+
if (!specFiles[key]) {
|
|
252
|
+
BLOCKING_ERROR(`specificationFiles.${key} missing`);
|
|
253
|
+
}
|
|
254
|
+
const filePath = path.join('.ralph', specFiles[key]);
|
|
255
|
+
if (!fs.existsSync(filePath)) {
|
|
256
|
+
BLOCKING_ERROR(`Companion file not found: ${filePath}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Why companion files matter:**
|
|
262
|
+
- `/apex` loads only the specs needed per layer (entities for L0, rules+usecases for L2, screens for L3)
|
|
263
|
+
- Context per layer is ~200-500 lines instead of ~2000 lines monolithic
|
|
264
|
+
- Zero risk of specs being lost at the bottom of a large context window
|
|
265
|
+
- Full BA detail available for oneshot generation (attributes, formulas, screen columns)
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
211
269
|
## Validation Checklist
|
|
212
270
|
|
|
213
271
|
Use this checklist when validating PRD files:
|
|
@@ -219,6 +277,7 @@ Use this checklist when validating PRD files:
|
|
|
219
277
|
| **Categories** | All 8 categories present | PASS/FAIL |
|
|
220
278
|
| **File Counts** | PRD counts match index.json handoff.filesToCreate | PASS/FAIL |
|
|
221
279
|
| **File Schemas** | All files have path, type, module | PASS/FAIL |
|
|
280
|
+
| **Spec Files** | `specificationFiles` present with 5 paths, all companion files exist | PASS/FAIL |
|
|
222
281
|
|
|
223
282
|
**All checks must PASS before proceeding to /business-analyse-develop**
|
|
224
283
|
|
|
@@ -151,6 +151,25 @@ Write via ba-writer.enrichModuleHandoff with the complete handoff payload:
|
|
|
151
151
|
- totalFiles, totalTasks, handedOffAt (ISO timestamp)
|
|
152
152
|
- featureDescription: {featureDescription}
|
|
153
153
|
|
|
154
|
+
### Specification Files (MANDATORY)
|
|
155
|
+
After writing the handoff data, ALSO write 5 companion files:
|
|
156
|
+
1. `.ralph/prd-{moduleCode}.entities.json` = VERBATIM copy of `{moduleDir}/entities.json`
|
|
157
|
+
2. `.ralph/prd-{moduleCode}.rules.json` = VERBATIM copy of `{moduleDir}/rules.json`
|
|
158
|
+
3. `.ralph/prd-{moduleCode}.usecases.json` = VERBATIM copy of `{moduleDir}/usecases.json`
|
|
159
|
+
4. `.ralph/prd-{moduleCode}.screens.json` = VERBATIM copy of `{moduleDir}/screens.json`
|
|
160
|
+
5. `.ralph/prd-{moduleCode}.permissions.json` = VERBATIM copy of `{moduleDir}/permissions.json`
|
|
161
|
+
|
|
162
|
+
Add `specificationFiles` to the PRD referencing these files (relative names, all in `.ralph/`):
|
|
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
|
+
|
|
154
173
|
### POST-CHECK (BLOCKING)
|
|
155
174
|
After writing, verify:
|
|
156
175
|
1. Handoff not empty
|
|
@@ -159,7 +178,12 @@ After writing, verify:
|
|
|
159
178
|
4. Section resources have entity field
|
|
160
179
|
5. SeedData contains CORE entries (NavigationModuleSeedData, NavigationSectionSeedData if sections exist, PermissionsSeedData, RolesSeedData)
|
|
161
180
|
6. For FIRST module only: SeedData contains APP-LEVEL CORE entries (NavigationApplicationSeedData, ApplicationRolesSeedData)
|
|
162
|
-
|
|
181
|
+
7. All 5 companion files exist in `.ralph/` and are non-empty
|
|
182
|
+
8. `specificationFiles` present in the PRD with all 5 paths
|
|
183
|
+
9. Entity count in companion matches source: `prd-{moduleCode}.entities.json` entities[] count = `{moduleDir}/entities.json` entities[] count
|
|
184
|
+
10. BR count in companion matches source: `prd-{moduleCode}.rules.json` rules[] count = `{moduleDir}/rules.json` rules[] count
|
|
185
|
+
11. Each `brToCodeMapping[].statement` is non-empty (not just a title paraphrase)
|
|
186
|
+
Display: "POST-CHECK PASS: {moduleCode} -- 8 categories, {brCount} BRs mapped, {coreCount} core seeds, 5 companion files"
|
|
163
187
|
```
|
|
164
188
|
|
|
165
189
|
### 3. Display Progress
|
|
@@ -91,6 +91,26 @@ for (const cat of categories) {
|
|
|
91
91
|
BLOCKING_ERROR(`${cat}: prd=${prdCount} but feature=${featureCount}`);
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
// Check 5: Specification files (companion files)
|
|
96
|
+
const specFiles = prd.specificationFiles;
|
|
97
|
+
if (!specFiles) {
|
|
98
|
+
BLOCKING_ERROR("specificationFiles missing from PRD");
|
|
99
|
+
}
|
|
100
|
+
const specKeys = ['entities', 'rules', 'usecases', 'screens', 'permissions'];
|
|
101
|
+
for (const key of specKeys) {
|
|
102
|
+
if (!specFiles[key]) {
|
|
103
|
+
BLOCKING_ERROR(`specificationFiles.${key} path missing`);
|
|
104
|
+
}
|
|
105
|
+
const companionPath = `.ralph/${specFiles[key]}`;
|
|
106
|
+
if (!fileExists(companionPath)) {
|
|
107
|
+
BLOCKING_ERROR(`Companion file not found: ${companionPath}`);
|
|
108
|
+
}
|
|
109
|
+
const companionSize = fileSize(companionPath);
|
|
110
|
+
if (companionSize < 10) {
|
|
111
|
+
BLOCKING_ERROR(`Companion file empty or too small: ${companionPath} (${companionSize} bytes)`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
94
114
|
```
|
|
95
115
|
|
|
96
116
|
Display verification table showing all 8 categories match between module JSON files and prd.json.
|
|
@@ -187,10 +207,15 @@ Readiness Scores:
|
|
|
187
207
|
└──────────────┴────────────┴──────────────┘
|
|
188
208
|
|
|
189
209
|
Artifacts generated:
|
|
190
|
-
.ralph/prd-{module}.json
|
|
191
|
-
.ralph/
|
|
192
|
-
.ralph/
|
|
193
|
-
|
|
210
|
+
.ralph/prd-{module}.json — Task breakdown per module
|
|
211
|
+
.ralph/prd-{module}.entities.json — Entity specifications (companion)
|
|
212
|
+
.ralph/prd-{module}.rules.json — Business rules (companion)
|
|
213
|
+
.ralph/prd-{module}.usecases.json — Use cases (companion)
|
|
214
|
+
.ralph/prd-{module}.screens.json — Screen specifications (companion)
|
|
215
|
+
.ralph/prd-{module}.permissions.json — Permissions (companion)
|
|
216
|
+
.ralph/progress.txt — Progression tracker
|
|
217
|
+
.ralph/modules-queue.json — Module execution order (if multi-module)
|
|
218
|
+
docs/index.json — BA manifest (status: handed-off)
|
|
194
219
|
|
|
195
220
|
[NEXT] Development:
|
|
196
221
|
1. Run /business-analyse-develop to begin implementation
|
|
@@ -210,5 +235,8 @@ Artifacts generated:
|
|
|
210
235
|
4. `docs/index.json` updated with correct entry count and status "handed-off"
|
|
211
236
|
5. All PRD files have $version=3.0.0 or 4.0.0, file manifest present, 8 categories
|
|
212
237
|
6. File counts match between PRD and handoff for all categories
|
|
238
|
+
7. For EACH module: 5 companion files exist in `.ralph/` (entities, rules, usecases, screens, permissions)
|
|
239
|
+
8. Each PRD contains `specificationFiles` with all 5 paths
|
|
240
|
+
9. Each companion file is non-empty (>10 bytes)
|
|
213
241
|
|
|
214
242
|
**IF any check fails -> fix before completing.**
|