@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.
@@ -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
- # If a handoff document exists, use it to derive tasks
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 handoff found:**
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 from category:**
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 Output │ │ Dead-End │ │ Loop to
168
- │ Report Promise │ │ (all blocked│ │ step-01
169
- │ step-05 │ step-05 │ │ or failed) │ │
170
- │ status: status: │ │ step-05 │ │ │
171
- │ "partial" │ │ "completed" status: │ │ │
172
- └──────────────┘ └──────────────┘ │ "failed" │ └─────────────┘
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 or max iterations or dead-end: `./step-05-report.md`
246
- - If tasks remaining: `./step-01-task.md`
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