@atlashub/smartstack-cli 4.25.0 → 4.26.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/dist/index.js +765 -517
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/agents/ba-writer.md +7 -3
- package/templates/skills/ba-generate-html/references/data-build.md +22 -13
- package/templates/skills/ba-generate-html/references/data-mapping.md +33 -24
- package/templates/skills/ba-generate-html/steps/step-01-collect.md +15 -6
- package/templates/skills/ba-generate-html/steps/step-02-build-data.md +37 -22
- package/templates/skills/business-analyse/steps/step-00-init.md +9 -11
- package/templates/skills/business-analyse/steps/step-04-consolidate.md +15 -0
- package/templates/skills/derive-prd/steps/step-01-transform.md +6 -2
- package/templates/skills/derive-prd/steps/step-02-export.md +12 -0
- package/templates/skills/ralph-loop/references/category-completeness.md +3 -3
- package/templates/skills/ralph-loop/references/compact-loop.md +47 -11
- package/templates/skills/ralph-loop/references/init-resume-recovery.md +1 -1
- package/templates/skills/ralph-loop/references/module-transition.md +30 -5
- package/templates/skills/ralph-loop/references/multi-module-queue.md +4 -4
- package/templates/skills/ralph-loop/references/section-splitting.md +6 -6
- package/templates/skills/ralph-loop/steps/step-01-task.md +14 -4
- package/templates/skills/ralph-loop/steps/step-02-execute.md +14 -6
- package/templates/skills/ralph-loop/steps/step-03-commit.md +15 -4
- package/templates/skills/ralph-loop/steps/step-04-check.md +19 -5
- package/templates/skills/ralph-loop/steps/step-05-report.md +35 -3
package/package.json
CHANGED
|
@@ -63,8 +63,11 @@ Create initial index.json and empty thematic files with metadata and draft statu
|
|
|
63
63
|
- For application scope: modules: []
|
|
64
64
|
- For module scope: (no modules array)
|
|
65
65
|
5. Create empty thematic files appropriate for scope:
|
|
66
|
-
- Project/application: cadrage.json,
|
|
67
|
-
|
|
66
|
+
- Project/application: cadrage.json, validation.json, handoff.json, consolidation.json
|
|
67
|
+
> **NOTE:** Do NOT create entities.json, rules.json, usecases.json, permissions.json, screens.json at project/application level.
|
|
68
|
+
> These thematic files contain module-specific data and are created ONLY at module level by step-03 (specify).
|
|
69
|
+
> Creating empty versions at app level causes downstream tools (derive-prd) to read empty arrays instead of module data.
|
|
70
|
+
- Module: entities.json, rules.json, usecases.json, permissions.json, screens.json, validation.json, handoff.json
|
|
68
71
|
6. Update `.business-analyse/config.json` with new lastFeatureId
|
|
69
72
|
7. IF scope = "module" AND applicationRef provided AND moduleCode provided:
|
|
70
73
|
a. Read master index.json (via applicationRef FEAT-NNN)
|
|
@@ -107,7 +110,8 @@ Create a project-level index.json for multi-application analysis. Only used when
|
|
|
107
110
|
- fileHashes: {}
|
|
108
111
|
- applications: []
|
|
109
112
|
- applicationDependencyGraph: {}
|
|
110
|
-
5. Create thematic files (cadrage.json,
|
|
113
|
+
5. Create thematic files (cadrage.json, validation.json, consolidation.json)
|
|
114
|
+
> Do NOT create entities/rules/usecases/permissions/screens at project level — these exist only at module level.
|
|
111
115
|
6. Update `.business-analyse/config.json` with new lastProjectId
|
|
112
116
|
7. Deploy schemas to `docs/business-analyse/schemas/` (including project-schema.json)
|
|
113
117
|
8. Return project ID (PROJ-NNN) and path
|
|
@@ -63,11 +63,13 @@ const FEATURE_DATA = {
|
|
|
63
63
|
],
|
|
64
64
|
moduleSpecs: {
|
|
65
65
|
// CRITICAL: Must have ONE entry per module with ALL module data
|
|
66
|
+
// FLAT-FILE: Data comes from collected_data.modules[moduleCode] (flat files), NOT module index.json
|
|
66
67
|
"{moduleCode}": {
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
// const mod = collected_data.modules[moduleCode];
|
|
69
|
+
useCases: mod.usecases?.useCases || [],
|
|
70
|
+
businessRules: mod.rules?.rules || [],
|
|
69
71
|
// ENTITY SAFETY NET: map fields[] → attributes[] if agent deviated from canonical format
|
|
70
|
-
entities: (
|
|
72
|
+
entities: (mod.entities?.entities || []).map(ent => ({
|
|
71
73
|
name: ent.name,
|
|
72
74
|
description: ent.description || "",
|
|
73
75
|
attributes: (ent.attributes || []).length > 0
|
|
@@ -79,8 +81,8 @@ const FEATURE_DATA = {
|
|
|
79
81
|
: (ent.fields || []).filter(f => f.foreignKey)
|
|
80
82
|
.map(f => `${f.foreignKey.table} (N:1) - ${f.description || f.name}`)
|
|
81
83
|
})),
|
|
82
|
-
permissions:
|
|
83
|
-
apiEndpoints:
|
|
84
|
+
permissions: mod.permissions?.matrix || mod.permissions?.permissions || [],
|
|
85
|
+
apiEndpoints: mod.usecases?.apiEndpoints || []
|
|
84
86
|
}
|
|
85
87
|
},
|
|
86
88
|
consolidation: {
|
|
@@ -102,10 +104,15 @@ const FEATURE_DATA = {
|
|
|
102
104
|
### Build Process
|
|
103
105
|
|
|
104
106
|
1. Extract metadata from master index.json
|
|
105
|
-
2. Extract cadrage from master
|
|
106
|
-
3. Extract stakeholders from master.stakeholders
|
|
107
|
+
2. Extract cadrage from master cadrage.json (CONVERT scope keys)
|
|
108
|
+
3. Extract stakeholders from master cadrage.stakeholders
|
|
107
109
|
4. Iterate ALL modules and populate moduleSpecs (THIS IS CRITICAL — empty moduleSpecs = BUG)
|
|
108
|
-
5. For EACH module,
|
|
110
|
+
5. For EACH module, read FLAT FILES from `collected_data.modules[moduleCode]`:
|
|
111
|
+
- `entities.json` → entities
|
|
112
|
+
- `rules.json` → businessRules
|
|
113
|
+
- `usecases.json` → useCases
|
|
114
|
+
- `permissions.json` → permissions
|
|
115
|
+
- `screens.json` → wireframes
|
|
109
116
|
6. Extract consolidation data (integrations, shared entities, E2E flows)
|
|
110
117
|
7. Extract handoff section (complexity, strategy, module order, file counts)
|
|
111
118
|
|
|
@@ -119,10 +126,12 @@ const FEATURE_DATA = {
|
|
|
119
126
|
const EMBEDDED_ARTIFACTS = {
|
|
120
127
|
wireframes: {
|
|
121
128
|
// PER-MODULE keyed object (NOT a flat array)
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
[moduleCode]:
|
|
129
|
+
// FLAT-FILE: Read from screens.json in each module directory (collected in step-01)
|
|
130
|
+
// const mod = collected_data.modules[moduleCode];
|
|
131
|
+
// const screens = mod.screens?.screens || [];
|
|
132
|
+
[moduleCode]: screens
|
|
133
|
+
.filter(s => s.wireframe || s.mockup || s.mockupFormat)
|
|
134
|
+
.map(wf => ({
|
|
126
135
|
screen: wf.screen || wf.name || wf.title || wf.id || "", // SAFETY NET: fallback name/title/id → screen
|
|
127
136
|
section: wf.section || "", // e.g. "list"
|
|
128
137
|
format: wf.mockupFormat || "ascii", // RENAME: mockupFormat → format
|
|
@@ -160,7 +169,7 @@ const EMBEDDED_ARTIFACTS = {
|
|
|
160
169
|
|
|
161
170
|
### Artifact Gathering
|
|
162
171
|
|
|
163
|
-
1. For EACH module: read `
|
|
172
|
+
1. For EACH module: read `screens.json` flat file from `collected_data.modules[moduleCode].screens`, filter screens with wireframe data, **rename fields** (`mockupFormat`→`format`, `mockup`/`ascii`/`content`→`content`, `screen`/`name`/`title`→`screen`), store under `wireframes[moduleCode]`
|
|
164
173
|
2. Read master's `consolidation.e2eFlows[]` and build e2eFlows array with diagram generation
|
|
165
174
|
3. Read master's `dependencyGraph` and build nodes/edges
|
|
166
175
|
4. Serialize as JSON with 2-space indentation
|
|
@@ -93,29 +93,32 @@ Build a JSON object following this **exact mapping** from index.json to the HTML
|
|
|
93
93
|
}
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
### Module Specs Mapping
|
|
96
|
+
### Module Specs Mapping (FLAT-FILE ARCHITECTURE)
|
|
97
97
|
|
|
98
98
|
For EACH module in `master.modules[]`:
|
|
99
99
|
|
|
100
|
-
1.
|
|
101
|
-
2.
|
|
100
|
+
1. Use flat file data from `collected_data.modules[moduleCode]` (loaded in step-01)
|
|
101
|
+
2. Data sources: `entities.json`, `rules.json`, `usecases.json`, `permissions.json`, `screens.json`
|
|
102
|
+
3. Do NOT read from `moduleIndex.specification` or `moduleIndex.analysis` — these don't exist in flat-file format
|
|
102
103
|
|
|
103
104
|
```javascript
|
|
105
|
+
const mod = collected_data.modules[moduleCode];
|
|
106
|
+
|
|
104
107
|
moduleSpecs[moduleCode] = {
|
|
105
|
-
useCases: (
|
|
108
|
+
useCases: (mod.usecases?.useCases || []).map(uc => ({
|
|
106
109
|
name: uc.name,
|
|
107
110
|
actor: uc.primaryActor,
|
|
108
111
|
steps: (uc.mainScenario || []).join("\n"), // array → newline-separated string
|
|
109
112
|
alternative: (uc.alternativeScenarios || [])
|
|
110
113
|
.map(a => a.name + ": " + (a.steps || []).join(", ")).join("\n")
|
|
111
114
|
})),
|
|
112
|
-
businessRules: (
|
|
115
|
+
businessRules: (mod.rules?.rules || []).map(br => ({
|
|
113
116
|
name: br.name,
|
|
114
117
|
category: br.category, // "validation"|"calculation"|"workflow"|"security"|"data"
|
|
115
118
|
statement: br.statement,
|
|
116
119
|
example: (br.examples || []).map(e => e.input + " → " + e.expected).join("; ")
|
|
117
120
|
})),
|
|
118
|
-
entities: (
|
|
121
|
+
entities: (mod.entities?.entities || []).map(ent => ({
|
|
119
122
|
name: ent.name,
|
|
120
123
|
description: ent.description || "",
|
|
121
124
|
attributes: (ent.attributes || []).map(a => ({
|
|
@@ -128,7 +131,7 @@ moduleSpecs[moduleCode] = {
|
|
|
128
131
|
r.target + " (" + r.type + ") - " + (r.description || "")
|
|
129
132
|
)
|
|
130
133
|
})),
|
|
131
|
-
permissions: buildPermissionKeys(
|
|
134
|
+
permissions: buildPermissionKeys(mod.permissions), // see below
|
|
132
135
|
notes: "",
|
|
133
136
|
mockupNotes: "" // Deprecated: wireframes now embedded separately in EMBEDDED_ARTIFACTS
|
|
134
137
|
}
|
|
@@ -139,9 +142,10 @@ moduleSpecs[moduleCode] = {
|
|
|
139
142
|
The HTML uses `"Role|Action"` format (e.g. `"RH Admin|Consulter"`):
|
|
140
143
|
|
|
141
144
|
```javascript
|
|
142
|
-
|
|
145
|
+
// Input: permissions object from flat file (mod.permissions), NOT moduleFeature
|
|
146
|
+
function buildPermissionKeys(permissionsData) {
|
|
143
147
|
const keys = [];
|
|
144
|
-
const matrix =
|
|
148
|
+
const matrix = permissionsData?.matrix || permissionsData?.permissionMatrix;
|
|
145
149
|
if (!matrix) return keys;
|
|
146
150
|
const actionMap = { read: "Consulter", create: "Creer", update: "Modifier",
|
|
147
151
|
delete: "Supprimer", validate: "Valider", export: "Exporter",
|
|
@@ -207,25 +211,30 @@ Build a JSON object containing ALL visual artifacts from module JSON files (inde
|
|
|
207
211
|
}
|
|
208
212
|
```
|
|
209
213
|
|
|
210
|
-
### Wireframes Mapping
|
|
214
|
+
### Wireframes Mapping (FLAT-FILE: from screens.json)
|
|
211
215
|
|
|
212
216
|
For EACH module in `master.modules[]`:
|
|
213
217
|
|
|
214
218
|
```javascript
|
|
215
|
-
//
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
219
|
+
// Use flat file data from collected_data (loaded in step-01)
|
|
220
|
+
const mod = collected_data.modules[moduleCode];
|
|
221
|
+
const screens = mod.screens?.screens || [];
|
|
222
|
+
|
|
223
|
+
// Extract wireframes from screens that have mockup/wireframe data
|
|
224
|
+
const wireframes = screens
|
|
225
|
+
.filter(s => s.wireframe || s.mockup || s.mockupFormat)
|
|
226
|
+
.map(wf => ({
|
|
227
|
+
screen: wf.screen || wf.name || wf.id || "", // e.g. "UM-list", "UM-form"
|
|
228
|
+
section: wf.section || "", // e.g. "list", "form"
|
|
229
|
+
format: wf.mockupFormat || "ascii", // "ascii" | "svg"
|
|
230
|
+
content: wf.mockup || wf.ascii || wf.content || "", // ASCII art or SVG markup
|
|
231
|
+
description: wf.description || "",
|
|
232
|
+
elements: wf.elements || [], // ["DataGrid", "FilterBar", ...]
|
|
233
|
+
actions: wf.actions || [], // ["filter", "sort", "create", ...]
|
|
234
|
+
componentMapping: wf.componentMapping || [], // [{ wireframeElement, reactComponent }]
|
|
235
|
+
layout: wf.layout || null, // { type, regions: [...] }
|
|
236
|
+
permissionsRequired: wf.permissionsRequired || []
|
|
237
|
+
}));
|
|
229
238
|
|
|
230
239
|
// Store in artifacts object
|
|
231
240
|
EMBEDDED_ARTIFACTS.wireframes[moduleCode] = wireframes;
|
|
@@ -51,7 +51,11 @@ ELSE:
|
|
|
51
51
|
BLOCKING ERROR: "Feature status: {feature.status}, expected consolidated"
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
### 4. Read All JSON Files
|
|
54
|
+
### 4. Read All JSON Files (FLAT-FILE ARCHITECTURE)
|
|
55
|
+
|
|
56
|
+
> **CRITICAL:** Module data lives in separate flat files (entities.json, usecases.json, etc.),
|
|
57
|
+
> NOT inside index.json. The module index.json only contains metadata and file pointers.
|
|
58
|
+
> You MUST read each flat file individually.
|
|
55
59
|
|
|
56
60
|
```
|
|
57
61
|
collected_data = {
|
|
@@ -63,13 +67,18 @@ collected_data = {
|
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
FOR each module in master.index.modules[]:
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
// Resolve module directory path
|
|
71
|
+
moduleDir = "{app}/{module.code}/business-analyse/v{version}/"
|
|
72
|
+
moduleIndex = READ(moduleDir + "index.json")
|
|
68
73
|
|
|
74
|
+
// Read ALL flat files from module directory
|
|
69
75
|
collected_data.modules[module.code] = {
|
|
70
76
|
index: moduleIndex,
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
entities: READ(moduleDir + "entities.json") || { entities: [] },
|
|
78
|
+
rules: READ(moduleDir + "rules.json") || { rules: [] },
|
|
79
|
+
usecases: READ(moduleDir + "usecases.json") || { useCases: [] },
|
|
80
|
+
permissions: READ(moduleDir + "permissions.json") || { roles: [], permissions: [], matrix: [] },
|
|
81
|
+
screens: READ(moduleDir + "screens.json") || { screens: [] }
|
|
73
82
|
}
|
|
74
83
|
```
|
|
75
84
|
|
|
@@ -78,7 +87,7 @@ FOR each module in master.index.modules[]:
|
|
|
78
87
|
```
|
|
79
88
|
JSON collected:
|
|
80
89
|
Master: index.json + cadrage.json
|
|
81
|
-
Modules: {module_count}
|
|
90
|
+
Modules: {module_count} modules (flat files: entities, rules, usecases, permissions, screens)
|
|
82
91
|
→ Proceeding to data build...
|
|
83
92
|
```
|
|
84
93
|
|
|
@@ -89,24 +89,29 @@ modules: master.modules.map(m => ({
|
|
|
89
89
|
|
|
90
90
|
**moduleSpecs{} — ONE entry per module (CRITICAL):**
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
> **FLAT-FILE ARCHITECTURE:** Module data comes from separate JSON files collected in step-01,
|
|
93
|
+
> NOT from index.json fields. Use `collected_data.modules[moduleCode].entities`, `.usecases`, etc.
|
|
94
|
+
|
|
95
|
+
For EACH module, use the flat file data from `collected_data.modules[moduleCode]`:
|
|
93
96
|
|
|
94
97
|
```javascript
|
|
98
|
+
const mod = collected_data.modules[moduleCode];
|
|
99
|
+
|
|
95
100
|
moduleSpecs[moduleCode] = {
|
|
96
|
-
useCases: (
|
|
101
|
+
useCases: (mod.usecases?.useCases || []).map(uc => ({
|
|
97
102
|
name: uc.name,
|
|
98
103
|
actor: uc.primaryActor,
|
|
99
104
|
steps: (uc.mainScenario || []).join("\n"),
|
|
100
105
|
alternative: (uc.alternativeScenarios || []).map(a => a.name + ": " + (a.steps || []).join(", ")).join("\n")
|
|
101
106
|
})),
|
|
102
|
-
businessRules: (
|
|
107
|
+
businessRules: (mod.rules?.rules || []).map(br => ({
|
|
103
108
|
name: br.name,
|
|
104
109
|
category: br.category,
|
|
105
110
|
statement: br.statement,
|
|
106
111
|
example: (br.examples || []).map(e => e.input + " → " + e.expected).join("; ")
|
|
107
112
|
})),
|
|
108
113
|
// ENTITY SAFETY NET: map fields[] → attributes[] if agent deviated
|
|
109
|
-
entities: (
|
|
114
|
+
entities: (mod.entities?.entities || []).map(ent => ({
|
|
110
115
|
name: ent.name,
|
|
111
116
|
description: ent.description || "",
|
|
112
117
|
attributes: (ent.attributes || []).length > 0
|
|
@@ -115,8 +120,8 @@ moduleSpecs[moduleCode] = {
|
|
|
115
120
|
relationships: (ent.relationships || []).map(r =>
|
|
116
121
|
typeof r === 'string' ? r : r.target + " (" + r.type + ") - " + (r.description || ""))
|
|
117
122
|
})),
|
|
118
|
-
permissions: buildPermissionKeys(
|
|
119
|
-
apiEndpoints:
|
|
123
|
+
permissions: buildPermissionKeys(mod.permissions),
|
|
124
|
+
apiEndpoints: mod.usecases?.apiEndpoints || []
|
|
120
125
|
}
|
|
121
126
|
```
|
|
122
127
|
|
|
@@ -158,24 +163,34 @@ handoff: {
|
|
|
158
163
|
> **FIELD RENAME WARNING:** Module JSON uses `mockupFormat`/`mockup`. HTML reads `format`/`content`. You MUST rename.
|
|
159
164
|
|
|
160
165
|
**wireframes{} — per moduleCode:**
|
|
166
|
+
|
|
167
|
+
> **FLAT-FILE:** Wireframes come from `screens.json` in each module directory (collected in step-01),
|
|
168
|
+
> NOT from `moduleFeature.specification.uiWireframes`.
|
|
169
|
+
|
|
161
170
|
```javascript
|
|
171
|
+
const mod = collected_data.modules[moduleCode];
|
|
172
|
+
const screens = mod.screens?.screens || [];
|
|
173
|
+
|
|
174
|
+
// Extract wireframes from screens that have mockup/wireframe data
|
|
162
175
|
wireframes: {
|
|
163
|
-
[moduleCode]:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
:
|
|
174
|
-
|
|
175
|
-
:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
[moduleCode]: screens
|
|
177
|
+
.filter(s => s.wireframe || s.mockup || s.mockupFormat)
|
|
178
|
+
.map(wf => ({
|
|
179
|
+
screen: wf.screen || wf.name || wf.title || wf.id || "",
|
|
180
|
+
section: wf.section || "",
|
|
181
|
+
format: wf.mockupFormat || "ascii", // RENAME: mockupFormat → format
|
|
182
|
+
content: wf.mockup || wf.ascii || wf.content || "", // RENAME: mockup → content
|
|
183
|
+
svgContent: null,
|
|
184
|
+
description: wf.description || "",
|
|
185
|
+
elements: wf.elements || [],
|
|
186
|
+
actions: wf.actions || [],
|
|
187
|
+
componentMapping: Array.isArray(wf.componentMapping) ? wf.componentMapping
|
|
188
|
+
: typeof wf.componentMapping === 'object' && wf.componentMapping !== null
|
|
189
|
+
? Object.entries(wf.componentMapping).map(([k, v]) => ({ wireframeElement: k, reactComponent: v }))
|
|
190
|
+
: [],
|
|
191
|
+
layout: typeof wf.layout === 'object' ? wf.layout : null,
|
|
192
|
+
permissionsRequired: wf.permissionsRequired || []
|
|
193
|
+
}))
|
|
179
194
|
}
|
|
180
195
|
```
|
|
181
196
|
|
|
@@ -358,13 +358,12 @@ IF workflow_mode = "project":
|
|
|
358
358
|
|
|
359
359
|
Output path: docs/business-analyse/v{version}/index.json
|
|
360
360
|
Thematic files (empty, initialized): docs/business-analyse/v{version}/*.json
|
|
361
|
-
-
|
|
362
|
-
- rules.json
|
|
363
|
-
- usecases.json
|
|
364
|
-
- permissions.json
|
|
365
|
-
- screens.json
|
|
361
|
+
- cadrage.json
|
|
366
362
|
- validation.json
|
|
367
|
-
-
|
|
363
|
+
- consolidation.json
|
|
364
|
+
// NOTE: entities, rules, usecases, permissions, screens are MODULE-LEVEL only.
|
|
365
|
+
// They are created by step-03 (specify) in each module directory.
|
|
366
|
+
// Do NOT create them at project/app level — empty files mislead downstream tools.
|
|
368
367
|
|
|
369
368
|
Store:
|
|
370
369
|
project_id: string // PROJ-NNN
|
|
@@ -396,13 +395,12 @@ ELSE:
|
|
|
396
395
|
|
|
397
396
|
Output path: docs/{app}/business-analyse/v{version}/index.json
|
|
398
397
|
Thematic files (empty, initialized): docs/{app}/business-analyse/v{version}/*.json
|
|
399
|
-
-
|
|
400
|
-
- rules.json
|
|
401
|
-
- usecases.json
|
|
402
|
-
- permissions.json
|
|
403
|
-
- screens.json
|
|
398
|
+
- cadrage.json
|
|
404
399
|
- validation.json
|
|
405
400
|
- handoff.json
|
|
401
|
+
// NOTE: entities, rules, usecases, permissions, screens are MODULE-LEVEL only.
|
|
402
|
+
// They are created by step-03 (specify) in each module directory.
|
|
403
|
+
// Do NOT create them at app level — empty files mislead derive-prd and ralph-loop.
|
|
406
404
|
```
|
|
407
405
|
|
|
408
406
|
> **Note:** In project mode, per-application index.json files are created later in step-01-cadrage.
|
|
@@ -423,6 +423,21 @@ ba-writer.enrichSection({
|
|
|
423
423
|
// Update status
|
|
424
424
|
ba-writer.updateStatus({feature_id}, "consolidated");
|
|
425
425
|
|
|
426
|
+
// FIX H3: Clean up app-level index.json files entries
|
|
427
|
+
// Only keep entries for files that actually have content at app level.
|
|
428
|
+
// Module-level data lives in module directories — do NOT declare empty app-level files.
|
|
429
|
+
ba-writer.enrichSection({
|
|
430
|
+
featureId: {feature_id},
|
|
431
|
+
section: "files",
|
|
432
|
+
data: {
|
|
433
|
+
// Keep only files that exist and have content at app level
|
|
434
|
+
cadrage: { path: "cadrage.json", hash: "framed", lastModified: now() },
|
|
435
|
+
validation: { path: "validation.json", hash: "consolidated", lastModified: now() }
|
|
436
|
+
// REMOVED: entities, rules, usecases, permissions, screens, handoff
|
|
437
|
+
// These exist only at module level (flat-file architecture)
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
426
441
|
// Save workflow state for resume support
|
|
427
442
|
ba-writer.enrichSection({
|
|
428
443
|
featureId: {feature_id},
|
|
@@ -201,8 +201,12 @@ FOR i = 0 to modules.length-1:
|
|
|
201
201
|
- totalFiles: count from filesToCreate
|
|
202
202
|
- totalTasks: estimated from complexity
|
|
203
203
|
- handedOffAt: ISO timestamp
|
|
204
|
-
3.
|
|
205
|
-
|
|
204
|
+
3. Propagate featureDescription from app-level index.json:
|
|
205
|
+
```javascript
|
|
206
|
+
handoffPayload.featureDescription = appIndex.metadata.featureDescription || null;
|
|
207
|
+
```
|
|
208
|
+
4. Write via ba-writer.enrichModuleHandoff({ moduleFeatureId, handoffData })
|
|
209
|
+
5. Display: "handoff {i+1}/{N}: {moduleCode} ({fileCount} files)"
|
|
206
210
|
```
|
|
207
211
|
|
|
208
212
|
### 10. POST-CHECK Per Module (BLOCKING)
|
|
@@ -29,6 +29,18 @@ For each module, generate PRD files via CLI, verify integrity, create progress t
|
|
|
29
29
|
|
|
30
30
|
For EACH module:
|
|
31
31
|
|
|
32
|
+
**Option A — Flat-file BA (recommended):**
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Single module:
|
|
36
|
+
ss derive-prd --ba-dir {moduleBaDir} --output .ralph/prd-{moduleCode}.json
|
|
37
|
+
|
|
38
|
+
# All modules from app-level index.json:
|
|
39
|
+
ss derive-prd --ba-app {appIndexPath}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Option B — Legacy feature.json:**
|
|
43
|
+
|
|
32
44
|
```bash
|
|
33
45
|
ss derive-prd --feature {moduleFeaturePath} --output .ralph/prd-{moduleCode}.json
|
|
34
46
|
```
|
|
@@ -130,7 +130,7 @@ if (guardrailsNeeded.length > 0) {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
writeJSON(
|
|
133
|
+
writeJSON(currentPrdPath, prd);
|
|
134
134
|
}
|
|
135
135
|
```
|
|
136
136
|
|
|
@@ -142,7 +142,7 @@ const { missing, guardrailsNeeded } = checkCategoryCompleteness(prd);
|
|
|
142
142
|
|
|
143
143
|
if (guardrailsNeeded.length > 0) {
|
|
144
144
|
prd.tasks.push(...guardrailsNeeded);
|
|
145
|
-
writeJSON(
|
|
145
|
+
writeJSON(currentPrdPath, prd);
|
|
146
146
|
|
|
147
147
|
const newPending = prd.tasks.filter(t => t.status === 'pending').length;
|
|
148
148
|
console.log(`PRD updated: +${guardrailsNeeded.length} guardrails, ${newPending} pending tasks`);
|
|
@@ -186,7 +186,7 @@ for (const [cat, check] of Object.entries(artifactChecks)) {
|
|
|
186
186
|
t.error = 'Artifacts missing — re-execute';
|
|
187
187
|
t.completed_at = null;
|
|
188
188
|
});
|
|
189
|
-
writeJSON(
|
|
189
|
+
writeJSON(currentPrdPath, prd);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
```
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
|
|
17
17
|
## Loop Entry
|
|
18
18
|
|
|
19
|
+
```javascript
|
|
20
|
+
// PRD PATH RESOLUTION (MANDATORY - runs at loop entry)
|
|
21
|
+
const queuePath = '.ralph/modules-queue.json';
|
|
22
|
+
let currentPrdPath = '.ralph/prd.json';
|
|
23
|
+
if (fileExists(queuePath)) {
|
|
24
|
+
const queue = readJSON(queuePath);
|
|
25
|
+
currentPrdPath = queue.queue[queue.currentIndex].prdFile;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
19
29
|
Display compact progress:
|
|
20
30
|
```
|
|
21
31
|
[{iteration}/{max}] {completed}/{total} done | Finding eligible tasks...
|
|
@@ -26,7 +36,7 @@ Display compact progress:
|
|
|
26
36
|
## A0. Section-Split Mode (checked BEFORE standard batching)
|
|
27
37
|
|
|
28
38
|
```javascript
|
|
29
|
-
const prd = readJSON(
|
|
39
|
+
const prd = readJSON(currentPrdPath);
|
|
30
40
|
|
|
31
41
|
if (prd._sectionSplit?.enabled) {
|
|
32
42
|
// SECTION-SPLIT: Delegate per-phase instead of per-category batch
|
|
@@ -96,7 +106,7 @@ Apex sees a focused PRD for this phase only. After apex returns, continue:
|
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
prd._sectionSplit.currentPhase = nextPhase.phase;
|
|
99
|
-
writeJSON(
|
|
109
|
+
writeJSON(currentPrdPath, prd);
|
|
100
110
|
|
|
101
111
|
// → Skip to section C (commit PRD state), then D (loop back)
|
|
102
112
|
// → SKIP sections A and B. Jump directly to section C (COMMIT_PRD) below.
|
|
@@ -130,7 +140,7 @@ if (isDeadEnd) {
|
|
|
130
140
|
// Then fall through to `eligible.length === 0` below which returns to step-04.
|
|
131
141
|
console.log(`DEAD-END: ${prd.tasks.filter(t=>t.status==='completed').length} done, ${failedBeforeRetry.length} failed (retries exhausted), ${prd.tasks.filter(t=>t.status==='blocked').length} blocked`);
|
|
132
142
|
prd.status = 'failed';
|
|
133
|
-
writeJSON(
|
|
143
|
+
writeJSON(currentPrdPath, prd);
|
|
134
144
|
// DO NOT reset retries below — skip to eligible search (will find 0 tasks → CHECK_COMPLETION)
|
|
135
145
|
}
|
|
136
146
|
|
|
@@ -172,7 +182,7 @@ if (!isDeadEnd) {
|
|
|
172
182
|
if (depsBlocked) { task.status = 'blocked'; task.error = 'Blocked by failed dependency'; }
|
|
173
183
|
}
|
|
174
184
|
|
|
175
|
-
writeJSON(
|
|
185
|
+
writeJSON(currentPrdPath, prd);
|
|
176
186
|
} // end if (!isDeadEnd)
|
|
177
187
|
|
|
178
188
|
// Find ALL eligible tasks (dependencies met)
|
|
@@ -213,12 +223,12 @@ for (const task of batch) {
|
|
|
213
223
|
task.status = 'in_progress';
|
|
214
224
|
task.started_at = new Date().toISOString();
|
|
215
225
|
}
|
|
216
|
-
writeJSON(
|
|
226
|
+
writeJSON(currentPrdPath, prd);
|
|
217
227
|
```
|
|
218
228
|
|
|
219
229
|
### B2. Invoke /apex
|
|
220
230
|
|
|
221
|
-
**INVOKE `/apex -d
|
|
231
|
+
**INVOKE `/apex -d {currentPrdPath}`**
|
|
222
232
|
|
|
223
233
|
Apex handles everything for the current module:
|
|
224
234
|
- Reads PRD, extracts context (app_name, module_code, entities, sections)
|
|
@@ -230,11 +240,37 @@ Apex handles everything for the current module:
|
|
|
230
240
|
|
|
231
241
|
> **FLAGS:** `-d` implies `-a` (auto, no user confirmation) and `-e` (economy, no nested teams).
|
|
232
242
|
|
|
233
|
-
###
|
|
243
|
+
### B2b. MANDATORY CHECKPOINT: Verify prd.json tasks updated
|
|
234
244
|
|
|
235
245
|
```javascript
|
|
236
246
|
// Re-read PRD after apex execution
|
|
237
|
-
const updatedPrd = readJSON(
|
|
247
|
+
const updatedPrd = readJSON(currentPrdPath);
|
|
248
|
+
const batchIds = batch.map(t => t.id);
|
|
249
|
+
|
|
250
|
+
// BLOCKING CHECK: Verify apex actually updated task statuses
|
|
251
|
+
const stillPending = updatedPrd.tasks.filter(t => batchIds.includes(t.id) && t.status === 'pending').length;
|
|
252
|
+
if (stillPending === batchIds.length) {
|
|
253
|
+
console.error(`BLOCKING ERROR: apex did not update any task statuses — all ${stillPending} tasks still pending`);
|
|
254
|
+
console.error('Possible causes: apex crashed, PRD path mismatch, or apex did not receive the correct PRD');
|
|
255
|
+
// Mark tasks as failed so retry logic can handle them
|
|
256
|
+
for (const task of updatedPrd.tasks) {
|
|
257
|
+
if (batchIds.includes(task.id) && task.status === 'pending') {
|
|
258
|
+
task.status = 'failed';
|
|
259
|
+
task.error = 'apex did not update task status';
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
writeJSON(currentPrdPath, updatedPrd);
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
> **NEVER write progress.txt manually. ALWAYS update prd.json tasks FIRST,
|
|
267
|
+
> then derive progress.txt from prd.json state.**
|
|
268
|
+
|
|
269
|
+
### B3. Verify Post-Apex Results
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
// Re-read PRD after apex execution (use currentPrdPath)
|
|
273
|
+
const updatedPrd = readJSON(currentPrdPath);
|
|
238
274
|
const batchIds = batch.map(t => t.id);
|
|
239
275
|
|
|
240
276
|
const completed = updatedPrd.tasks.filter(t => batchIds.includes(t.id) && t.status === 'completed').length;
|
|
@@ -260,7 +296,7 @@ console.log(`Apex completed: ${completed}/${batchIds.length} tasks`);
|
|
|
260
296
|
### C1. Update Progress File (MANDATORY)
|
|
261
297
|
|
|
262
298
|
```javascript
|
|
263
|
-
const prdCheck = readJSON(
|
|
299
|
+
const prdCheck = readJSON(currentPrdPath);
|
|
264
300
|
const totalCompleted = prdCheck.tasks.filter(t => t.status === 'completed').length;
|
|
265
301
|
const total = prdCheck.tasks.length;
|
|
266
302
|
|
|
@@ -275,13 +311,13 @@ appendFile('.ralph/progress.txt',
|
|
|
275
311
|
```javascript
|
|
276
312
|
prdCheck.config.current_iteration++;
|
|
277
313
|
prdCheck.updated_at = new Date().toISOString();
|
|
278
|
-
writeJSON(
|
|
314
|
+
writeJSON(currentPrdPath, prdCheck);
|
|
279
315
|
```
|
|
280
316
|
|
|
281
317
|
### C3. Git Commit (PRD state only — apex already committed code)
|
|
282
318
|
|
|
283
319
|
```bash
|
|
284
|
-
git add
|
|
320
|
+
git add {currentPrdPath} .ralph/progress.txt
|
|
285
321
|
[ -f .ralph/modules-queue.json ] && git add .ralph/modules-queue.json
|
|
286
322
|
|
|
287
323
|
git commit -m "$(cat <<'EOF'
|