@atlashub/smartstack-cli 4.47.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-design/steps/step-02-wireframes.md +14 -0
- package/templates/skills/business-analyse-develop/references/compact-loop.md +15 -1
- package/templates/skills/business-analyse-develop/references/module-transition.md +10 -0
- package/templates/skills/business-analyse-develop/references/quality-gates.md +91 -1
- package/templates/skills/business-analyse-develop/steps/step-00-init.md +57 -0
- package/templates/skills/business-analyse-develop/steps/step-01-task.md +151 -2
- package/templates/skills/business-analyse-develop/steps/step-02-execute.md +14 -1
- package/templates/skills/business-analyse-develop/steps/step-02-v4-verify.md +22 -2
- package/templates/skills/business-analyse-develop/steps/step-03-commit.md +1 -1
- package/templates/skills/business-analyse-develop/steps/step-04-check.md +4 -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 +100 -0
- package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +27 -1
- package/templates/skills/business-analyse-handoff/steps/step-02-export.md +38 -8
- 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/business-analyse-html/steps/step-02-build-data.md +26 -5
- 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
|
}
|
|
@@ -127,6 +127,20 @@ For each module, update screens.json to embed wireframes at the resource level:
|
|
|
127
127
|
|
|
128
128
|
Write via ba-writer for each module. Update index.json hashes.
|
|
129
129
|
|
|
130
|
+
## Consumer Contract (wireframe field names)
|
|
131
|
+
|
|
132
|
+
> **IMPORTANT:** Downstream consumers (`/business-analyse-html`) depend on these EXACT field names.
|
|
133
|
+
> Any rename requires updating `business-analyse-html/steps/step-02-build-data.md` (wireframe mapping).
|
|
134
|
+
|
|
135
|
+
| Field | Type | Description |
|
|
136
|
+
|-------|------|-------------|
|
|
137
|
+
| `mockupFormat` | `"ascii"` \| `"html"` | Wireframe rendering format |
|
|
138
|
+
| `mockup` | string | Wireframe content (ASCII art or HTML) |
|
|
139
|
+
| `elements` | string[] | UI element names |
|
|
140
|
+
| `componentMapping` | object[] | Wireframe element → React component mapping |
|
|
141
|
+
| `layout` | object | Layout type and regions |
|
|
142
|
+
| `permissionsRequired` | string[] | Permission paths needed |
|
|
143
|
+
|
|
130
144
|
## Validation
|
|
131
145
|
|
|
132
146
|
- [ ] Every resource has a wireframe object
|
|
@@ -22,7 +22,7 @@ const queuePath = '.ralph/modules-queue.json';
|
|
|
22
22
|
let currentPrdPath = '.ralph/prd.json';
|
|
23
23
|
if (fileExists(queuePath)) {
|
|
24
24
|
const queue = readJSON(queuePath);
|
|
25
|
-
currentPrdPath = queue.
|
|
25
|
+
currentPrdPath = queue.modules[queue.currentIndex].prdFile;
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
@@ -226,6 +226,20 @@ for (const task of batch) {
|
|
|
226
226
|
writeJSON(currentPrdPath, prd);
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
+
### B1b. State Persistence (before delegation)
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
// Write state BEFORE delegating to apex so position survives context compression
|
|
233
|
+
writeJSON('.ralph/ralph-state.json', {
|
|
234
|
+
currentStep: 'compact-loop',
|
|
235
|
+
nextStep: 'step-04-check',
|
|
236
|
+
currentModule: {current_module},
|
|
237
|
+
phase: 'apex-delegating',
|
|
238
|
+
iteration: prdCheck?.config?.current_iteration || 'unknown',
|
|
239
|
+
timestamp: new Date().toISOString()
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
229
243
|
### B2. Invoke /apex
|
|
230
244
|
|
|
231
245
|
**INVOKE `/apex -d {currentPrdPath}`**
|
|
@@ -122,11 +122,21 @@ if (missing.length === 0) {
|
|
|
122
122
|
// CONTEXT REFRESH: Force /compact between modules to prevent context degradation
|
|
123
123
|
// (audit ba-002: quality degraded progressively — module 2 had more errors than module 1
|
|
124
124
|
// because patterns from module 1 were lost during context compaction)
|
|
125
|
+
// STATE PERSISTENCE: Write state BEFORE compaction so position survives context compression
|
|
126
|
+
writeJSON('.ralph/ralph-state.json', {
|
|
127
|
+
currentStep: 'step-01-task',
|
|
128
|
+
currentModule: queue.modules[nextIndex].code,
|
|
129
|
+
reason: 'module-transition',
|
|
130
|
+
fromModule: currentModule.code,
|
|
131
|
+
timestamp: new Date().toISOString()
|
|
132
|
+
});
|
|
133
|
+
|
|
125
134
|
console.log('Forcing /compact for fresh context before next module...');
|
|
126
135
|
// The /compact command clears accumulated tool results and intermediate reasoning,
|
|
127
136
|
// preserving only key decisions and the current state. This ensures the next module
|
|
128
137
|
// starts with a clean context window instead of degraded fragments from the previous module.
|
|
129
138
|
// INVOKE /compact
|
|
139
|
+
// After compaction: read .ralph/ralph-state.json to recover position if context lost
|
|
130
140
|
|
|
131
141
|
// Return to step-01 to load next module's tasks
|
|
132
142
|
// (step-01 will detect module-changed.json and load next PRD)
|
|
@@ -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.
|
|
@@ -91,6 +91,51 @@ See `references/multi-module-queue.md` for queue initialization.
|
|
|
91
91
|
|
|
92
92
|
Quick: `glob('.ralph/prd-*.json').length > 1` → create modules-queue.json
|
|
93
93
|
|
|
94
|
+
### 4b+. Normalize modules-queue.json (backward-compat with handoff v1 format)
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
const queuePath = '.ralph/modules-queue.json';
|
|
98
|
+
if (fileExists(queuePath)) {
|
|
99
|
+
const raw = readJSON(queuePath);
|
|
100
|
+
let dirty = false;
|
|
101
|
+
|
|
102
|
+
// RC1: Rename "queue" → "modules" property (handoff v1 used "queue")
|
|
103
|
+
if (raw.queue && !raw.modules) {
|
|
104
|
+
raw.modules = raw.queue;
|
|
105
|
+
delete raw.queue;
|
|
106
|
+
dirty = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// RC2: Rename "moduleCode" → "code" in each entry (handoff v1 used "moduleCode")
|
|
110
|
+
for (const mod of (raw.modules || [])) {
|
|
111
|
+
if (mod.moduleCode && !mod.code) {
|
|
112
|
+
mod.code = mod.moduleCode;
|
|
113
|
+
delete mod.moduleCode;
|
|
114
|
+
dirty = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// RC3: Add missing fields
|
|
119
|
+
if (raw.currentIndex === undefined) { raw.currentIndex = 0; dirty = true; }
|
|
120
|
+
if (raw.completedModules === undefined) { raw.completedModules = 0; dirty = true; }
|
|
121
|
+
if (raw.totalModules === undefined) { raw.totalModules = raw.modules?.length || 0; dirty = true; }
|
|
122
|
+
for (const mod of (raw.modules || [])) {
|
|
123
|
+
if (!mod.prdFile) { mod.prdFile = `.ralph/prd-${mod.code}.json`; dirty = true; }
|
|
124
|
+
if (!mod.status) { mod.status = 'pending'; dirty = true; }
|
|
125
|
+
}
|
|
126
|
+
// Set first module to in-progress if none started
|
|
127
|
+
if (raw.modules?.length && !raw.modules.some(m => m.status !== 'pending')) {
|
|
128
|
+
raw.modules[0].status = 'in-progress';
|
|
129
|
+
dirty = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (dirty) {
|
|
133
|
+
writeJSON(queuePath, raw);
|
|
134
|
+
console.log('modules-queue.json normalized (format v2)');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
94
139
|
---
|
|
95
140
|
|
|
96
141
|
## 4c. Team Orchestration (multi-module LEGACY — dependency layer mode only)
|
|
@@ -175,4 +220,16 @@ MCP: Ready | Branch: {branch} | PRD: v{prd_version}
|
|
|
175
220
|
-> Loading spec...
|
|
176
221
|
```
|
|
177
222
|
|
|
223
|
+
### 7b. Initialize State File (context compression defense)
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
writeJSON('.ralph/ralph-state.json', {
|
|
227
|
+
currentStep: 'step-01-task',
|
|
228
|
+
currentModule: {current_module},
|
|
229
|
+
iteration: 1,
|
|
230
|
+
prdVersion: {prd_version},
|
|
231
|
+
timestamp: new Date().toISOString()
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
178
235
|
**Proceed directly to step-01-task.md**
|
|
@@ -6,6 +6,9 @@ next_step: steps/step-02-execute.md
|
|
|
6
6
|
|
|
7
7
|
# Step 1: Load Task
|
|
8
8
|
|
|
9
|
+
> **STATE RECOVERY:** If you are unsure which step you are executing (e.g., after context compression),
|
|
10
|
+
> read `.ralph/ralph-state.json` to recover your position.
|
|
11
|
+
|
|
9
12
|
> **MODULE TRANSITION CHECK:** Before "Only Read Once" rule, check for module transition:
|
|
10
13
|
|
|
11
14
|
```javascript
|
|
@@ -52,7 +55,7 @@ const queuePath = '.ralph/modules-queue.json';
|
|
|
52
55
|
let currentPrdPath = '.ralph/prd.json';
|
|
53
56
|
if (fileExists(queuePath)) {
|
|
54
57
|
const queue = readJSON(queuePath);
|
|
55
|
-
currentPrdPath = queue.
|
|
58
|
+
currentPrdPath = queue.modules[queue.currentIndex].prdFile;
|
|
56
59
|
}
|
|
57
60
|
```
|
|
58
61
|
|
|
@@ -63,6 +66,149 @@ If `{currentPrdPath}` exists:
|
|
|
63
66
|
- Write back to `{currentPrdPath}`
|
|
64
67
|
- **Run CATEGORY COMPLETENESS CHECK (section 4b) before proceeding**
|
|
65
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)
|
|
66
212
|
3. **v2 legacy:** If `$version === "2.0.0"` → find next eligible task (section 5)
|
|
67
213
|
4. **FORMAT A (deprecated):** If `.project && .requirements && !.$version` → run `transformPrdJsonToRalphV2()` → section 5
|
|
68
214
|
|
|
@@ -117,7 +263,10 @@ Check for BA handoff source (priority):
|
|
|
117
263
|
3. Markdown handoff: `find . -path "*development-handoff*" -name "*.md"`
|
|
118
264
|
4. Direct task from `{task_description}`
|
|
119
265
|
|
|
120
|
-
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).
|
|
121
270
|
|
|
122
271
|
## 3. Create prd.json
|
|
123
272
|
|
|
@@ -35,7 +35,7 @@ const queuePath = '.ralph/modules-queue.json';
|
|
|
35
35
|
let currentPrdPath = '.ralph/prd.json';
|
|
36
36
|
if (fileExists(queuePath)) {
|
|
37
37
|
const queue = readJSON(queuePath);
|
|
38
|
-
currentPrdPath = queue.
|
|
38
|
+
currentPrdPath = queue.modules[queue.currentIndex].prdFile;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const prd = readJSON(currentPrdPath);
|
|
@@ -75,6 +75,19 @@ See `references/parallel-execution.md` for complete parallel protocol:
|
|
|
75
75
|
|
|
76
76
|
Continue with section-split mode or standard mode below.
|
|
77
77
|
|
|
78
|
+
#### 3a+. State Persistence (before delegation)
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// Write state BEFORE delegating to apex so position survives context compression
|
|
82
|
+
writeJSON('.ralph/ralph-state.json', {
|
|
83
|
+
currentStep: 'step-02-execute',
|
|
84
|
+
nextStep: 'step-03-commit',
|
|
85
|
+
currentModule: {current_module},
|
|
86
|
+
phase: 'apex-delegating',
|
|
87
|
+
timestamp: new Date().toISOString()
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
78
91
|
#### 3b. Section-Split Mode
|
|
79
92
|
|
|
80
93
|
```javascript
|
|
@@ -12,6 +12,19 @@ After ALL modules are implemented, run final verification across the ENTIRE proj
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
+
## VERSION GUARD
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// This step is ONLY for v4 PRDs. If a v3 PRD reaches here, route back to step-05-report.md.
|
|
19
|
+
const prd = readJSON(currentPrdPath);
|
|
20
|
+
if (prd.$version !== '4.0.0') {
|
|
21
|
+
console.error(`step-02-v4-verify loaded with $version=${prd.$version} — expected 4.0.0`);
|
|
22
|
+
console.log('Routing to step-05-report.md (v3 report generation)');
|
|
23
|
+
// → Load steps/step-05-report.md instead
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
15
28
|
## FINAL VERIFICATION SEQUENCE
|
|
16
29
|
|
|
17
30
|
### 1. Full Solution Build
|
|
@@ -39,11 +52,18 @@ npm test -- --run 2>&1
|
|
|
39
52
|
|
|
40
53
|
### 4. File Reconciliation
|
|
41
54
|
|
|
42
|
-
For each module's PRD, compare
|
|
55
|
+
For each module's PRD, compare expected files against actual files on disk:
|
|
43
56
|
|
|
44
57
|
```javascript
|
|
58
|
+
// v4 uses prd.expectedFiles, v3 uses prd.implementation.filesToCreate (guard: should be v4 here)
|
|
59
|
+
const fileManifest = prd.expectedFiles || prd.implementation?.filesToCreate;
|
|
60
|
+
if (!fileManifest) {
|
|
61
|
+
console.error('BLOCKING: PRD has no file manifest (neither expectedFiles nor implementation.filesToCreate)');
|
|
62
|
+
STOP;
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
for (const category of ['domain', 'application', 'infrastructure', 'api', 'frontend', 'seedData', 'tests', 'documentation']) {
|
|
46
|
-
const expected =
|
|
66
|
+
const expected = fileManifest[category] || [];
|
|
47
67
|
for (const file of expected) {
|
|
48
68
|
if (!existsOnDisk(file.path)) {
|
|
49
69
|
MISSING.push({ category, path: file.path, type: file.type });
|
|
@@ -27,7 +27,7 @@ const queuePath = '.ralph/modules-queue.json';
|
|
|
27
27
|
let currentPrdPath = '.ralph/prd.json';
|
|
28
28
|
if (fileExists(queuePath)) {
|
|
29
29
|
const queue = readJSON(queuePath);
|
|
30
|
-
currentPrdPath = queue.
|
|
30
|
+
currentPrdPath = queue.modules[queue.currentIndex].prdFile;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const prd = readJSON(currentPrdPath);
|
|
@@ -6,6 +6,9 @@ next_step: steps/step-05-report.md OR steps/step-01-task.md
|
|
|
6
6
|
|
|
7
7
|
# Step 4: Check Completion
|
|
8
8
|
|
|
9
|
+
> **STATE RECOVERY:** If you are unsure which step you are executing (e.g., after context compression),
|
|
10
|
+
> read `.ralph/ralph-state.json` to recover your position.
|
|
11
|
+
|
|
9
12
|
## YOUR TASK:
|
|
10
13
|
|
|
11
14
|
Check if all tasks are complete and decide: output completion promise, advance module, or continue loop.
|
|
@@ -20,7 +23,7 @@ const queuePath = '.ralph/modules-queue.json';
|
|
|
20
23
|
let currentPrdPath = '.ralph/prd.json';
|
|
21
24
|
if (fileExists(queuePath)) {
|
|
22
25
|
const queue = readJSON(queuePath);
|
|
23
|
-
currentPrdPath = queue.
|
|
26
|
+
currentPrdPath = queue.modules[queue.currentIndex].prdFile;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
const prd = readJSON(currentPrdPath);
|