@atlashub/smartstack-cli 2.7.2 → 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/package.json +1 -1
- package/templates/agents/efcore/squash.md +3 -1
- 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
package/package.json
CHANGED
|
@@ -42,7 +42,8 @@ main → BLOQUÉ (jamais squash)
|
|
|
42
42
|
5. **Fetch**: Récupérer **ModelSnapshot ET migrations** de branche parente (CRUCIAL!)
|
|
43
43
|
6. **Delete**: Supprimer migrations de la branche UNIQUEMENT
|
|
44
44
|
7. **Create**: Créer migration consolidée (MCP obligatoire)
|
|
45
|
-
8. **
|
|
45
|
+
8. **Inject SQL Objects**: Si `Persistence/SqlObjects/` contient des `.sql`, injecter `SqlObjectHelper.ApplyAll(migrationBuilder)` dans le `Up()`
|
|
46
|
+
9. **Validate**: Build + script OK
|
|
46
47
|
|
|
47
48
|
## Commandes Clés
|
|
48
49
|
|
|
@@ -103,6 +104,7 @@ dotnet build && dotnet ef migrations script --idempotent > /dev/null
|
|
|
103
104
|
- [ ] **Snapshot parent récupéré** (origin/$BASE_BRANCH)
|
|
104
105
|
- [ ] **Migrations parent récupérées** (fichiers .cs ET .Designer.cs)
|
|
105
106
|
- [ ] Migrations de la branche supprimées (pas celles héritées!)
|
|
107
|
+
- [ ] **SQL Objects injectés** (si `Persistence/SqlObjects/*.sql` existe → `SqlObjectHelper.ApplyAll`)
|
|
106
108
|
- [ ] Build OK après squash
|
|
107
109
|
- [ ] Script générable
|
|
108
110
|
|
|
@@ -131,6 +131,45 @@ echo " Up methods: $UP_METHODS"
|
|
|
131
131
|
echo " Down methods: $DOWN_METHODS"
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
### 6. Inject SQL Objects (Functions, Views, SP)
|
|
135
|
+
|
|
136
|
+
EF Core ne génère PAS de DDL pour les TVF, vues et procédures stockées.
|
|
137
|
+
Les scripts SQL source sont dans `Persistence/SqlObjects/` (Embedded Resources).
|
|
138
|
+
Après un squash, il faut les réinjecter dans la migration consolidée.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Check if SqlObjects folder exists with .sql files
|
|
142
|
+
SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
|
|
143
|
+
SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
|
|
144
|
+
|
|
145
|
+
if [ "$SQL_FILES" -gt 0 ]; then
|
|
146
|
+
echo ""
|
|
147
|
+
echo "Found $SQL_FILES SQL object(s) in SqlObjects/"
|
|
148
|
+
echo "Injecting SqlObjectHelper.ApplyAll(migrationBuilder) into migration..."
|
|
149
|
+
|
|
150
|
+
# Add using directive if not present
|
|
151
|
+
if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
|
|
152
|
+
sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Add SqlObjectHelper.ApplyAll at the end of Up() method
|
|
156
|
+
# Find the closing brace of Up() and insert before it
|
|
157
|
+
sed -i '/protected override void Up/,/^ }/ {
|
|
158
|
+
/^ }/ i\
|
|
159
|
+
\ // Apply SQL objects (TVF, Views, SP) from embedded resources\
|
|
160
|
+
\ SqlObjectHelper.ApplyAll(migrationBuilder);
|
|
161
|
+
}' "$MIGRATION_FILE"
|
|
162
|
+
|
|
163
|
+
echo " ✅ SqlObjectHelper.ApplyAll(migrationBuilder) injected"
|
|
164
|
+
find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do
|
|
165
|
+
echo " 📄 $f"
|
|
166
|
+
done
|
|
167
|
+
else
|
|
168
|
+
echo ""
|
|
169
|
+
echo "No SQL objects found in SqlObjects/ - skipping injection"
|
|
170
|
+
fi
|
|
171
|
+
```
|
|
172
|
+
|
|
134
173
|
---
|
|
135
174
|
|
|
136
175
|
## EXPECTED RESULT:
|
|
@@ -108,7 +108,7 @@ The loop only stops when this exact tag is output or max iterations reached.
|
|
|
108
108
|
</ralph_concept>
|
|
109
109
|
|
|
110
110
|
<workflow>
|
|
111
|
-
**Standard flow:**
|
|
111
|
+
**Standard flow (single module):**
|
|
112
112
|
1. Parse flags and task description
|
|
113
113
|
2. Verify MCP servers are available (MANDATORY)
|
|
114
114
|
3. Initialize .ralph/ structure and state files
|
|
@@ -117,6 +117,15 @@ The loop only stops when this exact tag is output or max iterations reached.
|
|
|
117
117
|
6. Commit changes, update progress
|
|
118
118
|
7. Check completion criteria
|
|
119
119
|
8. If complete: generate final report
|
|
120
|
+
|
|
121
|
+
**Multi-module flow (from BA handoff):**
|
|
122
|
+
1-3. Same as standard flow
|
|
123
|
+
4. Detect `prd-*.json` files → create `modules-queue.json`
|
|
124
|
+
5. Copy current module's prd to `prd.json`
|
|
125
|
+
6. Execute all tasks for current module (standard loop: steps 01→02→03→04)
|
|
126
|
+
7. When module complete: advance to next module in queue
|
|
127
|
+
8. Repeat steps 5-7 for each module
|
|
128
|
+
9. When all modules done: generate cross-module report
|
|
120
129
|
</workflow>
|
|
121
130
|
|
|
122
131
|
<state_variables>
|
|
@@ -136,6 +145,8 @@ The loop only stops when this exact tag is output or max iterations reached.
|
|
|
136
145
|
| `{task_category}` | string | Task category (domain/application/infrastructure/api/frontend/i18n/test/validation) |
|
|
137
146
|
| `{files_created}` | string[] | Files created during current task |
|
|
138
147
|
| `{files_modified}` | string[] | Files modified during current task |
|
|
148
|
+
| `{current_module}` | string\|null | Current module code (multi-module only) |
|
|
149
|
+
| `{modules_queue}` | object\|null | Module queue state (multi-module only) |
|
|
139
150
|
|
|
140
151
|
</state_variables>
|
|
141
152
|
|
|
@@ -184,14 +195,33 @@ Before ANY work, verify MCP servers:
|
|
|
184
195
|
|
|
185
196
|
```
|
|
186
197
|
.ralph/
|
|
187
|
-
├── prd.json
|
|
188
|
-
├── progress.txt
|
|
198
|
+
├── prd.json # Task list with status (current module or single)
|
|
199
|
+
├── progress.txt # Persistent memory between iterations
|
|
200
|
+
├── modules-queue.json # Multi-module tracking (only if multiple prd-*.json exist)
|
|
201
|
+
├── prd-{module1}.json # Per-module PRD from BA handoff (source, not modified)
|
|
202
|
+
├── prd-{module2}.json # Per-module PRD from BA handoff (source, not modified)
|
|
189
203
|
├── logs/
|
|
190
204
|
│ └── {timestamp}.log
|
|
191
205
|
└── reports/
|
|
192
206
|
└── {feature-name}.md
|
|
193
207
|
```
|
|
194
208
|
|
|
209
|
+
**modules-queue.json structure (multi-module only):**
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"modules": [
|
|
213
|
+
{
|
|
214
|
+
"code": "module-code",
|
|
215
|
+
"prdFile": ".ralph/prd-module-code.json",
|
|
216
|
+
"status": "pending|in-progress|completed|failed|partial"
|
|
217
|
+
}
|
|
218
|
+
],
|
|
219
|
+
"currentIndex": 0,
|
|
220
|
+
"totalModules": 3,
|
|
221
|
+
"completedModules": 0
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
195
225
|
**prd.json structure:**
|
|
196
226
|
```json
|
|
197
227
|
{
|
|
@@ -267,6 +297,8 @@ Before ANY work, verify MCP servers:
|
|
|
267
297
|
- Completion promise output when genuinely complete
|
|
268
298
|
- Final report generated in `.ralph/reports/`
|
|
269
299
|
- `prd.json` top-level `status` set to `"completed"` or `"partial"`
|
|
300
|
+
- **Multi-module:** All modules in `modules-queue.json` processed sequentially
|
|
301
|
+
- **Multi-module:** Cross-module aggregation in final report
|
|
270
302
|
</success_criteria>
|
|
271
303
|
|
|
272
304
|
<available_commands>
|
|
@@ -163,6 +163,22 @@ fi
|
|
|
163
163
|
- Read `prd.config.completion_promise` → `{completion_promise}`
|
|
164
164
|
- Read `prd.config.max_iterations` → `{max_iterations}`
|
|
165
165
|
- Read progress.txt for context
|
|
166
|
+
|
|
167
|
+
6. **Restore multi-module state (if applicable):**
|
|
168
|
+
```javascript
|
|
169
|
+
if (fileExists('.ralph/modules-queue.json')) {
|
|
170
|
+
const queue = readJSON('.ralph/modules-queue.json');
|
|
171
|
+
const currentModule = queue.modules[queue.currentIndex];
|
|
172
|
+
|
|
173
|
+
// Restore module state
|
|
174
|
+
{modules_queue} = queue;
|
|
175
|
+
{current_module} = currentModule.code;
|
|
176
|
+
|
|
177
|
+
console.log(`Resuming multi-module: ${currentModule.code} (${queue.currentIndex + 1}/${queue.totalModules})`);
|
|
178
|
+
console.log(`Modules completed: ${queue.completedModules}/${queue.totalModules}`);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
166
182
|
- Continue to step-01
|
|
167
183
|
|
|
168
184
|
### 4. Initialize .ralph/ Structure (new loop)
|
|
@@ -183,6 +199,80 @@ mkdir -p .ralph/logs
|
|
|
183
199
|
mkdir -p .ralph/reports
|
|
184
200
|
```
|
|
185
201
|
|
|
202
|
+
### 4b. Detect Multi-Module PRDs (from BA Handoff)
|
|
203
|
+
|
|
204
|
+
**After creating `.ralph/` directory, check for per-module PRD files:**
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Count prd-*.json files (generated by BA handoff via ss derive-prd)
|
|
208
|
+
PRD_FILES=$(ls .ralph/prd-*.json 2>/dev/null)
|
|
209
|
+
PRD_COUNT=$(echo "$PRD_FILES" | grep -c "prd-" 2>/dev/null || echo "0")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**If multiple prd-*.json files found (PRD_COUNT > 0):**
|
|
213
|
+
|
|
214
|
+
1. **Read module order from master feature.json (if available):**
|
|
215
|
+
```bash
|
|
216
|
+
# Look for master feature.json that references modules
|
|
217
|
+
MASTER_FEATURE=$(find docs/business -maxdepth 4 -name "feature.json" -path "*/business-analyse/*" | head -1)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
2. **Build module queue:**
|
|
221
|
+
```javascript
|
|
222
|
+
const modules = [];
|
|
223
|
+
|
|
224
|
+
if (MASTER_FEATURE && masterJson.metadata?.workflow?.moduleOrder) {
|
|
225
|
+
// Use order from BA master feature.json
|
|
226
|
+
for (const mod of masterJson.metadata.workflow.moduleOrder) {
|
|
227
|
+
const prdFile = `.ralph/prd-${mod.code}.json`;
|
|
228
|
+
if (fileExists(prdFile)) {
|
|
229
|
+
modules.push({ code: mod.code, prdFile, status: "pending" });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
// Fallback: alphabetical order from prd-*.json filenames
|
|
234
|
+
for (const file of prdFiles) {
|
|
235
|
+
const code = file.match(/prd-(.+)\.json$/)[1];
|
|
236
|
+
modules.push({ code, prdFile: `.ralph/${file}`, status: "pending" });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
3. **Create modules-queue.json:**
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"modules": [
|
|
245
|
+
{ "code": "mod1", "prdFile": ".ralph/prd-mod1.json", "status": "pending" },
|
|
246
|
+
{ "code": "mod2", "prdFile": ".ralph/prd-mod2.json", "status": "pending" }
|
|
247
|
+
],
|
|
248
|
+
"currentIndex": 0,
|
|
249
|
+
"totalModules": 2,
|
|
250
|
+
"completedModules": 0
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
4. **Set first module as active:**
|
|
255
|
+
```javascript
|
|
256
|
+
modules[0].status = "in-progress";
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
5. **Log detection:**
|
|
260
|
+
```
|
|
261
|
+
Multi-module detected: {PRD_COUNT} modules
|
|
262
|
+
Queue: {module codes joined by " → "}
|
|
263
|
+
Starting with: {modules[0].code}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**If only `.ralph/prd.json` exists (single module):**
|
|
267
|
+
- Skip queue creation (backward compatible)
|
|
268
|
+
- Proceed normally
|
|
269
|
+
|
|
270
|
+
**Store:**
|
|
271
|
+
```
|
|
272
|
+
{modules_queue} = queue object or null
|
|
273
|
+
{current_module} = first module code or null
|
|
274
|
+
```
|
|
275
|
+
|
|
186
276
|
### 5. Validate Completion Promise
|
|
187
277
|
|
|
188
278
|
**If {completion_promise} is null:**
|
|
@@ -251,10 +341,12 @@ Branch: {CURRENT_BRANCH}
|
|
|
251
341
|
║ MCP: ✅ Ready ║
|
|
252
342
|
║ Schema: v2.0.0 ║
|
|
253
343
|
║ Branch: {CURRENT_BRANCH} ║
|
|
344
|
+
║ {modules_queue ? "Modules: " + totalModules + " (" + module_codes.join(" → ") + ")" : "Mode: single module"} ║
|
|
254
345
|
╠══════════════════════════════════════════════════════════════════╣
|
|
255
346
|
║ Files: ║
|
|
256
347
|
║ - .ralph/prd.json (tasks) ║
|
|
257
348
|
║ - .ralph/progress.txt (memory) ║
|
|
349
|
+
║ {modules_queue ? "- .ralph/modules-queue.json (module tracking)" : ""} ║
|
|
258
350
|
╚══════════════════════════════════════════════════════════════════╝
|
|
259
351
|
|
|
260
352
|
-> Loading tasks...
|
|
@@ -273,6 +365,7 @@ Branch: {CURRENT_BRANCH}
|
|
|
273
365
|
- Metadata collected (branch, version, MCP status)
|
|
274
366
|
- prd.json v2 schema validated (if resume)
|
|
275
367
|
- Branch integrity validated (if resume)
|
|
368
|
+
- Multi-module prd files detected and modules-queue.json created (if applicable)
|
|
276
369
|
- Output is COMPACT
|
|
277
370
|
- Proceeded to step-01 immediately after summary
|
|
278
371
|
|
|
@@ -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
|
|