@atlashub/smartstack-cli 4.47.0 → 4.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "4.47.0",
3
+ "version": "4.48.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -127,6 +127,20 @@ For each module, update screens.json to embed wireframes at the resource level:
127
127
 
128
128
  Write via ba-writer for each module. Update index.json hashes.
129
129
 
130
+ ## Consumer Contract (wireframe field names)
131
+
132
+ > **IMPORTANT:** Downstream consumers (`/business-analyse-html`) depend on these EXACT field names.
133
+ > Any rename requires updating `business-analyse-html/steps/step-02-build-data.md` (wireframe mapping).
134
+
135
+ | Field | Type | Description |
136
+ |-------|------|-------------|
137
+ | `mockupFormat` | `"ascii"` \| `"html"` | Wireframe rendering format |
138
+ | `mockup` | string | Wireframe content (ASCII art or HTML) |
139
+ | `elements` | string[] | UI element names |
140
+ | `componentMapping` | object[] | Wireframe element → React component mapping |
141
+ | `layout` | object | Layout type and regions |
142
+ | `permissionsRequired` | string[] | Permission paths needed |
143
+
130
144
  ## Validation
131
145
 
132
146
  - [ ] Every resource has a wireframe object
@@ -22,7 +22,7 @@ const queuePath = '.ralph/modules-queue.json';
22
22
  let currentPrdPath = '.ralph/prd.json';
23
23
  if (fileExists(queuePath)) {
24
24
  const queue = readJSON(queuePath);
25
- currentPrdPath = queue.queue[queue.currentIndex].prdFile;
25
+ currentPrdPath = queue.modules[queue.currentIndex].prdFile;
26
26
  }
27
27
  ```
28
28
 
@@ -226,6 +226,20 @@ for (const task of batch) {
226
226
  writeJSON(currentPrdPath, prd);
227
227
  ```
228
228
 
229
+ ### B1b. State Persistence (before delegation)
230
+
231
+ ```javascript
232
+ // Write state BEFORE delegating to apex so position survives context compression
233
+ writeJSON('.ralph/ralph-state.json', {
234
+ currentStep: 'compact-loop',
235
+ nextStep: 'step-04-check',
236
+ currentModule: {current_module},
237
+ phase: 'apex-delegating',
238
+ iteration: prdCheck?.config?.current_iteration || 'unknown',
239
+ timestamp: new Date().toISOString()
240
+ });
241
+ ```
242
+
229
243
  ### B2. Invoke /apex
230
244
 
231
245
  **INVOKE `/apex -d {currentPrdPath}`**
