@atlashub/smartstack-cli 4.24.0 → 4.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/dist/index.js +765 -517
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/agents/ba-writer.md +7 -3
  5. package/templates/skills/ba-generate-html/references/data-build.md +22 -13
  6. package/templates/skills/ba-generate-html/references/data-mapping.md +33 -24
  7. package/templates/skills/ba-generate-html/steps/step-01-collect.md +15 -6
  8. package/templates/skills/ba-generate-html/steps/step-02-build-data.md +37 -22
  9. package/templates/skills/business-analyse/steps/step-00-init.md +9 -11
  10. package/templates/skills/business-analyse/steps/step-04-consolidate.md +15 -0
  11. package/templates/skills/derive-prd/steps/step-01-transform.md +6 -2
  12. package/templates/skills/derive-prd/steps/step-02-export.md +12 -0
  13. package/templates/skills/ralph-loop/references/category-completeness.md +3 -3
  14. package/templates/skills/ralph-loop/references/compact-loop.md +47 -11
  15. package/templates/skills/ralph-loop/references/init-resume-recovery.md +1 -1
  16. package/templates/skills/ralph-loop/references/module-transition.md +30 -5
  17. package/templates/skills/ralph-loop/references/multi-module-queue.md +4 -4
  18. package/templates/skills/ralph-loop/references/section-splitting.md +6 -6
  19. package/templates/skills/ralph-loop/steps/step-01-task.md +14 -4
  20. package/templates/skills/ralph-loop/steps/step-02-execute.md +14 -6
  21. package/templates/skills/ralph-loop/steps/step-03-commit.md +15 -4
  22. package/templates/skills/ralph-loop/steps/step-04-check.md +19 -5
  23. package/templates/skills/ralph-loop/steps/step-05-report.md +35 -3
@@ -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('.ralph/prd.json', prd);
59
+ writeJSON(currentModule.prdFile, prd);
60
60
 
61
- // Load next module's PRD
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('.ralph/prd.json', nextPrd);
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 .ralph/prd.json
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('.ralph/prd.json');
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('.ralph/prd.json', modulePrd);
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('.ralph/prd.json', transformPrdJsonToRalphV2(modulePrd, currentModule.code));
151
+ writeJSON(currentModule.prdFile, transformPrdJsonToRalphV2(modulePrd, currentModule.code));
152
152
  }
153
153
  // v2 legacy
154
154
  else {
155
- writeJSON('.ralph/prd.json', modulePrd);
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('.ralph/prd.json');
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('.ralph/prd.json', prd);
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('.ralph/prd.json', prd);
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('.ralph/prd.json', prd);
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 !== '.ralph/prd.json') {
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('.ralph/prd.json', prd);
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
- If `.ralph/prd.json` exists:
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 `.ralph/prd.json`
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 `.ralph/prd.json` does not exist: continue to section 1b.
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 `.ralph/prd.json` 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).
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
- const prd = readJSON('.ralph/prd.json');
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('.ralph/prd.json', prd);
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('.ralph/prd.json', prd);
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('.ralph/prd.json', prd);
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 .ralph/prd.json`**
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('.ralph/prd.json');
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;
@@ -18,8 +18,19 @@ Commit PRD state changes after apex delegation. Apex already committed code —
18
18
 
19
19
  ### 1. Finalize Task in prd.json
20
20
 
21
+ > **BLOCKING:** If prd.tasks has any task with status='pending' that should be 'completed'
22
+ > based on actual code commits, FIX prd.json BEFORE committing.
23
+
21
24
  ```javascript
22
- const prd = readJSON('.ralph/prd.json');
25
+ // PRD PATH RESOLUTION
26
+ const queuePath = '.ralph/modules-queue.json';
27
+ let currentPrdPath = '.ralph/prd.json';
28
+ if (fileExists(queuePath)) {
29
+ const queue = readJSON(queuePath);
30
+ currentPrdPath = queue.queue[queue.currentIndex].prdFile;
31
+ }
32
+
33
+ const prd = readJSON(currentPrdPath);
23
34
  const now = new Date().toISOString();
24
35
 
