@atlashub/smartstack-cli 4.25.0 → 4.27.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/dist/mcp-entry.mjs +33 -11
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/ba-writer.md +46 -42
- package/templates/project/appsettings.json.template +4 -6
- package/templates/skills/apex/SKILL.md +1 -0
- package/templates/skills/apex/references/challenge-questions.md +17 -0
- package/templates/skills/apex/references/post-checks.md +48 -0
- package/templates/skills/apex/steps/step-03-execute.md +63 -2
- 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 +22 -14
- package/templates/skills/business-analyse/steps/step-04-consolidate.md +3 -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 +81 -14
- 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
|
@@ -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
|
|
|
@@ -357,14 +357,12 @@ IF workflow_mode = "project":
|
|
|
357
357
|
})
|
|
358
358
|
|
|
359
359
|
Output path: docs/business-analyse/v{version}/index.json
|
|
360
|
-
Thematic files
|
|
361
|
-
-
|
|
362
|
-
- rules.json
|
|
363
|
-
- usecases.json
|
|
364
|
-
- permissions.json
|
|
365
|
-
- screens.json
|
|
360
|
+
Thematic files — EXACTLY these 3, NO OTHERS:
|
|
361
|
+
- cadrage.json
|
|
366
362
|
- validation.json
|
|
367
|
-
-
|
|
363
|
+
- consolidation.json
|
|
364
|
+
FORBIDDEN: entities.json, rules.json, usecases.json, permissions.json, screens.json, handoff.json
|
|
365
|
+
Creating any FORBIDDEN file at project level is a BLOCKING ERROR.
|
|
368
366
|
|
|
369
367
|
Store:
|
|
370
368
|
project_id: string // PROJ-NNN
|
|
@@ -395,19 +393,29 @@ ELSE:
|
|
|
395
393
|
})
|
|
396
394
|
|
|
397
395
|
Output path: docs/{app}/business-analyse/v{version}/index.json
|
|
398
|
-
Thematic files
|
|
399
|
-
-
|
|
400
|
-
- rules.json
|
|
401
|
-
- usecases.json
|
|
402
|
-
- permissions.json
|
|
403
|
-
- screens.json
|
|
396
|
+
Thematic files — EXACTLY these 3, NO OTHERS:
|
|
397
|
+
- cadrage.json
|
|
404
398
|
- validation.json
|
|
405
|
-
-
|
|
399
|
+
- consolidation.json
|
|
400
|
+
FORBIDDEN: entities.json, rules.json, usecases.json, permissions.json, screens.json, handoff.json
|
|
401
|
+
Creating any FORBIDDEN file at application level is a BLOCKING ERROR.
|
|
406
402
|
```
|
|
407
403
|
|
|
408
404
|
> **Note:** In project mode, per-application index.json files are created later in step-01-cadrage.
|
|
409
405
|
> In single-app mode, step-02 (structure) determines if it's single or multi-module.
|
|
410
406
|
|
|
407
|
+
### POST-CREATE VERIFICATION (MANDATORY)
|
|
408
|
+
|
|
409
|
+
After creating thematic files, verify no forbidden files were created:
|
|
410
|
+
|
|
411
|
+
```
|
|
412
|
+
List all *.json files in {docs_dir}/
|
|
413
|
+
Expected (project/application): [index.json, cadrage.json, validation.json, consolidation.json]
|
|
414
|
+
If any file NOT in expected list is found → DELETE it immediately + log WARNING
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
This guard catches any accidental creation of module-only files at the wrong scope.
|
|
418
|
+
|
|
411
419
|
## Step 10: Update Config
|
|
412
420
|
|
|
413
421
|
Update `.business-analyse/config.json` with new feature information.
|
|
@@ -423,6 +423,9 @@ ba-writer.enrichSection({
|
|
|
423
423
|
// Update status
|
|
424
424
|
ba-writer.updateStatus({feature_id}, "consolidated");
|
|
425
425
|
|
|
426
|
+
// Clean up any forbidden files that may have been created at app level
|
|
427
|
+
ba-writer.cleanupAppLevelFiles({ featureId: {feature_id} });
|
|
428
|
+
|
|
426
429
|
// Save workflow state for resume support
|
|
427
430
|
ba-writer.enrichSection({
|
|
428
431
|
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
|
|
|
@@ -270,18 +306,49 @@ appendFile('.ralph/progress.txt',
|
|
|
270
306
|
);
|
|
271
307
|
```
|
|
272
308
|
|
|
273
|
-
### C2.
|
|
309
|
+
### C2. Skipped Task Audit
|
|
310
|
+
|
|
311
|
+
After apex returns, check for newly skipped tasks:
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
const newlySkipped = prdCheck.tasks.filter(t =>
|
|
315
|
+
batchIds.includes(t.id) && t.status === 'skipped'
|
|
316
|
+
);
|
|
317
|
+
if (newlySkipped.length > 0) {
|
|
318
|
+
console.warn(`⚠ ${newlySkipped.length} tasks were SKIPPED by apex:`);
|
|
319
|
+
newlySkipped.forEach(t => console.warn(` - ${t.id}: ${t.description}`));
|
|
320
|
+
|
|
321
|
+
// If ALL tasks in a category were skipped → BLOCKING, reset for retry
|
|
322
|
+
const skippedCategories = [...new Set(newlySkipped.map(t => t.category))];
|
|
323
|
+
for (const cat of skippedCategories) {
|
|
324
|
+
const allInCat = prdCheck.tasks.filter(t => t.category === cat);
|
|
325
|
+
const allSkipped = allInCat.every(t => t.status === 'skipped');
|
|
326
|
+
if (allSkipped) {
|
|
327
|
+
console.error(`BLOCKING: ALL tasks in category "${cat}" were skipped — investigate root cause`);
|
|
328
|
+
// Reset to pending for retry
|
|
329
|
+
allInCat.forEach(t => {
|
|
330
|
+
t.status = 'pending';
|
|
331
|
+
t._retryCount = (t._retryCount || 0) + 1;
|
|
332
|
+
t.error = `All tasks in category "${cat}" skipped — auto-retry`;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
writeJSON(currentPrdPath, prdCheck);
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### C3. Increment Iteration
|
|
274
341
|
|
|
275
342
|
```javascript
|
|
276
343
|
prdCheck.config.current_iteration++;
|
|
277
344
|
prdCheck.updated_at = new Date().toISOString();
|
|
278
|
-
writeJSON(
|
|
345
|
+
writeJSON(currentPrdPath, prdCheck);
|
|
279
346
|
```
|
|
280
347
|
|
|
281
|
-
###
|
|
348
|
+
### C4. Git Commit (PRD state only — apex already committed code)
|
|
282
349
|
|
|
283
350
|
```bash
|
|
284
|
-
git add
|
|
351
|
+
git add {currentPrdPath} .ralph/progress.txt
|
|
285
352
|
[ -f .ralph/modules-queue.json ] && git add .ralph/modules-queue.json
|
|
286
353
|
|
|
287
354
|
git commit -m "$(cat <<'EOF'
|
|
@@ -295,7 +362,7 @@ EOF
|
|
|
295
362
|
)"
|
|
296
363
|
```
|
|
297
364
|
|
|
298
|
-
###
|
|
365
|
+
### C5. PRD Sync Verification (HARD CHECK)
|
|
299
366
|
|
|
300
367
|
> **Note:** `prdCheck` (read in C1) is the authoritative post-apex snapshot.
|
|
301
368
|
> `completed` (from B3) was computed on a DIFFERENT read — do NOT mix the two.
|
|
@@ -54,15 +54,17 @@ if (missing.length === 0) {
|
|
|
54
54
|
timestamp: new Date().toISOString()
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
// Mark current PRD as complete
|
|
57
|
+
// Mark current PRD as complete (write to module-specific file, NOT .ralph/prd.json)
|
|
58
58
|
prd.status = 'completed';
|
|
59
|
-
writeJSON(
|
|
59
|
+
writeJSON(currentModule.prdFile, prd);
|
|
60
60
|
|
|
61
|
-
//
|
|
61
|
+
// Prepare next module's PRD (work directly with prd-{moduleCode}.json)
|
|
62
62
|
const nextPrd = readJSON(queue.modules[nextIndex].prdFile);
|
|
63
63
|
nextPrd.config.current_iteration = 1;
|
|
64
64
|
nextPrd.config.max_iterations = prd.config.max_iterations;
|
|
65
|
-
writeJSON(
|
|
65
|
+
writeJSON(queue.modules[nextIndex].prdFile, nextPrd);
|
|
66
|
+
// NOTE: Ralph works directly with prd-{moduleCode}.json via currentPrdPath resolution.
|
|
67
|
+
// No need to copy to .ralph/prd.json — this avoids overwriting between modules.
|
|
66
68
|
|
|
67
69
|
console.log(`MODULE COMPLETE: ${currentModule.code} → NEXT: ${queue.modules[nextIndex].code}`);
|
|
68
70
|
|
|
@@ -76,6 +78,29 @@ if (missing.length === 0) {
|
|
|
76
78
|
}
|
|
77
79
|
```
|
|
78
80
|
|
|
81
|
+
### MANDATORY POST-CHECK: modules-queue.json integrity
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
// VERIFY: modules-queue.json updated correctly after advancement
|
|
85
|
+
const queueCheck = readJSON(queuePath);
|
|
86
|
+
const actualCompleted = queueCheck.modules.filter(m => m.status === 'completed').length;
|
|
87
|
+
if (queueCheck.completedModules !== actualCompleted) {
|
|
88
|
+
console.error(`QUEUE SYNC ERROR: completedModules=${queueCheck.completedModules} but actual=${actualCompleted}`);
|
|
89
|
+
queueCheck.completedModules = actualCompleted;
|
|
90
|
+
writeJSON(queuePath, queueCheck);
|
|
91
|
+
console.log('FIXED: completedModules synced to actual count');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (nextIndex < queueCheck.totalModules) {
|
|
95
|
+
if (queueCheck.currentIndex !== nextIndex) {
|
|
96
|
+
console.error(`QUEUE SYNC ERROR: currentIndex=${queueCheck.currentIndex} but expected=${nextIndex}`);
|
|
97
|
+
queueCheck.currentIndex = nextIndex;
|
|
98
|
+
writeJSON(queuePath, queueCheck);
|
|
99
|
+
console.log('FIXED: currentIndex synced');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
79
104
|
---
|
|
80
105
|
|
|
81
106
|
## Check: All Modules Complete
|
|
@@ -121,7 +146,7 @@ if (fileExists(moduleChangePath)) {
|
|
|
121
146
|
├─ step-04: Check completion
|
|
122
147
|
│ └─ If all categories complete:
|
|
123
148
|
│ ├─ Write module-changed.json
|
|
124
|
-
│ ├─ Write next PRD to
|
|
149
|
+
│ ├─ Write next PRD to prd-{moduleCode}.json (via currentPrdPath)
|
|
125
150
|
│ └─ Return to step-01 ✓
|
|
126
151
|
│
|
|
127
152
|
└─ [Phase B: Module 2]
|
|
@@ -123,7 +123,7 @@ if (fileExists(queuePath)) {
|
|
|
123
123
|
const queue = readJSON(queuePath);
|
|
124
124
|
const currentModule = queue.modules[queue.currentIndex];
|
|
125
125
|
|
|
126
|
-
const currentPrd = readJSON(
|
|
126
|
+
const currentPrd = readJSON(currentPrdPath);
|
|
127
127
|
if (currentPrd.metadata?.moduleCode !== currentModule.code) {
|
|
128
128
|
// Module has changed — load new PRD
|
|
129
129
|
const modulePrd = readJSON(currentModule.prdFile);
|
|
@@ -143,16 +143,16 @@ if (fileExists(queuePath)) {
|
|
|
143
143
|
modulePrd.metadata.project_path = modulePrd.metadata.project_path || process.cwd();
|
|
144
144
|
modulePrd.metadata.mcp_servers = modulePrd.metadata.mcp_servers || { smartstack: true, context7: true };
|
|
145
145
|
modulePrd.history = modulePrd.history || [];
|
|
146
|
-
writeJSON(
|
|
146
|
+
writeJSON(currentModule.prdFile, modulePrd);
|
|
147
147
|
}
|
|
148
148
|
// LEGACY: FORMAT A
|
|
149
149
|
else if (modulePrd.project && modulePrd.requirements && !modulePrd.$version) {
|
|
150
150
|
console.warn('⚠ DEPRECATED: FORMAT A prd.json detected. Re-run `ss derive-prd` to generate v3 format.');
|
|
151
|
-
writeJSON(
|
|
151
|
+
writeJSON(currentModule.prdFile, transformPrdJsonToRalphV2(modulePrd, currentModule.code));
|
|
152
152
|
}
|
|
153
153
|
// v2 legacy
|
|
154
154
|
else {
|
|
155
|
-
writeJSON(
|
|
155
|
+
writeJSON(currentModule.prdFile, modulePrd);
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -50,7 +50,7 @@ EF Core creates a single ModelSnapshot per DbContext. If Phase 1 creates a migra
|
|
|
50
50
|
## 1. Detection
|
|
51
51
|
|
|
52
52
|
```javascript
|
|
53
|
-
const prd = readJSON(
|
|
53
|
+
const prd = readJSON(currentPrdPath);
|
|
54
54
|
const domainTasks = prd.tasks.filter(t => t.category === 'domain');
|
|
55
55
|
const sections = prd.architecture?.sections ?? [];
|
|
56
56
|
|
|
@@ -313,7 +313,7 @@ prd._sectionSplit = {
|
|
|
313
313
|
entityToSection
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
-
writeJSON(
|
|
316
|
+
writeJSON(currentPrdPath, prd);
|
|
317
317
|
```
|
|
318
318
|
|
|
319
319
|
---
|
|
@@ -344,7 +344,7 @@ if (!depsOk) {
|
|
|
344
344
|
// After apex returns:
|
|
345
345
|
nextPhase.status = 'completed';
|
|
346
346
|
prd._sectionSplit.currentPhase = nextPhase.phase;
|
|
347
|
-
writeJSON(
|
|
347
|
+
writeJSON(currentPrdPath, prd);
|
|
348
348
|
```
|
|
349
349
|
|
|
350
350
|
---
|
|
@@ -369,7 +369,7 @@ for (const phaseTask of phasePrd.tasks) {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
writeJSON(
|
|
372
|
+
writeJSON(currentPrdPath, prd);
|
|
373
373
|
```
|
|
374
374
|
|
|
375
375
|
---
|
|
@@ -413,13 +413,13 @@ After all phases complete or on final report:
|
|
|
413
413
|
if (prd._sectionSplit?.enabled) {
|
|
414
414
|
// Delete temporary phase PRD files
|
|
415
415
|
for (const phase of prd._sectionSplit.phases) {
|
|
416
|
-
if (fileExists(phase.prdFile) && phase.prdFile !==
|
|
416
|
+
if (fileExists(phase.prdFile) && phase.prdFile !== currentPrdPath) {
|
|
417
417
|
deleteFile(phase.prdFile);
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
// Remove split state from master PRD
|
|
421
421
|
delete prd._sectionSplit;
|
|
422
|
-
writeJSON(
|
|
422
|
+
writeJSON(currentPrdPath, prd);
|
|
423
423
|
}
|
|
424
424
|
```
|
|
425
425
|
|
|
@@ -46,17 +46,27 @@ See [references/task-transform-legacy.md](../references/task-transform-legacy.md
|
|
|
46
46
|
|
|
47
47
|
## 1. Check for Existing prd.json
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
```javascript
|
|
50
|
+
// PRD PATH RESOLUTION (MANDATORY - runs before any PRD access)
|
|
51
|
+
const queuePath = '.ralph/modules-queue.json';
|
|
52
|
+
let currentPrdPath = '.ralph/prd.json';
|
|
53
|
+
if (fileExists(queuePath)) {
|
|
54
|
+
const queue = readJSON(queuePath);
|
|
55
|
+
currentPrdPath = queue.queue[queue.currentIndex].prdFile;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If `{currentPrdPath}` exists:
|
|
50
60
|
1. Read the PRD
|
|
51
61
|
2. **v3 FAST PATH:** If `$version === "3.0.0"` AND `tasks[]` exists:
|
|
52
62
|
- Inject runtime fields if missing (status, config, feature, created, updated_at, history)
|
|
53
|
-
- Write back to
|
|
63
|
+
- Write back to `{currentPrdPath}`
|
|
54
64
|
- **Run CATEGORY COMPLETENESS CHECK (section 4b) before proceeding**
|
|
55
65
|
- Skip directly to section 5 (find next task)
|
|
56
66
|
3. **v2 legacy:** If `$version === "2.0.0"` → find next eligible task (section 5)
|
|
57
67
|
4. **FORMAT A (deprecated):** If `.project && .requirements && !.$version` → run `transformPrdJsonToRalphV2()` → section 5
|
|
58
68
|
|
|
59
|
-
If
|
|
69
|
+
If `{currentPrdPath}` does not exist: continue to section 1b.
|
|
60
70
|
|
|
61
71
|
### 1b. BA Handoff Quality Gate (BLOCKING when BA artifacts present)
|
|
62
72
|
|
|
@@ -110,7 +120,7 @@ Generate **3-30 subtasks** by category (domain → infrastructure → applicatio
|
|
|
110
120
|
|
|
111
121
|
## 3. Create prd.json
|
|
112
122
|
|
|
113
|
-
Write
|
|
123
|
+
Write `{currentPrdPath}` with all tasks (every task MUST have: id, description, status, category, dependencies, acceptance_criteria, started_at, completed_at, iteration, commit_hash, files_changed, validation, error).
|
|
114
124
|
|
|
115
125
|
Initialize `.ralph/progress.txt`:
|
|
116
126
|
```markdown
|
|
@@ -30,13 +30,21 @@ Delegate the current module's tasks to `/apex -d` for code generation. Ralph han
|
|
|
30
30
|
### 1. Verify Dependencies
|
|
31
31
|
|
|
32
32
|
```javascript
|
|
33
|
-
|
|
33
|
+
// PRD PATH RESOLUTION
|
|
34
|
+
const queuePath = '.ralph/modules-queue.json';
|
|
35
|
+
let currentPrdPath = '.ralph/prd.json';
|
|
36
|
+
if (fileExists(queuePath)) {
|
|
37
|
+
const queue = readJSON(queuePath);
|
|
38
|
+
currentPrdPath = queue.queue[queue.currentIndex].prdFile;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const prd = readJSON(currentPrdPath);
|
|
34
42
|
const task = prd.tasks.find(t => t.id === {current_task_id});
|
|
35
43
|
for (const depId of task.dependencies) {
|
|
36
44
|
const dep = prd.tasks.find(t => t.id === depId);
|
|
37
45
|
if (!dep || dep.status !== 'completed') {
|
|
38
46
|
task.status = 'blocked'; task.error = `Dependency ${depId} not completed`;
|
|
39
|
-
writeJSON(
|
|
47
|
+
writeJSON(currentPrdPath, prd);
|
|
40
48
|
STOP — return to step-01
|
|
41
49
|
}
|
|
42
50
|
}
|
|
@@ -47,7 +55,7 @@ for (const depId of task.dependencies) {
|
|
|
47
55
|
```javascript
|
|
48
56
|
task.status = 'in_progress';
|
|
49
57
|
task.started_at = new Date().toISOString();
|
|
50
|
-
writeJSON(
|
|
58
|
+
writeJSON(currentPrdPath, prd);
|
|
51
59
|
```
|
|
52
60
|
|
|
53
61
|
### 3. Delegate to /apex
|
|
@@ -106,7 +114,7 @@ Read `references/section-splitting.md` sections 7-9 for execution, merging, and
|
|
|
106
114
|
|
|
107
115
|
nextPhase.status = 'completed';
|
|
108
116
|
prd._sectionSplit.currentPhase = nextPhase.phase;
|
|
109
|
-
writeJSON(
|
|
117
|
+
writeJSON(currentPrdPath, prd);
|
|
110
118
|
|
|
111
119
|
// Continue to step-03 (commit), then step-04 loops back for next phase
|
|
112
120
|
}
|
|
@@ -116,7 +124,7 @@ Read `references/section-splitting.md` sections 7-9 for execution, merging, and
|
|
|
116
124
|
|
|
117
125
|
#### 3c. Standard Mode (no split, no parallel)
|
|
118
126
|
|
|
119
|
-
**INVOKE `/apex -d
|
|
127
|
+
**INVOKE `/apex -d {currentPrdPath}`**
|
|
120
128
|
|
|
121
129
|
This delegates ALL remaining tasks for the current module to apex:
|
|
122
130
|
|
|
@@ -134,7 +142,7 @@ This delegates ALL remaining tasks for the current module to apex:
|
|
|
134
142
|
|
|
135
143
|
```javascript
|
|
136
144
|
// Re-read PRD after apex execution
|
|
137
|
-
const updatedPrd = readJSON(
|
|
145
|
+
const updatedPrd = readJSON(currentPrdPath);
|
|
138
146
|
const moduleTasks = updatedPrd.tasks.filter(t => t.module === {current_module});
|
|
139
147
|
|
|
140
148
|
const completed = moduleTasks.filter(t => t.status === 'completed').length;
|