@@ -122,11 +122,21 @@ if (missing.length === 0) {
122
122
  // CONTEXT REFRESH: Force /compact between modules to prevent context degradation
123
123
  // (audit ba-002: quality degraded progressively — module 2 had more errors than module 1
124
124
  // because patterns from module 1 were lost during context compaction)
125
+ // STATE PERSISTENCE: Write state BEFORE compaction so position survives context compression
126
+ writeJSON('.ralph/ralph-state.json', {
127
+ currentStep: 'step-01-task',
128
+ currentModule: queue.modules[nextIndex].code,
129
+ reason: 'module-transition',
130
+ fromModule: currentModule.code,
131
+ timestamp: new Date().toISOString()
132
+ });
133
+
125
134
  console.log('Forcing /compact for fresh context before next module...');
126
135
  // The /compact command clears accumulated tool results and intermediate reasoning,
127
136
  // preserving only key decisions and the current state. This ensures the next module
128
137
  // starts with a clean context window instead of degraded fragments from the previous module.
129
138
  // INVOKE /compact
139
+ // After compaction: read .ralph/ralph-state.json to recover position if context lost
130
140
 
131
141
  // Return to step-01 to load next module's tasks
132
142
  // (step-01 will detect module-changed.json and load next PRD)
@@ -91,6 +91,51 @@ See `references/multi-module-queue.md` for queue initialization.
91
91
 
92
92
  Quick: `glob('.ralph/prd-*.json').length > 1` → create modules-queue.json
93
93
 
94
+ ### 4b+. Normalize modules-queue.json (backward-compat with handoff v1 format)
95
+
96
+ ```javascript
97
+ const queuePath = '.ralph/modules-queue.json';
98
+ if (fileExists(queuePath)) {
99
+ const raw = readJSON(queuePath);
100
+ let dirty = false;
101
+
102
+ // RC1: Rename "queue" → "modules" property (handoff v1 used "queue")
103
+ if (raw.queue && !raw.modules) {
104
+ raw.modules = raw.queue;
105
+ delete raw.queue;
106
+ dirty = true;
107
+ }
108
+
109
+ // RC2: Rename "moduleCode" → "code" in each entry (handoff v1 used "moduleCode")
110
+ for (const mod of (raw.modules || [])) {
111
+ if (mod.moduleCode && !mod.code) {
112
+ mod.code = mod.moduleCode;
113
+ delete mod.moduleCode;
114
+ dirty = true;
115
+ }
116
+ }
117
+
118
+ // RC3: Add missing fields
119
+ if (raw.currentIndex === undefined) { raw.currentIndex = 0; dirty = true; }
120
+ if (raw.completedModules === undefined) { raw.completedModules = 0; dirty = true; }
121
+ if (raw.totalModules === undefined) { raw.totalModules = raw.modules?.length || 0; dirty = true; }
122
+ for (const mod of (raw.modules || [])) {
123
+ if (!mod.prdFile) { mod.prdFile = `.ralph/prd-${mod.code}.json`; dirty = true; }
124
+ if (!mod.status) { mod.status = 'pending'; dirty = true; }
125
+ }
126
+ // Set first module to in-progress if none started
127
+ if (raw.modules?.length && !raw.modules.some(m => m.status !== 'pending')) {
128
+ raw.modules[0].status = 'in-progress';
129
+ dirty = true;
130
+ }
131
+
132
+ if (dirty) {
133
+ writeJSON(queuePath, raw);
134
+ console.log('modules-queue.json normalized (format v2)');
135
+ }
136
+ }
137
+ ```
138
+
94
139
  ---
95
140
 
96
141
  ## 4c. Team Orchestration (multi-module LEGACY — dependency layer mode only)
@@ -175,4 +220,16 @@ MCP: Ready | Branch: {branch} | PRD: v{prd_version}
175
220
  -> Loading spec...
176
221
  ```
177
222
 
223
+ ### 7b. Initialize State File (context compression defense)
224
+
225
+ ```javascript
226
+ writeJSON('.ralph/ralph-state.json', {
227
+ currentStep: 'step-01-task',
228
+ currentModule: {current_module},
229
+ iteration: 1,
230
+ prdVersion: {prd_version},
231
+ timestamp: new Date().toISOString()
232
+ });
233
+ ```
234
+
178
235
  **Proceed directly to step-01-task.md**
@@ -6,6 +6,9 @@ next_step: steps/step-02-execute.md
6
6
 
7
7
  # Step 1: Load Task
8
8
 
9
+ > **STATE RECOVERY:** If you are unsure which step you are executing (e.g., after context compression),
10
+ > read `.ralph/ralph-state.json` to recover your position.
11
+
9
12
  > **MODULE TRANSITION CHECK:** Before "Only Read Once" rule, check for module transition:
10
13
 
11
14
  ```javascript
@@ -52,7 +55,7 @@ const queuePath = '.ralph/modules-queue.json';
52
55
  let currentPrdPath = '.ralph/prd.json';
53
56
  if (fileExists(queuePath)) {
54
57
  const queue = readJSON(queuePath);
55
- currentPrdPath = queue.queue[queue.currentIndex].prdFile;
58
+ currentPrdPath = queue.modules[queue.currentIndex].prdFile;
56
59
  }
57
60
  ```
58
61
 
@@ -35,7 +35,7 @@ const queuePath = '.ralph/modules-queue.json';
35
35
  let currentPrdPath = '.ralph/prd.json';
36
36
  if (fileExists(queuePath)) {
37
37
  const queue = readJSON(queuePath);
38
- currentPrdPath = queue.queue[queue.currentIndex].prdFile;
38
+ currentPrdPath = queue.modules[queue.currentIndex].prdFile;
39
39
  }
40
40
 
41
41
  const prd = readJSON(currentPrdPath);
@@ -75,6 +75,19 @@ See `references/parallel-execution.md` for complete parallel protocol:
75
75
 
76
76
  Continue with section-split mode or standard mode below.
77
77
 
78
+ #### 3a+. State Persistence (before delegation)
79
+
80
+ ```javascript
81
+ // Write state BEFORE delegating to apex so position survives context compression
82
+ writeJSON('.ralph/ralph-state.json', {
83
+ currentStep: 'step-02-execute',
84
+ nextStep: 'step-03-commit',
85
+ currentModule: {current_module},
86
+ phase: 'apex-delegating',
87
+ timestamp: new Date().toISOString()
88
+ });
89
+ ```
90
+
78
91
  #### 3b. Section-Split Mode
79
92
 
80
93
  ```javascript
@@ -12,6 +12,19 @@ After ALL modules are implemented, run final verification across the ENTIRE proj
12
12
 
13
13
  ---
14
14
 
15
+ ## VERSION GUARD
16
+
17
+ ```javascript
18
+ // This step is ONLY for v4 PRDs. If a v3 PRD reaches here, route back to step-05-report.md.
19
+ const prd = readJSON(currentPrdPath);
20
+ if (prd.$version !== '4.0.0') {
21
+ console.error(`step-02-v4-verify loaded with $version=${prd.$version} — expected 4.0.0`);
22
+ console.log('Routing to step-05-report.md (v3 report generation)');
23
+ // → Load steps/step-05-report.md instead
24
+ return;
25
+ }
26
+ ```
27
+
15
28
  ## FINAL VERIFICATION SEQUENCE
16
29
 
17
30
  ### 1. Full Solution Build
@@ -39,11 +52,18 @@ npm test -- --run 2>&1
39
52
 
40
53
  ### 4. File Reconciliation
41
54
 
42
- For each module's PRD, compare `expectedFiles` against actual files on disk:
55
+ For each module's PRD, compare expected files against actual files on disk:
43
56
 
44
57
  ```javascript
58
+ // v4 uses prd.expectedFiles, v3 uses prd.implementation.filesToCreate (guard: should be v4 here)
59
+ const fileManifest = prd.expectedFiles || prd.implementation?.filesToCreate;
60
+ if (!fileManifest) {
61
+ console.error('BLOCKING: PRD has no file manifest (neither expectedFiles nor implementation.filesToCreate)');
62
+ STOP;
63
+ }
64
+
45
65
  for (const category of ['domain', 'application', 'infrastructure', 'api', 'frontend', 'seedData', 'tests', 'documentation']) {
46
- const expected = prd.expectedFiles[category] || [];
66
+ const expected = fileManifest[category] || [];
47
67
  for (const file of expected) {
48
68
  if (!existsOnDisk(file.path)) {
49
69
  MISSING.push({ category, path: file.path, type: file.type });
@@ -27,7 +27,7 @@ const queuePath = '.ralph/modules-queue.json';
27
27
  let currentPrdPath = '.ralph/prd.json';
28
28
  if (fileExists(queuePath)) {
29
29
  const queue = readJSON(queuePath);
30
- currentPrdPath = queue.queue[queue.currentIndex].prdFile;
30
+ currentPrdPath = queue.modules[queue.currentIndex].prdFile;
31
31
  }
32
32
 
33
33
  const prd = readJSON(currentPrdPath);
@@ -6,6 +6,9 @@ next_step: steps/step-05-report.md OR steps/step-01-task.md
6
6
 
7
7
  # Step 4: Check Completion
8
8
 
9
+ > **STATE RECOVERY:** If you are unsure which step you are executing (e.g., after context compression),
10
+ > read `.ralph/ralph-state.json` to recover your position.
11
+
9
12
  ## YOUR TASK:
10
13
 
11
14
  Check if all tasks are complete and decide: output completion promise, advance module, or continue loop.
@@ -20,7 +23,7 @@ const queuePath = '.ralph/modules-queue.json';
20
23
  let currentPrdPath = '.ralph/prd.json';
21
24
  if (fileExists(queuePath)) {
22
25
  const queue = readJSON(queuePath);
23
- currentPrdPath = queue.queue[queue.currentIndex].prdFile;
26
+ currentPrdPath = queue.modules[queue.currentIndex].prdFile;
24
27
  }
25
28
 
26
29
  const prd = readJSON(currentPrdPath);
@@ -80,6 +80,47 @@ ss business-analyse-handoff --feature {path} --output .ralph/prd-{moduleCode}.js
80
80
 
81
81
  ---
82
82
 
83
+ ### 2b. PRD v4.0 Structure (spec-oriented)
84
+
85
+ > **v4 PRDs are generated with `--v4` flag.** They use `expectedFiles` instead of `implementation.filesToCreate`.
86
+
87
+ ```json
88
+ {
89
+ "$version": "4.0.0",
90
+ "objectives": [
91
+ { "description": "...", "acceptanceCriteria": ["..."] }
92
+ ],
93
+ "expectedFiles": {
94
+ "domain": [...],
95
+ "application": [...],
96
+ "infrastructure": [...],
97
+ "api": [...],
98
+ "frontend": [...],
99
+ "seedData": [...],
100
+ "tests": [...],
101
+ "documentation": [...]
102
+ },
103
+ "gates": { /* declarative quality gate definitions */ }
104
+ }
105
+ ```
106
+
107
+ **Key differences from v3:**
108
+
109
+ | Aspect | v3 (`$version: "3.0.0"`) | v4 (`$version: "4.0.0"`) |
110
+ |--------|--------------------------|--------------------------|
111
+ | File manifest | `implementation.filesToCreate` | `expectedFiles` (root level) |
112
+ | Task model | `tasks[]` (pre-computed) | `objectives[]` (goal-oriented) |
113
+ | Execution | Batch iterations (compact-loop) | Continuous (compaction-enabled) |
114
+
115
+ **Consumer code must handle both:**
116
+ ```javascript
117
+ const fileManifest = prd.$version === '4.0.0'
118
+ ? prd.expectedFiles
119
+ : prd.implementation?.filesToCreate;
120
+ ```
121
+
122
+ ---
123
+
83
124
  ### 3. Eight Categories (MANDATORY)
84
125
 
85
126
  All PRD files MUST include these 8 categories under `implementation.filesToCreate`:
@@ -157,7 +157,9 @@ After writing, verify:
157
157
  2. All 8 categories present
158
158
  3. brToCodeMapping non-empty
159
159
  4. Section resources have entity field
160
- Display: "POST-CHECK PASS: {moduleCode} -- 8 categories, {brCount} BRs mapped"
160
+ 5. SeedData contains CORE entries (NavigationModuleSeedData, NavigationSectionSeedData if sections exist, PermissionsSeedData, RolesSeedData)
161
+ 6. For FIRST module only: SeedData contains APP-LEVEL CORE entries (NavigationApplicationSeedData, ApplicationRolesSeedData)
162
+ Display: "POST-CHECK PASS: {moduleCode} -- 8 categories, {brCount} BRs mapped, {coreCount} core seeds"
161
163
  ```
162
164
 
163
165
  ### 3. Display Progress
@@ -99,11 +99,13 @@ Display verification table showing all 8 categories match between module JSON fi
99
99
 
100
100
  ```json
101
101
  {
102
- "queue": [
103
- { "moduleCode": "{code}", "order": 1, "dependencies": [], "complexity": "simple" },
104
- { "moduleCode": "{code}", "order": 2, "dependencies": ["{dep}"], "complexity": "medium" }
102
+ "modules": [
103
+ { "code": "{code}", "prdFile": ".ralph/prd-{code}.json", "status": "pending", "order": 1, "dependencies": [], "complexity": "simple" },
104
+ { "code": "{code}", "prdFile": ".ralph/prd-{code}.json", "status": "pending", "order": 2, "dependencies": ["{dep}"], "complexity": "medium" }
105
105
  ],
106
- "totalModules": {count},
106
+ "currentIndex": 0,
107
+ "totalModules": "{count}",
108
+ "completedModules": 0,
107
109
  "strategy": "topological"
108
110
  }
109
111
  ```
@@ -198,13 +198,34 @@ handoff: {
198
198
 
199
199
  ```javascript
200
200
  const mod = collected_data.modules[moduleCode];
201
- const screens = mod.screens?.screens || [];
202
201
 
203
- // Extract wireframes from screens that have mockup/wireframe data
202
+ // STEP 1: Collect wireframe sources from BOTH flat and nested structures
203
+ // Flat: mod.screens.screens[] with mockupFormat/mockup at top level
204
+ // Nested: mod.screens.sections[].resources[].wireframe (design step output)
205
+ let rawWireframes = [];
206
+
207
+ // Source A: flat screens[] array (original BA output or manually enriched)
208
+ const flatScreens = mod.screens?.screens || [];
209
+ rawWireframes.push(...flatScreens.filter(s => s.mockup || s.mockupFormat));
210
+
211
+ // Source B: nested sections[].resources[].wireframe (design step output)
212
+ const sections = mod.screens?.sections || [];
213
+ for (const section of sections) {
214
+ for (const resource of (section.resources || [])) {
215
+ if (resource.wireframe) {
216
+ // Unwrap nested wireframe and add section context
217
+ rawWireframes.push({
218
+ ...resource.wireframe,
219
+ section: resource.wireframe.section || section.sectionCode || "",
220
+ screen: resource.wireframe.screen || `${section.sectionCode}-${resource.code}` || ""
221
+ });
222
+ }
223
+ }
224
+ }
225
+
226
+ // STEP 2: Map to HTML format (RENAME: mockupFormat → format, mockup → content)
204
227
  wireframes: {
205
- [moduleCode]: screens
206
- .filter(s => s.wireframe || s.mockup || s.mockupFormat)
207
- .map(wf => ({
228
+ [moduleCode]: rawWireframes.map(wf => ({
208
229
  screen: wf.screen || wf.name || wf.title || wf.id || "",
209
230
  section: wf.section || "",
210
231
  format: wf.mockupFormat || "ascii", // RENAME: mockupFormat → format