@atlashub/smartstack-cli 4.45.0 → 4.46.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.45.0",
3
+ "version": "4.46.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -16,8 +16,8 @@
16
16
  | `entities.json` | Entity definitions | `entities[]` | — |
17
17
  | `rules.json` | Business rules | `rules[]` | `businessRules[]` |
18
18
  | `usecases.json` | Use cases | `useCases[]` | `usecases[]` (lowercase) |
19
- | `permissions.json` | Roles and permission matrix | `permissionPaths[]` | `permissionSet.permissions[]`, `permissions[]`, `actions[]` |
20
- | `screens.json` | UI screen specifications | `screens[]` | `sections[].resources[]` |
19
+ | `permissions.json` | Roles and permission matrix | `permissionPaths[]` | `permissionSet.permissions[]`, `basePermissions[]`, `actions[]`, `permissions[]` |
20
+ | `screens.json` | UI screen specifications | `screens[]` | `sections[].resources[]`, `sections[]`, `wireframes[]` |
21
21
 
22
22
  The validation script **assembles** these files before checking.
23
23
 
@@ -66,17 +66,30 @@ const fs = require('fs');
66
66
  const path = require('path');
67
67
  const dir = process.argv[1];
68
68
 
69
- // Load index.json
70
- const index = JSON.parse(fs.readFileSync(path.join(dir, 'index.json'), 'utf-8'));
69
+ // Load index.json (optional — may not exist at module level)
70
+ let index = {};
71
+ const indexPath = path.join(dir, 'index.json');
72
+ if (fs.existsSync(indexPath)) {
73
+ index = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
74
+ }
71
75
  const files = index.files || {};
72
76
 
73
- // Load flat files (with fallbacks)
77
+ // Load flat files: try index.json refs first, fallback to direct file load
74
78
  function loadFile(key) {
79
+ // Strategy 1: resolve via index.json > files references
75
80
  const ref = files[key];
76
- if (!ref || !ref.path) return null;
77
- const filePath = path.join(dir, ref.path);
78
- if (!fs.existsSync(filePath)) return null;
79
- return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
81
+ if (ref && ref.path) {
82
+ const filePath = path.join(dir, ref.path);
83
+ if (fs.existsSync(filePath)) {
84
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
85
+ }
86
+ }
87
+ // Strategy 2: direct file load (module dirs without index.json)
88
+ const directPath = path.join(dir, key + '.json');
89
+ if (fs.existsSync(directPath)) {
90
+ return JSON.parse(fs.readFileSync(directPath, 'utf-8'));
91
+ }
92
+ return null;
80
93
  }
81
94
 
82
95
  const entitiesData = loadFile('entities') || {};
@@ -91,19 +104,40 @@ const hasReferencedEntities = (entitiesData.referencedEntities || []).length > 0
91
104
  const useCases = usecasesData.useCases || usecasesData.usecases || [];
92
105
  const rules = rulesData.rules || rulesData.businessRules || [];
93
106
 
