@atlashub/smartstack-cli 2.7.1 → 2.7.3
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 +50 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/agents/ba-writer.md +180 -11
- package/templates/agents/efcore/squash.md +3 -1
- package/templates/skills/business-analyse/_shared.md +19 -5
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +43 -1
- package/templates/skills/business-analyse/schemas/shared/common-defs.json +96 -3
- package/templates/skills/business-analyse/steps/step-00-init.md +48 -0
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +75 -7
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +21 -10
- package/templates/skills/business-analyse/steps/step-03-specify.md +702 -93
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +169 -4
- package/templates/skills/business-analyse/steps/step-05-handoff.md +24 -0
- package/templates/skills/efcore/steps/squash/step-03-create.md +39 -0
- package/templates/skills/ralph-loop/SKILL.md +35 -3
- package/templates/skills/ralph-loop/steps/step-00-init.md +93 -0
- package/templates/skills/ralph-loop/steps/step-01-task.md +182 -3
- package/templates/skills/ralph-loop/steps/step-03-commit.md +8 -1
- package/templates/skills/ralph-loop/steps/step-04-check.md +77 -13
- package/templates/skills/ralph-loop/steps/step-05-report.md +79 -0
|
@@ -16,6 +16,173 @@ Load the current task from prd.json or create initial task breakdown with catego
|
|
|
16
16
|
|
|
17
17
|
## EXECUTION SEQUENCE:
|
|
18
18
|
|
|
19
|
+
### 0. Multi-Module Queue Check
|
|
20
|
+
|
|
21
|
+
**Before any other logic, check for modules-queue.json:**
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
const queuePath = '.ralph/modules-queue.json';
|
|
25
|
+
const hasQueue = fileExists(queuePath);
|
|
26
|
+
|
|
27
|
+
if (hasQueue) {
|
|
28
|
+
const queue = readJSON(queuePath);
|
|
29
|
+
const currentModule = queue.modules[queue.currentIndex];
|
|
30
|
+
|
|
31
|
+
console.log(`Module ${queue.currentIndex + 1}/${queue.totalModules}: ${currentModule.code}`);
|
|
32
|
+
|
|
33
|
+
// Copy current module's prd file to prd.json (overwrite)
|
|
34
|
+
const modulePrd = readJSON(currentModule.prdFile);
|
|
35
|
+
|
|
36
|
+
// Transform PrdJson format to Ralph v2 if needed
|
|
37
|
+
if (modulePrd.project && modulePrd.requirements && !modulePrd.$version) {
|
|
38
|
+
// This is a PrdJson format from ss derive-prd → transform to Ralph v2
|
|
39
|
+
const transformedPrd = transformPrdJsonToRalphV2(modulePrd, currentModule.code);
|
|
40
|
+
writeJSON('.ralph/prd.json', transformedPrd);
|
|
41
|
+
} else {
|
|
42
|
+
// Already Ralph v2 format
|
|
43
|
+
writeJSON('.ralph/prd.json', modulePrd);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`Loaded PRD for module: ${currentModule.code}`);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**PrdJson → Ralph v2 transformation:**
|
|
51
|
+
|
|
52
|
+
The PrdJson format (from `ss derive-prd`) has this structure:
|
|
53
|
+
```
|
|
54
|
+
{ version, source, project, requirements: { useCases[], functionalRequirements[] },
|
|
55
|
+
businessRules[], architecture: { entities[], apiEndpoints[], sections[] },
|
|
56
|
+
implementation: { filesToCreate: { domain[], application[], infrastructure[], api[], frontend[], tests[] } },
|
|
57
|
+
seedData }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
function transformPrdJsonToRalphV2(prdJson, moduleCode) {
|
|
62
|
+
const tasks = [];
|
|
63
|
+
let taskId = 1;
|
|
64
|
+
|
|
65
|
+
// Layer processing order (SmartStack convention)
|
|
66
|
+
const layerOrder = [
|
|
67
|
+
{ key: "domain", category: "domain" },
|
|
68
|
+
{ key: "infrastructure", category: "infrastructure" },
|
|
69
|
+
{ key: "application", category: "application" },
|
|
70
|
+
{ key: "api", category: "api" },
|
|
71
|
+
{ key: "frontend", category: "frontend" },
|
|
72
|
+
{ key: "seedData", category: "infrastructure" },
|
|
73
|
+
{ key: "tests", category: "test" }
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const filesToCreate = prdJson.implementation?.filesToCreate || {};
|
|
77
|
+
|
|
78
|
+
// 1. Generate tasks from implementation.filesToCreate (primary source)
|
|
79
|
+
const lastIdByCategory = {};
|
|
80
|
+
|
|
81
|
+
for (const layer of layerOrder) {
|
|
82
|
+
const files = filesToCreate[layer.key] || [];
|
|
83
|
+
for (const fileSpec of files) {
|
|
84
|
+
const depIds = [];
|
|
85
|
+
// Depend on last task in same category (sequential within layer)
|
|
86
|
+
if (lastIdByCategory[layer.category]) {
|
|
87
|
+
depIds.push(lastIdByCategory[layer.category]);
|
|
88
|
+
}
|
|
89
|
+
// Cross-layer: api depends on last application task, frontend depends on last api task
|
|
90
|
+
if (layer.category === "api" && lastIdByCategory["application"]) {
|
|
91
|
+
depIds.push(lastIdByCategory["application"]);
|
|
92
|
+
}
|
|
93
|
+
if (layer.category === "frontend" && lastIdByCategory["api"]) {
|
|
94
|
+
depIds.push(lastIdByCategory["api"]);
|
|
95
|
+
}
|
|
96
|
+
if (layer.category === "test" && lastIdByCategory["api"]) {
|
|
97
|
+
depIds.push(lastIdByCategory["api"]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Build acceptance criteria from linked FRs and UCs
|
|
101
|
+
const linkedFRs = (fileSpec.linkedFRs || [])
|
|
102
|
+
.map(frId => {
|
|
103
|
+
const fr = prdJson.requirements?.functionalRequirements?.find(f => f.id === frId);
|
|
104
|
+
return fr ? fr.statement : frId;
|
|
105
|
+
});
|
|
106
|
+
const criteria = linkedFRs.length > 0
|
|
107
|
+
? `Implements: ${linkedFRs.join("; ")}`
|
|
108
|
+
: `File ${fileSpec.path} created and compiles correctly`;
|
|
109
|
+
|
|
110
|
+
tasks.push({
|
|
111
|
+
id: taskId,
|
|
112
|
+
description: fileSpec.description
|
|
113
|
+
? `[${layer.category}] ${fileSpec.description} → ${fileSpec.path}`
|
|
114
|
+
: `[${layer.category}] Create ${fileSpec.type}: ${fileSpec.path}`,
|
|
115
|
+
status: "pending",
|
|
116
|
+
category: layer.category,
|
|
117
|
+
dependencies: [...new Set(depIds)],
|
|
118
|
+
acceptance_criteria: criteria,
|
|
119
|
+
started_at: null,
|
|
120
|
+
completed_at: null,
|
|
121
|
+
iteration: null,
|
|
122
|
+
commit_hash: null,
|
|
123
|
+
files_changed: { created: [fileSpec.path], modified: [] },
|
|
124
|
+
validation: null,
|
|
125
|
+
error: null,
|
|
126
|
+
module: moduleCode
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
lastIdByCategory[layer.category] = taskId;
|
|
130
|
+
taskId++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 2. Add a final validation task
|
|
135
|
+
tasks.push({
|
|
136
|
+
id: taskId,
|
|
137
|
+
description: `[validation] Build, test, and MCP validate module ${moduleCode}`,
|
|
138
|
+
status: "pending",
|
|
139
|
+
category: "validation",
|
|
140
|
+
dependencies: Object.values(lastIdByCategory),
|
|
141
|
+
acceptance_criteria: "dotnet build succeeds, dotnet test passes, MCP validate_conventions clean",
|
|
142
|
+
started_at: null,
|
|
143
|
+
completed_at: null,
|
|
144
|
+
iteration: null,
|
|
145
|
+
commit_hash: null,
|
|
146
|
+
files_changed: { created: [], modified: [] },
|
|
147
|
+
validation: null,
|
|
148
|
+
error: null,
|
|
149
|
+
module: moduleCode
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
$version: "2.0.0",
|
|
154
|
+
feature: `${prdJson.project?.module || moduleCode} (${prdJson.project?.application || "app"})`,
|
|
155
|
+
status: "in_progress",
|
|
156
|
+
created: new Date().toISOString(),
|
|
157
|
+
updated_at: new Date().toISOString(),
|
|
158
|
+
metadata: {
|
|
159
|
+
cli_version: "unknown",
|
|
160
|
+
branch: getCurrentBranch(),
|
|
161
|
+
project_path: process.cwd(),
|
|
162
|
+
mcp_servers: { smartstack: true, context7: true },
|
|
163
|
+
module: moduleCode
|
|
164
|
+
},
|
|
165
|
+
config: {
|
|
166
|
+
max_iterations: {max_iterations},
|
|
167
|
+
completion_promise: "{completion_promise}",
|
|
168
|
+
current_iteration: 1
|
|
169
|
+
},
|
|
170
|
+
source: {
|
|
171
|
+
type: "ba-handoff",
|
|
172
|
+
handoff_path: null,
|
|
173
|
+
feature_json_path: prdJson.source?.featureJson || null,
|
|
174
|
+
module: moduleCode
|
|
175
|
+
},
|
|
176
|
+
tasks: tasks,
|
|
177
|
+
history: []
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**If no queue exists:** proceed with standard logic below (backward compatible).
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
19
186
|
### 1. Check for Existing prd.json
|
|
20
187
|
|
|
21
188
|
**If `.ralph/prd.json` exists:**
|
|
@@ -38,16 +205,27 @@ Load the current task from prd.json or create initial task breakdown with catego
|
|
|
38
205
|
- What layer does each task belong to?
|
|
39
206
|
- What dependencies exist between tasks?
|
|
40
207
|
|
|
41
|
-
**Check for BA handoff source:**
|
|
208
|
+
**Check for BA handoff source (priority order):**
|
|
42
209
|
|
|
43
210
|
```bash
|
|
44
|
-
#
|
|
211
|
+
# Priority 1: Per-module PRD files (from ss derive-prd / BA handoff)
|
|
212
|
+
# These are already handled by Section 0 if modules-queue.json exists
|
|
213
|
+
PRD_MODULE=$(ls .ralph/prd-*.json 2>/dev/null | head -1)
|
|
214
|
+
|
|
215
|
+
# Priority 2: Single prd.json already present (from BA or previous run)
|
|
216
|
+
PRD_SINGLE=".ralph/prd.json"
|
|
217
|
+
|
|
218
|
+
# Priority 3: Markdown handoff document
|
|
45
219
|
HANDOFF=$(find . -path "*development-handoff*" -name "*.md" | head -1)
|
|
46
220
|
BA_OUTPUT=$(find . -path ".claude/output/ba/*" -name "4-*.md" | head -1)
|
|
47
221
|
SOURCE_PATH=${HANDOFF:-$BA_OUTPUT}
|
|
48
222
|
```
|
|
49
223
|
|
|
50
|
-
**If
|
|
224
|
+
**If prd-*.json files exist but no modules-queue.json:**
|
|
225
|
+
- This means step-00 didn't create a queue (single prd file)
|
|
226
|
+
- Copy the single `prd-*.json` to `prd.json` and transform if needed
|
|
227
|
+
|
|
228
|
+
**If markdown handoff found:**
|
|
51
229
|
- Read the handoff document
|
|
52
230
|
- Derive tasks from its specifications (per-layer breakdown)
|
|
53
231
|
- Set `source.type = "ba-handoff"` and `source.handoff_path`
|
|
@@ -329,6 +507,7 @@ Read `.ralph/progress.txt` to understand:
|
|
|
329
507
|
```
|
|
330
508
|
╔══════════════════════════════════════════════════════════════════╗
|
|
331
509
|
║ ITERATION {current_iteration} / {max_iterations} ║
|
|
510
|
+
║ {modules_queue ? "Module " + (currentIndex+1) + "/" + totalModules + ": " + current_module : ""} ║
|
|
332
511
|
╠══════════════════════════════════════════════════════════════════╣
|
|
333
512
|
║ Progress: {tasks_completed} / {tasks_total} tasks complete ║
|
|
334
513
|
║ Blocked: {tasks_blocked} ║
|
|
@@ -42,13 +42,14 @@ git commit -m "$(cat <<'EOF'
|
|
|
42
42
|
|
|
43
43
|
Task {current_task_id}/{tasks_total} - Iteration {current_iteration}
|
|
44
44
|
Category: {current_task_category}
|
|
45
|
+
{current_module ? "Module: " + current_module : ""}
|
|
45
46
|
|
|
46
47
|
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
47
48
|
EOF
|
|
48
49
|
)"
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
**Determine commit prefix
|
|
52
|
+
**Determine commit prefix and scope:**
|
|
52
53
|
|
|
53
54
|
| Category | Prefix | Scope |
|
|
54
55
|
|----------|--------|-------|
|
|
@@ -62,6 +63,8 @@ EOF
|
|
|
62
63
|
| `validation` | `chore` | validation |
|
|
63
64
|
| `other` | `chore` | ralph |
|
|
64
65
|
|
|
66
|
+
**Multi-module scope:** When `{current_module}` is set (from modules-queue.json), prefix the scope with the module code: `{PREFIX}({module}/{scope})`. Example: `feat(orders/OrderController): Create REST endpoints`.
|
|
67
|
+
|
|
65
68
|
**Get commit hash:**
|
|
66
69
|
```bash
|
|
67
70
|
COMMIT_HASH=$(git rev-parse --short HEAD)
|
|
@@ -176,11 +179,15 @@ Result: ✅ Met / ❌ Not met
|
|
|
176
179
|
|
|
177
180
|
```bash
|
|
178
181
|
git add .ralph/prd.json .ralph/progress.txt
|
|
182
|
+
# Also stage modules-queue.json if it exists (multi-module tracking)
|
|
183
|
+
[ -f .ralph/modules-queue.json ] && git add .ralph/modules-queue.json
|
|
184
|
+
|
|
179
185
|
git commit -m "$(cat <<'EOF'
|
|
180
186
|
chore(ralph): update progress - task {current_task_id}/{tasks_total}
|
|
181
187
|
|
|
182
188
|
Iteration {current_iteration} complete
|
|
183
189
|
Status: {task.status}
|
|
190
|
+
{current_module ? "Module: " + current_module : ""}
|
|
184
191
|
|
|
185
192
|
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
186
193
|
EOF
|
|
@@ -67,6 +67,69 @@ writeJSON('.ralph/prd.json', prd);
|
|
|
67
67
|
|
|
68
68
|
**If allDone = true:**
|
|
69
69
|
|
|
70
|
+
**First, check for multi-module queue:**
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
const queuePath = '.ralph/modules-queue.json';
|
|
74
|
+
const hasQueue = fileExists(queuePath);
|
|
75
|
+
|
|
76
|
+
if (hasQueue) {
|
|
77
|
+
const queue = readJSON(queuePath);
|
|
78
|
+
const currentModule = queue.modules[queue.currentIndex];
|
|
79
|
+
|
|
80
|
+
// Mark current module as completed
|
|
81
|
+
currentModule.status = 'completed';
|
|
82
|
+
queue.completedModules++;
|
|
83
|
+
|
|
84
|
+
// Check if more modules remain
|
|
85
|
+
const nextIndex = queue.currentIndex + 1;
|
|
86
|
+
|
|
87
|
+
if (nextIndex < queue.totalModules) {
|
|
88
|
+
// ADVANCE TO NEXT MODULE
|
|
89
|
+
queue.currentIndex = nextIndex;
|
|
90
|
+
queue.modules[nextIndex].status = 'in-progress';
|
|
91
|
+
writeJSON(queuePath, queue);
|
|
92
|
+
|
|
93
|
+
// Mark current prd.json as completed
|
|
94
|
+
prd.status = 'completed';
|
|
95
|
+
prd.updated_at = new Date().toISOString();
|
|
96
|
+
writeJSON('.ralph/prd.json', prd);
|
|
97
|
+
|
|
98
|
+
// Reset iteration counter for next module
|
|
99
|
+
// (preserve max_iterations from config)
|
|
100
|
+
|
|
101
|
+
console.log(`
|
|
102
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
103
|
+
║ ✅ MODULE COMPLETE: ${currentModule.code} ║
|
|
104
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
105
|
+
║ Tasks: ${tasksCompleted} completed, ${tasksSkipped} skipped ║
|
|
106
|
+
║ Modules: ${queue.completedModules} / ${queue.totalModules} ║
|
|
107
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
108
|
+
║ ADVANCING TO NEXT MODULE: ${queue.modules[nextIndex].code} ║
|
|
109
|
+
╚══════════════════════════════════════════════════════════════════╝
|
|
110
|
+
`);
|
|
111
|
+
|
|
112
|
+
// Loop back to step-01 which will load next module's prd
|
|
113
|
+
// -> Proceed to step-01-task.md
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ALL MODULES COMPLETE - fall through to completion below
|
|
118
|
+
writeJSON(queuePath, queue);
|
|
119
|
+
|
|
120
|
+
console.log(`
|
|
121
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
122
|
+
║ ✅ ALL MODULES COMPLETE ║
|
|
123
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
124
|
+
║ Modules: ${queue.completedModules} / ${queue.totalModules} ║
|
|
125
|
+
║ ${queue.modules.map(m => m.code + ": " + m.status).join("\n║ ")} ║
|
|
126
|
+
╚══════════════════════════════════════════════════════════════════╝
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Then output completion (single module or all modules done):**
|
|
132
|
+
|
|
70
133
|
```
|
|
71
134
|
╔══════════════════════════════════════════════════════════════════╗
|
|
72
135
|
║ ✅ ALL TASKS COMPLETE ║
|
|
@@ -160,17 +223,16 @@ Continuing to next iteration...
|
|
|
160
223
|
│ Reached │ │ Done │ │ Remaining │
|
|
161
224
|
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
|
|
162
225
|
│ │ │
|
|
163
|
-
│
|
|
164
|
-
│
|
|
165
|
-
▼
|
|
166
|
-
|
|
167
|
-
│ Partial
|
|
168
|
-
│ Report
|
|
169
|
-
│ step-05
|
|
170
|
-
│ status:
|
|
171
|
-
│ "partial"
|
|
172
|
-
|
|
173
|
-
└─────────────┘
|
|
226
|
+
│ ┌────────┴────────┐ ┌───────┴────────┐
|
|
227
|
+
│ │ │ │ │
|
|
228
|
+
▼ ▼ ▼ ▼ ▼
|
|
229
|
+
┌────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
230
|
+
│ Partial │ │ More modules│ │ No more │ │ Dead-End / │
|
|
231
|
+
│ Report │ │ in queue? │ │ modules │ │ Loop to │
|
|
232
|
+
│ step-05 │ │ → step-01 │ │ → Promise │ │ step-01 │
|
|
233
|
+
│ status: │ │ (next mod) │ │ → step-05 │ │ │
|
|
234
|
+
│ "partial" │ └─────────────┘ └─────────────┘ └─────────────┘
|
|
235
|
+
└────────────┘
|
|
174
236
|
```
|
|
175
237
|
|
|
176
238
|
---
|
|
@@ -242,5 +304,7 @@ Completion Check:
|
|
|
242
304
|
|
|
243
305
|
## NEXT STEP:
|
|
244
306
|
|
|
245
|
-
- If complete
|
|
246
|
-
- If tasks
|
|
307
|
+
- If all tasks complete AND more modules in queue: `./step-01-task.md` (next module)
|
|
308
|
+
- If all tasks complete AND no more modules (or no queue): `./step-05-report.md`
|
|
309
|
+
- If max iterations or dead-end: `./step-05-report.md`
|
|
310
|
+
- If tasks remaining in current module: `./step-01-task.md`
|
|
@@ -59,6 +59,70 @@ const filesModified = [...new Set(allFilesModified)];
|
|
|
59
59
|
const totalDuration = prd.history.reduce((sum, h) => sum + (h.duration_seconds || 0), 0);
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
### 1b. Multi-Module Aggregation (if modules-queue.json exists)
|
|
63
|
+
|
|
64
|
+
**Check for multi-module context:**
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const queuePath = '.ralph/modules-queue.json';
|
|
68
|
+
const hasQueue = fileExists(queuePath);
|
|
69
|
+
|
|
70
|
+
let moduleStats = null;
|
|
71
|
+
|
|
72
|
+
if (hasQueue) {
|
|
73
|
+
const queue = readJSON(queuePath);
|
|
74
|
+
|
|
75
|
+
moduleStats = {
|
|
76
|
+
totalModules: queue.totalModules,
|
|
77
|
+
completedModules: queue.completedModules,
|
|
78
|
+
modules: []
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Aggregate stats from each module's prd file
|
|
82
|
+
for (const mod of queue.modules) {
|
|
83
|
+
const modPrd = readJSON(mod.prdFile);
|
|
84
|
+
const modTasks = modPrd.tasks || [];
|
|
85
|
+
|
|
86
|
+
const modData = {
|
|
87
|
+
code: mod.code,
|
|
88
|
+
status: mod.status,
|
|
89
|
+
tasks: {
|
|
90
|
+
total: modTasks.length,
|
|
91
|
+
completed: modTasks.filter(t => t.status === 'completed').length,
|
|
92
|
+
failed: modTasks.filter(t => t.status === 'failed').length,
|
|
93
|
+
blocked: modTasks.filter(t => t.status === 'blocked').length,
|
|
94
|
+
skipped: modTasks.filter(t => t.status === 'skipped').length
|
|
95
|
+
},
|
|
96
|
+
filesCreated: [],
|
|
97
|
+
filesModified: [],
|
|
98
|
+
commits: []
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
for (const task of modTasks) {
|
|
102
|
+
if (task.files_changed) {
|
|
103
|
+
modData.filesCreated.push(...task.files_changed.created);
|
|
104
|
+
modData.filesModified.push(...task.files_changed.modified);
|
|
105
|
+
}
|
|
106
|
+
if (task.commit_hash) {
|
|
107
|
+
modData.commits.push(task.commit_hash);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
modData.filesCreated = [...new Set(modData.filesCreated)];
|
|
112
|
+
modData.filesModified = [...new Set(modData.filesModified)];
|
|
113
|
+
modData.commits = [...new Set(modData.commits)];
|
|
114
|
+
|
|
115
|
+
moduleStats.modules.push(modData);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Merge all module files into main aggregation
|
|
119
|
+
for (const mod of moduleStats.modules) {
|
|
120
|
+
allFilesCreated.push(...mod.filesCreated);
|
|
121
|
+
allFilesModified.push(...mod.filesModified);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
62
126
|
### 2. Collect MCP Usage (from logs if available)
|
|
63
127
|
|
|
64
128
|
**Parse from verbose logs:**
|
|
@@ -111,6 +175,18 @@ const validationStats = {
|
|
|
111
175
|
|
|
112
176
|
{status_emoji mapping: completed=✅, failed=❌, blocked=🚫, skipped=⏭️, pending=⏳}
|
|
113
177
|
|
|
178
|
+
{if moduleStats:}
|
|
179
|
+
## Module Progress
|
|
180
|
+
|
|
181
|
+
| Module | Status | Tasks | Completed | Failed | Files Created | Files Modified | Commits |
|
|
182
|
+
|--------|--------|-------|-----------|--------|---------------|----------------|---------|
|
|
183
|
+
{for each mod in moduleStats.modules:}
|
|
184
|
+
| {mod.code} | {mod.status} | {mod.tasks.total} | {mod.tasks.completed} | {mod.tasks.failed} | {mod.filesCreated.length} | {mod.filesModified.length} | {mod.commits.length} |
|
|
185
|
+
{end for}
|
|
186
|
+
|
|
187
|
+
**Modules: {moduleStats.completedModules}/{moduleStats.totalModules} completed**
|
|
188
|
+
{end if}
|
|
189
|
+
|
|
114
190
|
## Failed Tasks
|
|
115
191
|
|
|
116
192
|
{if any failed tasks:}
|
|
@@ -216,6 +292,7 @@ writeJSON('.ralph/prd.json', prd);
|
|
|
216
292
|
║ Iterations: {iterations_used} ║
|
|
217
293
|
║ Tasks: {completed}/{total} completed ║
|
|
218
294
|
║ {failed} failed, {blocked} blocked ║
|
|
295
|
+
║ {moduleStats ? "Modules: " + moduleStats.completedModules + "/" + moduleStats.totalModules + " completed" : ""} ║
|
|
219
296
|
╠══════════════════════════════════════════════════════════════════╣
|
|
220
297
|
║ Files Created: {filesCreated.length} ║
|
|
221
298
|
║ Files Modified: {filesModified.length} ║
|
|
@@ -262,6 +339,8 @@ Next steps:
|
|
|
262
339
|
- Duration calculated from history entries
|
|
263
340
|
- Final summary displayed
|
|
264
341
|
- prd.json `status` and `updated_at` finalized
|
|
342
|
+
- **Multi-module:** Per-module statistics table included (if modules-queue.json exists)
|
|
343
|
+
- **Multi-module:** Cross-module file and commit aggregation
|
|
265
344
|
|
|
266
345
|
## COMPLETION:
|
|
267
346
|
|