25
36
  // Apex updates individual task statuses. Ralph updates overall PRD state.
@@ -29,7 +40,7 @@ prd.status = allDone ? 'completed' : prd.tasks.some(t => t.status === 'failed' |
29
40
  prd.config.current_iteration++;
30
41
  prd.updated_at = now;
31
42
 
32
- writeJSON('.ralph/prd.json', prd);
43
+ writeJSON(currentPrdPath, prd);
33
44
  ```
34
45
 
35
46
  ### 2. Update progress.txt
@@ -44,7 +55,7 @@ Module: {current_module}
44
55
  ### 3. Stage and Commit (PRD state only)
45
56
 
46
57
  ```bash
47
- git add .ralph/prd.json .ralph/progress.txt
58
+ git add {currentPrdPath} .ralph/progress.txt
48
59
  [ -f .ralph/modules-queue.json ] && git add .ralph/modules-queue.json
49
60
 
50
61
  git commit -m "$(cat <<'EOF'
@@ -69,7 +80,7 @@ prd.history.push({
69
80
  commit_hash: COMMIT_HASH,
70
81
  notes: "Delegated to /apex"
71
82
  });
72
- writeJSON('.ralph/prd.json', prd);
83
+ writeJSON(currentPrdPath, prd);
73
84
  ```
74
85
 
75
86
  ### 5. Verify
@@ -15,7 +15,15 @@ Check if all tasks are complete and decide: output completion promise, advance m
15
15
  ## 1. Read Current State
16
16
 
17
17
  ```javascript
18
- const prd = readJSON('.ralph/prd.json');
18
+ // PRD PATH RESOLUTION
19
+ const queuePath = '.ralph/modules-queue.json';
20
+ let currentPrdPath = '.ralph/prd.json';
21
+ if (fileExists(queuePath)) {
22
+ const queue = readJSON(queuePath);
23
+ currentPrdPath = queue.queue[queue.currentIndex].prdFile;
24
+ }
25
+
26
+ const prd = readJSON(currentPrdPath);
19
27
  const tasksCompleted = prd.tasks.filter(t => t.status === 'completed').length;
20
28
  const tasksSkipped = prd.tasks.filter(t => t.status === 'skipped').length;
21
29
  const tasksFailed = prd.tasks.filter(t => t.status === 'failed').length;
@@ -41,7 +49,7 @@ if [ $BUILD_RC -ne 0 ]; then
41
49
  prd.tasks.push({ id: maxId+1, description: "Fix build regression",
42
50
  status: "pending", category: "validation", dependencies: [],
43
51
  acceptance_criteria: "dotnet build passes" });
44
- writeJSON('.ralph/prd.json', prd);
52
+ writeJSON(currentPrdPath, prd);
45
53
  fi
46
54
  ```
47
55
 
@@ -57,7 +65,7 @@ const { missing, guardrailsNeeded } = checkCategoryCompleteness(prd);
57
65
 
58
66
  if (guardrailsNeeded.length > 0) {
59
67
  prd.tasks.push(...guardrailsNeeded);
60
- writeJSON('.ralph/prd.json', prd);
68
+ writeJSON(currentPrdPath, prd);
61
69
  console.log(`PRD updated: +${guardrailsNeeded.length} guardrails, ${prd.tasks.filter(t => t.status === 'pending').length} pending tasks`);
62
70
  }
63
71
  // Artifact verification is handled inside checkCategoryCompleteness() — see reference file.
@@ -117,6 +125,8 @@ if (fileExists(queuePath)) {
117
125
  } else {
118
126
  // All categories complete — advance to next module
119
127
  // (detailed logic in references/module-transition.md)
128
+ // MANDATORY: If this was the LAST module, ALWAYS proceed to step-05-report.md.
129
+ // NEVER stop without generating the final report.
120
130
  }
121
131
  }
122
132
  ```
@@ -128,7 +138,11 @@ ALL TASKS COMPLETE | Iterations: {n} | Tasks: {completed}/{total}
128
138
  <promise>{completion_promise}</promise>
129
139
  ```
130
140
 
131
- Set `prd.status = 'completed'` → step-05.
141
+ Set `prd.status = 'completed'`.
142
+
143
+ **MANDATORY: After displaying completion, ALWAYS load step-05-report.md and generate the report.**
144
+ **NEVER stop after displaying "ALL TASKS COMPLETE" without generating the report.**
145
+ → step-05.
132
146
 
133
147
  ## 4. Dead-End Check
134
148
 
@@ -149,7 +163,7 @@ if (!hasPending && !allDone && retriableCount > 0) {
149
163
  // TRUE dead-end: all retries exhausted
150
164
  console.log(`DEAD-END: ${tasksCompletedNow} done, ${tasksFailed} failed (retries exhausted), ${tasksBlocked} blocked`);
151
165
  prd.status = 'failed';
152
- writeJSON('.ralph/prd.json', prd);
166
+ writeJSON(currentPrdPath, prd);
153
167
  // → step-05
154
168
  }
155
169
  ```
@@ -15,7 +15,17 @@ Generate a comprehensive feature report using structured data from prd.json.
15
15
  ## 1. Load Data
16
16
 
17
17
  ```javascript
18
- const prd = readJSON('.ralph/prd.json');
18
+ // PRD PATH RESOLUTION
19
+ const queuePath = '.ralph/modules-queue.json';
20
+ let currentPrdPath = '.ralph/prd.json';
21
+ if (fileExists(queuePath)) {
22
+ const queue = readJSON(queuePath);
23
+ // For report, use last module's PRD (or current if still in progress)
24
+ const idx = Math.min(queue.currentIndex, queue.modules.length - 1);
25
+ currentPrdPath = queue.modules[idx].prdFile;
26
+ }
27
+
28
+ const prd = readJSON(currentPrdPath);
19
29
  const stats = {
20
30
  feature: prd.feature, status: prd.status,
21
31
  iterations_used: prd.config.current_iteration - 1,
@@ -53,6 +63,28 @@ if (fileExists(queuePath)) {
53
63
  }
54
64
  ```
55
65
 
66
+ ### 1d. Artifact Verification (Post-Execution Validation)
67
+
68
+ ```javascript
69
+ // Verify key artifacts exist on disk
70
+ const allFiles = prd.implementation?.filesToCreate || {};
71
+ const missingFiles = [];
72
+ for (const cat of Object.keys(allFiles)) {
73
+ for (const file of allFiles[cat] || []) {
74
+ if (!fileExists(file.path)) missingFiles.push(file.path);
75
+ }
76
+ }
77
+ if (missingFiles.length > 0) {
78
+ console.warn(`WARNING: ${missingFiles.length} files declared in PRD but not found on disk`);
79
+ for (const f of missingFiles.slice(0, 10)) {
80
+ console.warn(` MISSING: ${f}`);
81
+ }
82
+ if (missingFiles.length > 10) {
83
+ console.warn(` ... and ${missingFiles.length - 10} more`);
84
+ }
85
+ }
86
+ ```
87
+
56
88
  ### 1c. Test Metrics (from PRD)
57
89
 
58
90
  > **Note:** Apex already ran tests and recorded results. Source metrics from PRD task data.
@@ -136,7 +168,7 @@ Write to `.ralph/reports/{feature-slug}.md`:
136
168
  // can resume with `-r` — deleting them here would make recovery impossible.
137
169
  if (prd._sectionSplit?.enabled && prd.status === 'completed') {
138
170
  for (const phase of prd._sectionSplit.phases) {
139
- if (fileExists(phase.prdFile) && phase.prdFile !== '.ralph/prd.json') {
171
+ if (fileExists(phase.prdFile) && phase.prdFile !== currentPrdPath) {
140
172
  deleteFile(phase.prdFile);
141
173
  }
142
174
  }
@@ -146,7 +178,7 @@ if (prd._sectionSplit?.enabled && prd.status === 'completed') {
146
178
  }
147
179
 
148
180
  prd.updated_at = new Date().toISOString();
149
- writeJSON('.ralph/prd.json', prd);
181
+ writeJSON(currentPrdPath, prd);
150
182
  ```
151
183
 
152
184
  ## 4. Display Summary