94
- // Permissions: canonical permissionPaths[] OR extract from nested structures
95
- const permissions = permissionsData.permissionPaths
96
- || (permissionsData.permissionSet && permissionsData.permissionSet.permissions || []).map(function(p) { return p.code || p.path || ''; })
97
- || (permissionsData.permissions || []).map(function(p) { return typeof p === 'string' ? p : (p.code || p.path || ''); })
98
- || (permissionsData.actions || []).map(function(a) { return a.code || ''; })
99
- || [];
100
-
101
- // Screens: canonical screens[] OR flatten sections[].resources[]
102
- const screens = screensData.screens || [];
103
- const sectionsAsScreens = (screensData.sections || []).reduce(function(acc, s) { return acc.concat(s.resources || []); }, []);
104
- const allScreens = screens.length > 0 ? screens : sectionsAsScreens;
107
+ // Permissions: try all known variants, extract into flat array
108
+ function extractPermissions(data) {
109
+ if (data.permissionPaths && data.permissionPaths.length > 0) return data.permissionPaths;
110
+ if (data.permissionSet && data.permissionSet.permissions && data.permissionSet.permissions.length > 0) {
111
+ return data.permissionSet.permissions.map(function(p) { return p.code || p.path || ''; });
112
+ }
113
+ if (data.basePermissions && data.basePermissions.length > 0) {
114
+ return data.basePermissions.map(function(p) { return p.code || p.path || ''; });
115
+ }
116
+ if (data.actions && data.actions.length > 0) {
117
+ return data.actions.map(function(a) { return a.code || ''; });
118
+ }
119
+ if (data.permissions && data.permissions.length > 0) {
120
+ return data.permissions.map(function(p) { return typeof p === 'string' ? p : (p.code || p.path || ''); });
121
+ }
122
+ return [];
123
+ }
124
+ const permissions = extractPermissions(permissionsData);
125
+
126
+ // Screens: canonical screens[] OR sections[] (with or without .resources sub-array)
127
+ function extractScreens(data) {
128
+ if (data.screens && data.screens.length > 0) return data.screens;
129
+ if (data.sections && data.sections.length > 0) {
130
+ // sections may contain resources[] or be screens themselves
131
+ var fromResources = data.sections.reduce(function(acc, s) { return acc.concat(s.resources || []); }, []);
132
+ return fromResources.length > 0 ? fromResources : data.sections;
133
+ }
134
+ if (data.wireframes && data.wireframes.length > 0) return data.wireframes;
135
+ return [];
136
+ }
137
+ const allScreens = extractScreens(screensData);
105
138
 
106
- const status = index.metadata?.status || 'unknown';
139
+ // Status: from index.json if present, or 'specified' if module has thematic files but no index.json
140
+ const status = (index.metadata && index.metadata.status) || (Object.keys(index).length === 0 ? 'specified' : 'unknown');
107
141
 
108
142
  const fails = [];
109
143
 
@@ -71,32 +71,27 @@ FOR each module:
71
71
 
72
72
  ### 4. Load Flat Files Per Module
73
73
 
74
- For each module, load the separate JSON files referenced in `index.json > files`:
74
+ For each module, load the separate JSON files directly (index.json may not exist at module level):
75
75
 
76
76
  ```javascript
77
- // Module index.json contains file references:
78
- // { "files": { "entities": { "path": "entities.json" }, "usecases": { "path": "usecases.json" }, ... } }
79
-
80
- const entitiesData = READ(moduleDir + '/entities.json');
81
- const usecasesData = READ(moduleDir + '/usecases.json');
82
- const rulesData = READ(moduleDir + '/rules.json');
83
- const permissionsData = READ(moduleDir + '/permissions.json');
84
- const screensData = READ(moduleDir + '/screens.json');
77
+ // Load thematic files directly from module directory
78
+ const entitiesData = READ(moduleDir + '/entities.json') || {};
79
+ const usecasesData = READ(moduleDir + '/usecases.json') || {};
80
+ const rulesData = READ(moduleDir + '/rules.json') || {};
81
+ const permissionsData = READ(moduleDir + '/permissions.json') || {};
82
+ const screensData = READ(moduleDir + '/screens.json') || {};
85
83
 
86
84
  // NORMALISATION: tolerate ba-writer + LLM field name variants
85
+ // See acceptance-criteria.md for the full extraction functions
87
86
  const moduleData = {
88
- index: READ(moduleDir + '/index.json'),
89
87
  entities: entitiesData.entities || [],
90
88
  hasReferencedEntities: (entitiesData.referencedEntities || []).length > 0,
91
89
  useCases: usecasesData.useCases || usecasesData.usecases || [],
92
90
  rules: rulesData.rules || rulesData.businessRules || [],
93
- permissions: permissionsData.permissionPaths
94
- || (permissionsData.permissionSet?.permissions || []).map(p => p.code || p.path)
95
- || (permissionsData.permissions || []).map(p => typeof p === 'string' ? p : (p.code || p.path))
96
- || [],
97
- screens: (screensData.screens || []).length > 0
98
- ? screensData.screens
99
- : (screensData.sections || []).flatMap(s => s.resources || [])
91
+ permissions: extractPermissions(permissionsData),
92
+ // tries: permissionPaths > permissionSet.permissions > basePermissions > actions > permissions
93
+ screens: extractScreens(screensData),
94
+ // tries: screens > sections[].resources > sections > wireframes
100
95
  };
101
96
  ```
102
97