@atlashub/smartstack-cli 4.30.0 → 4.31.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 (40) hide show
  1. package/dist/index.js +17 -4
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/skills/apex/references/code-generation.md +1 -1
  5. package/templates/skills/apex/references/person-extension-pattern.md +23 -2
  6. package/templates/skills/apex/references/post-checks.md +52 -0
  7. package/templates/skills/apex/references/smartstack-api.md +111 -0
  8. package/templates/skills/apex/references/smartstack-frontend.md +25 -2
  9. package/templates/skills/apex/references/smartstack-layers.md +1 -0
  10. package/templates/skills/apex/steps/step-03-execute.md +110 -7
  11. package/templates/skills/application/templates-frontend.md +1 -1
  12. package/templates/skills/ba-generate-html/SKILL.md +1 -1
  13. package/templates/skills/ba-generate-html/html/ba-interactive.html +42 -78
  14. package/templates/skills/ba-generate-html/html/src/partials/cadrage-scope.html +14 -36
  15. package/templates/skills/ba-generate-html/html/src/partials/decomp-modules.html +0 -8
  16. package/templates/skills/ba-generate-html/html/src/scripts/01-data-init.js +20 -20
  17. package/templates/skills/ba-generate-html/html/src/scripts/03-render-cadrage.js +4 -3
  18. package/templates/skills/ba-generate-html/html/src/scripts/04-render-modules.js +0 -2
  19. package/templates/skills/ba-generate-html/html/src/scripts/07-render-handoff.js +2 -5
  20. package/templates/skills/ba-generate-html/html/src/scripts/10-comments.js +1 -1
  21. package/templates/skills/ba-generate-html/html/src/styles/04-cards.css +2 -4
  22. package/templates/skills/ba-generate-html/html/src/template.html +14 -44
  23. package/templates/skills/ba-generate-html/references/data-build.md +4 -9
  24. package/templates/skills/ba-generate-html/references/data-mapping.md +2 -7
  25. package/templates/skills/ba-generate-html/references/output-modes.md +1 -1
  26. package/templates/skills/ba-generate-html/steps/step-02-build-data.md +3 -6
  27. package/templates/skills/ba-generate-html/steps/step-04-verify.md +2 -2
  28. package/templates/skills/ba-review/references/review-data-mapping.md +4 -6
  29. package/templates/skills/ba-review/steps/step-01-apply.md +2 -4
  30. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +4 -4
  31. package/templates/skills/business-analyse/questionnaire.md +1 -1
  32. package/templates/skills/business-analyse/react/schema.md +2 -7
  33. package/templates/skills/business-analyse/schemas/application-schema.json +2 -9
  34. package/templates/skills/business-analyse/schemas/project-schema.json +4 -8
  35. package/templates/skills/business-analyse/schemas/sections/discovery-schema.json +1 -3
  36. package/templates/skills/business-analyse/steps/step-01-cadrage.md +5 -12
  37. package/templates/skills/business-analyse/steps/step-02-structure.md +3 -5
  38. package/templates/skills/dev-start/SKILL.md +242 -0
  39. package/templates/skills/ui-components/SKILL.md +1 -1
  40. package/templates/skills/ui-components/patterns/data-table.md +1 -1
@@ -41,13 +41,9 @@ Build a JSON object following this **exact mapping** from index.json to the HTML
41
41
  frustrations: (s.painPoints || []).join("\n")
42
42
  })),
43
43
  scope: {
44
- vital: (master.cadrage.globalScope.mustHave || [])
44
+ inscope: (master.cadrage.globalScope.inScope || [])
45
45
  .map(item => ({ name: item, description: "" })), // string[] → {name,description}[]
46
- important: (master.cadrage.globalScope.shouldHave || [])
47
- .map(item => ({ name: item, description: "" })),
48
- optional: (master.cadrage.globalScope.couldHave || [])
49
- .map(item => ({ name: item, description: "" })),
50
- excluded: (master.cadrage.globalScope.outOfScope || [])
46
+ outofscope: (master.cadrage.globalScope.outOfScope || [])
51
47
  .map(item => ({ name: item, description: "" }))
52
48
  },
53
49
  criteria: (master.cadrage.acceptanceCriteria || []).map(ac => ({
@@ -60,7 +56,6 @@ Build a JSON object following this **exact mapping** from index.json to the HTML
60
56
  name: m.name || m.code, // display name (MANDATORY, separate from code)
61
57
  description: m.description || "",
62
58
  featureType: m.featureType || "data-centric",
63
- priority: m.priority || "must",
64
59
  entities: m.entities || [],
65
60
  anticipatedSections: m.anticipatedSections || [], // [{code, description, resources[]}]
66
61
  dependencies: m.dependencies || [],
@@ -111,7 +111,7 @@ Full handoff across multiple applications within a project:
111
111
  | Manifest entries incorrect | Verify appCode, moduleCode, and version match index.json metadata exactly. |
112
112
  | ba-interactive.html is too small (< 100KB) | FEATURE_DATA not serialized correctly. Verify JSON structure. Re-run FEATURE_DATA build. |
113
113
  | moduleSpecs missing from HTML | FEATURE_DATA.moduleSpecs is empty or not included in JSON. Verify each module is iterated and populated. |
114
- | Scope keys still show "mustHave" instead of "vital" | Conversion from JSON keys to HTML keys failed. Check cadrage.scope conversion logic. |
114
+ | Scope keys still show "inScope" instead of "inscope" | Conversion from JSON keys to HTML keys failed. Check cadrage.scope conversion logic. |
115
115
  | Wireframes array is empty | Verify each module has specification.uiWireframes. If missing, add wireframe stubs. |
116
116
  | dependencyGraph missing | Verify master index.json has consolidation.dependencyGraph. If missing, create empty nodes/edges arrays. |
117
117
  | HTML opens but data blank (localStorage issue) | Clear browser localStorage. HTML will load pre-populated FEATURE_DATA on first load. |
@@ -64,10 +64,8 @@ stakeholders: master.cadrage.stakeholders.map(s => ({
64
64
  **cadrage.scope** (KEY CONVERSION — CRITICAL):
65
65
  ```javascript
66
66
  scope: {
67
- vital: (master.cadrage.globalScope?.mustHave || []).map(item => ({ name: item, description: "" })),
68
- important: (master.cadrage.globalScope?.shouldHave || []).map(item => ({ name: item, description: "" })),
69
- optional: (master.cadrage.globalScope?.couldHave || []).map(item => ({ name: item, description: "" })),
70
- excluded: (master.cadrage.globalScope?.outOfScope || []).map(item => ({ name: item, description: "" }))
67
+ inscope: (master.cadrage.globalScope?.inScope || []).map(item => ({ name: item, description: "" })),
68
+ outofscope: (master.cadrage.globalScope?.outOfScope || []).map(item => ({ name: item, description: "" }))
71
69
  }
72
70
  ```
73
71
 
@@ -81,7 +79,6 @@ modules: master.modules.map(m => ({
81
79
  name: m.name || m.code, // display name (MANDATORY)
82
80
  description: m.description || "",
83
81
  featureType: m.featureType || "data-centric",
84
- priority: m.priority || "must",
85
82
  entities: m.entities || [],
86
83
  anticipatedSections: m.anticipatedSections || [],
87
84
  dependencies: m.dependencies || [],
@@ -239,7 +236,7 @@ dependencyGraph: {
239
236
  ## VALIDATION
240
237
 
241
238
  - FEATURE_DATA.moduleSpecs must have ONE entry per module
242
- - cadrage.scope uses HTML keys (vital/important/optional/excluded)
239
+ - cadrage.scope uses HTML keys (inscope/outofscope)
243
240
  - Wireframe fields use format/content (NOT mockupFormat/mockup)
244
241
 
245
242
  ## NEXT STEP
@@ -30,9 +30,9 @@ if ! grep -q '"moduleSpecs"' ba-interactive.html; then
30
30
  fi
31
31
  ```
32
32
 
33
- **Check 3 — cadrage.scope uses HTML keys (vital/important, NOT mustHave):**
33
+ **Check 3 — cadrage.scope uses HTML keys (inscope/outofscope, NOT inScope):**
34
34
  ```
35
- if grep -q '"mustHave"' ba-interactive.html; then
35
+ if grep -q '"inScope"' ba-interactive.html; then
36
36
  BLOCKING_ERROR("cadrage.scope still has JSON keys — conversion failed")
37
37
  fi
38
38
  ```
@@ -17,15 +17,13 @@ The ba-review.json is exported from the interactive HTML with the same structure
17
17
  ### Scope (HTML → index.json)
18
18
 
19
19
  ```javascript
20
- // HTML format: { vital: [{name, description}], important: [...], optional: [...], excluded: [...] }
21
- // index.json format: { mustHave: [string], shouldHave: [string], couldHave: [string], outOfScope: [string] }
20
+ // HTML format: { inscope: [{name, description}], outofscope: [{name, description}] }
21
+ // index.json format: { inScope: [string], outOfScope: [string] }
22
22
 
23
23
  reverseScope(reviewCadrage) {
24
24
  return {
25
- mustHave: (reviewCadrage.scope?.vital || []).map(s => s.name),
26
- shouldHave: (reviewCadrage.scope?.important || []).map(s => s.name),
27
- couldHave: (reviewCadrage.scope?.optional || []).map(s => s.name),
28
- outOfScope: (reviewCadrage.scope?.excluded || []).map(s => s.name)
25
+ inScope: (reviewCadrage.scope?.inscope || []).map(s => s.name),
26
+ outOfScope: (reviewCadrage.scope?.outofscope || []).map(s => s.name)
29
27
  };
30
28
  }
31
29
  ```
@@ -91,10 +91,8 @@ Read the new version's cadrage.json and apply reverse mapping from review data:
91
91
 
92
92
  ```
93
93
  1. Scope mapping (review → cadrage.json):
94
- - cadrage.scope.vital[] → cadrage.globalScope.mustHave[]
95
- - cadrage.scope.important[] → cadrage.globalScope.shouldHave[]
96
- - cadrage.scope.optional[] → cadrage.globalScope.couldHave[]
97
- - cadrage.scope.excluded[] → cadrage.globalScope.outOfScope[]
94
+ - cadrage.scope.inscope[] → cadrage.globalScope.inScope[]
95
+ - cadrage.scope.outofscope[] → cadrage.globalScope.outOfScope[]
98
96
  (Extract name from {name, description} objects)
99
97
 
100
98
  2. Stakeholders mapping:
@@ -75,7 +75,7 @@ Suggests system integrations based on requirements:
75
75
 
76
76
  ### 4.1 Trigger Point
77
77
 
78
- After step-01-cadrage completes scope definition (scope.mustHave + scope.shouldHave populated):
78
+ After step-01-cadrage completes scope definition (scope.inScope populated):
79
79
 
80
80
  ```
81
81
  Step-01: Analyse → Define Scope → Load Suggestion Catalog → Present Suggestions
@@ -86,10 +86,10 @@ Step-01: Analyse → Define Scope → Load Suggestion Catalog → Present Sugges
86
86
  ```
87
87
  Input: index.json (module name, keywords, must-haves, should-haves)
88
88
 
89
- 1. Read scope.mustHave + scope.shouldHave (user-defined features)
89
+ 1. Read scope.inScope (user-defined features)
90
90
  2. Extract primary keywords (module type) from scope.description
91
91
  3. Match against Module Patterns table → Suggested Modules
92
- 4. Analyze scope.mustHave + scope.shouldHave for conditions → Suggested Sections
92
+ 4. Analyze scope.inScope for conditions → Suggested Sections
93
93
  5. Detect mentions of integrations → Suggested Integrations
94
94
  6. Filter out already-present modules/sections
95
95
  7. Rank by relevance (keyword match > condition confidence)
@@ -207,7 +207,7 @@ Select all that apply: [ ] [ ] [ ] [ ]
207
207
 
208
208
  When user selects suggestions:
209
209
 
210
- 1. **Add Selected Modules:** Update scope.mustHave with new modules
210
+ 1. **Add Selected Modules:** Update scope.inScope with new modules
211
211
  2. **Add Selected Sections:** Update specification.sections (in step-02)
212
212
  3. **Add Selected Integrations:** Update specification.integrations
213
213
  4. **Store Decisions:** Log in index.json.suggestions[]
@@ -35,7 +35,7 @@
35
35
  | File | Questions | Phase | Focus |
36
36
  |------|-----------|-------|-------|
37
37
  | `questionnaire/01-context.md` | 3 | step-01-cadrage | Business process, friction points, vision |
38
- | `questionnaire/02-stakeholders-scope.md` | 10 | step-01-cadrage | User profiles, access levels, scope boundaries, MoSCoW |
38
+ | `questionnaire/02-stakeholders-scope.md` | 10 | step-01-cadrage | User profiles, access levels, scope boundaries |
39
39
  | `questionnaire/03-data-ui.md` | ~15 | step-03-specify (per module) | Data entities, UI expectations, dashboards |
40
40
  | `questionnaire/04-risks-metrics.md` | — | NOT USED | Risks/metrics out of BA scope |
41
41
  | `questionnaire/05-cross-module.md` | ~8 | step-03-specify (conditional) | Cross-module dependencies, integration impact |
@@ -69,9 +69,7 @@ export interface ApplicationCadrage {
69
69
  }
70
70
 
71
71
  export interface GlobalScope {
72
- mustHave: string[];
73
- shouldHave: string[];
74
- couldHave: string[];
72
+ inScope: string[];
75
73
  outOfScope: string[];
76
74
  }
77
75
 
@@ -88,7 +86,6 @@ export interface ApplicationModule {
88
86
  description: string;
89
87
  featureType: string;
90
88
  entities: string[];
91
- priority: 'must' | 'should' | 'could';
92
89
  estimatedComplexity: 'simple' | 'medium' | 'complex';
93
90
  status: 'pending' | 'in-progress' | 'specified' | 'validated';
94
91
  featureJsonPath?: string;
@@ -246,9 +243,7 @@ export interface Stakeholder {
246
243
  }
247
244
 
248
245
  export interface FeatureScope {
249
- mustHave: string[];
250
- shouldHave: string[];
251
- couldHave: string[];
246
+ inScope: string[];
252
247
  outOfScope: string[];
253
248
  mainFlow: string[];
254
249
  alternativeFlows: AlternativeFlow[];
@@ -133,9 +133,7 @@
133
133
  "globalScope": {
134
134
  "type": "object",
135
135
  "properties": {
136
- "mustHave": { "type": "array", "items": { "type": "string" } },
137
- "shouldHave": { "type": "array", "items": { "type": "string" } },
138
- "couldHave": { "type": "array", "items": { "type": "string" } },
136
+ "inScope": { "type": "array", "items": { "type": "string" } },
139
137
  "outOfScope": { "type": "array", "items": { "type": "string" } }
140
138
  }
141
139
  },
@@ -193,7 +191,7 @@
193
191
  "required": ["item", "category", "module"],
194
192
  "properties": {
195
193
  "item": { "type": "string", "description": "Requirement from the original brief" },
196
- "category": { "type": "string", "enum": ["mustHave", "shouldHave", "couldHave", "outOfScope", "implicit"] },
194
+ "category": { "type": "string", "enum": ["inScope", "outOfScope", "implicit"] },
197
195
  "module": { "type": ["string", "null"], "description": "Module code that covers this requirement (null if cross-cutting)" },
198
196
  "ucRef": { "type": ["string", "null"], "description": "UC ID if already assigned" },
199
197
  "notes": { "type": "string" },
@@ -246,11 +244,6 @@
246
244
  "type": ["string", "null"],
247
245
  "description": "Path to the module-level index.json once created"
248
246
  },
249
- "priority": {
250
- "type": "string",
251
- "enum": ["must", "should", "could"],
252
- "description": "Priority level from global scope"
253
- },
254
247
  "sortOrder": {
255
248
  "type": "integer",
256
249
  "description": "Position in topological order (0-based)"
@@ -118,11 +118,9 @@
118
118
  },
119
119
  "globalScope": {
120
120
  "type": "object",
121
- "description": "Project-wide MoSCoW scope",
121
+ "description": "Project-wide binary scope",
122
122
  "properties": {
123
- "mustHave": { "type": "array", "items": { "type": "string" } },
124
- "shouldHave": { "type": "array", "items": { "type": "string" } },
125
- "couldHave": { "type": "array", "items": { "type": "string" } },
123
+ "inScope": { "type": "array", "items": { "type": "string" } },
126
124
  "outOfScope": { "type": "array", "items": { "type": "string" } }
127
125
  }
128
126
  },
@@ -189,7 +187,7 @@
189
187
  "required": ["item", "category"],
190
188
  "properties": {
191
189
  "item": { "type": "string", "description": "Requirement from the original brief" },
192
- "category": { "type": "string", "enum": ["mustHave", "shouldHave", "couldHave", "outOfScope", "implicit"] },
190
+ "category": { "type": "string", "enum": ["inScope", "outOfScope", "implicit"] },
193
191
  "application": { "type": ["string", "null"], "description": "Application code (null if cross-cutting)" },
194
192
  "module": { "type": ["string", "null"], "description": "Module code (null if application-level)" },
195
193
  "notes": { "type": "string" },
@@ -251,9 +249,7 @@
251
249
  "type": "object",
252
250
  "description": "Application-specific scope (subset of global scope)",
253
251
  "properties": {
254
- "mustHave": { "type": "array", "items": { "type": "string" } },
255
- "shouldHave": { "type": "array", "items": { "type": "string" } },
256
- "couldHave": { "type": "array", "items": { "type": "string" } }
252
+ "inScope": { "type": "array", "items": { "type": "string" } }
257
253
  }
258
254
  },
259
255
  "modules": {
@@ -27,9 +27,7 @@
27
27
  "scope": {
28
28
  "type": "object",
29
29
  "properties": {
30
- "mustHave": { "type": "array", "items": { "type": "string" } },
31
- "shouldHave": { "type": "array", "items": { "type": "string" } },
32
- "couldHave": { "type": "array", "items": { "type": "string" } },
30
+ "inScope": { "type": "array", "items": { "type": "string" } },
33
31
  "outOfScope": { "type": "array", "items": { "type": "string" } },
34
32
  "mainFlow": { "type": "string" },
35
33
  "decisionPoints": { "type": "array", "items": { "type": "string" } },
@@ -232,10 +232,9 @@ Ask in 1-2 batches. After each batch:
232
232
 
233
233
  #### 4c. Functional Scope (ALWAYS — from `questionnaire/02-stakeholders-scope.md`)
234
234
 
235
- **Mandatory minimum:** Q2.13 (must-have), Q2.15 (exclusions), Q2.16 (main journey).
235
+ **Mandatory minimum:** Q2.13 (in-scope), Q2.15 (exclusions), Q2.16 (main journey).
236
236
 
237
237
  Ask in 1-2 batches. After each batch:
238
- - If everything is "must-have" → **BLOCKING**: apply MoSCoW prioritization question
239
238
  - If no exclusions → probe: "What should this system explicitly NOT do?"
240
239
  - If journey is too simple → detail: "What happens when something goes wrong? When someone cancels?"
241
240
 
@@ -252,10 +251,6 @@ Ask in 1-2 batches. After each batch:
252
251
  > ```
253
252
  >
254
253
  > These error flows become **Business Rules** in step-03 (BR-WF category: workflow/guard conditions).
255
- >
256
- > **BLOCKING RULE — MoSCoW DISTRIBUTION:**
257
- > If the client classifies ALL features as "must-have", you MUST challenge this BEFORE proceeding.
258
- > If client moves items → update categories. If client confirms ALL mustHave → accept but log in changelog.
259
254
 
260
255
  #### 4d. Challenge Implicit Assumptions (CRITICAL)
261
256
 
@@ -435,7 +430,7 @@ options:
435
430
  | Choice | Action |
436
431
  |--------|--------|
437
432
  | **Géré dans l'app** | Entity becomes a sub-entity of the parent module. Add as a **tab in the detail page** of the referencing module. Note in coverageMatrix with `parentModule` field. |
438
- | **Nouveau module dédié** | **Add new module to `{pre_analysis}.detected_modules[]`** with the proposed architecture (sections, detail tabs, dependencies). Add to `coverageMatrix` as mustHave. The `list` section is ALWAYS the main section. Additional sections ONLY for distinct functional zones (dashboard, approve, import...). `create` and `edit` are ACTIONS within `list` and detail pages, NEVER standalone sections. |
433
+ | **Nouveau module dédié** | **Add new module to `{pre_analysis}.detected_modules[]`** with the proposed architecture (sections, detail tabs, dependencies). Add to `coverageMatrix` as inScope. The `list` section is ALWAYS the main section. Additional sections ONLY for distinct functional zones (dashboard, approve, import...). `create` and `edit` are ACTIONS within `list` and detail pages, NEVER standalone sections. |
439
434
  | **Système externe** | Flag for integration specification. Add to `coverageMatrix` as integration. Load questionnaire materials on integrations if not already covered. |
440
435
  | **Liste simple en config** | Entity becomes a lookup/reference table (no dedicated module, no section). Note in coverageMatrix as config data. |
441
436
 
@@ -586,7 +581,7 @@ BEFORE transitioning to step-02:
586
581
  2. Re-read ALL answers collected during phases 2-4
587
582
  3. For EACH distinct feature/requirement identified:
588
583
  - Create an entry in `cadrage.coverageMatrix[]`
589
- - Assign it to mustHave, shouldHave, couldHave, or outOfScope
584
+ - Assign it to inScope or outOfScope
590
585
  - Assign the module that will cover it (or null if cross-cutting)
591
586
  - List anticipated sections (Level 4) — ONLY functional zones
592
587
  - List anticipated resources (Level 5) for each section
@@ -644,9 +639,7 @@ ba-writer.enrichSection({
644
639
  toBe: {from Phase 3, section 4a — Q1.8 answer},
645
640
  stakeholders: [{from Phase 3, section 4b}],
646
641
  globalScope: {
647
- mustHave: [{from Phase 3, section 4c + Phase 4 accepted suggestions}],
648
- shouldHave: [{from coverage matrix}],
649
- couldHave: [{from coverage matrix}],
642
+ inScope: [{from Phase 3, section 4c + Phase 4 accepted suggestions + coverage matrix}],
650
643
  outOfScope: [{from Phase 3, section 4c — Q2.15 exclusions}]
651
644
  },
652
645
  applicationRoles: [{from Phase 5, section 6}],
@@ -667,7 +660,7 @@ ba-writer.updateStatus({feature_id}, "framed")
667
660
  | Aspect | Count |
668
661
  |--------|-------|
669
662
  | Stakeholders | {count} |
670
- | Must-have scope items | {count} |
663
+ | In-scope items | {count} |
671
664
  | Application roles | {count} |
672
665
  | Suggestions accepted | {count} |
673
666
  | Anticipated sections | {total across all modules} |
@@ -60,7 +60,6 @@ For each module:
60
60
  - ApplicationCode (parent app)
61
61
  - Description
62
62
  - FeatureType (data-centric | workflow | reporting | integration | full-module)
63
- - Priority (must | should | could)
64
63
  - Entities (preliminary list)
65
64
  ```
66
65
 
@@ -137,14 +136,14 @@ Display the complete hierarchy for validation:
137
136
 
138
137
  ```
139
138
  [App: HumanResources]
140
- ├── [Module: Employees] (must)
139
+ ├── [Module: Employees]
141
140
  │ ├── list → SmartTable (employees-grid)
142
141
  │ └── detail → SmartForm (employee-form)
143
- ├── [Module: Absences] (must)
142
+ ├── [Module: Absences]
144
143
  │ ├── list → SmartTable (absences-grid)
145
144
  │ ├── detail → SmartForm (absence-form)
146
145
  │ └── calendar → SmartKanban (absences-calendar)
147
- └── [Module: Reports] (should)
146
+ └── [Module: Reports]
148
147
  └── dashboard → SmartDashboard (hr-dashboard)
149
148
  ```
150
149
 
@@ -170,7 +169,6 @@ Write via ba-writer:
170
169
  "applicationCode": "HumanResources",
171
170
  "name": "Employés",
172
171
  "featureType": "data-centric",
173
- "priority": "must",
174
172
  "entities": ["Employee", "Contract"],
175
173
  "anticipatedSections": [
176
174
  { "code": "list", "label": "Liste", "sectionType": "primary", "permissionMode": "crud", "resources": [{ "code": "employees-grid", "type": "SmartTable" }] },
@@ -0,0 +1,242 @@
1
+ ---
2
+ name: dev-start
3
+ description: Launch SmartStack dev environment (backend + frontend + admin credentials)
4
+ argument-hint: "[--stop] [--reset] [--backend-only] [--frontend-only]"
5
+ model: haiku
6
+ allowed-tools: Read, Bash, Glob, Write
7
+ ---
8
+
9
+ ## Arguments
10
+ $ARGUMENTS
11
+
12
+ ## Current state (auto-injected)
13
+
14
+ - Dev session: !`cat .claude/dev-session.json 2>/dev/null || echo "no session"`
15
+ - Backend port check: !`netstat.exe -ano 2>/dev/null | grep -E ":(5142|5290) .*LISTENING" | head -5 || echo "no backend listening"`
16
+ - Frontend port check: !`netstat.exe -ano 2>/dev/null | grep -E ":(6173|3000) .*LISTENING" | head -5 || echo "no frontend listening"`
17
+ - API directory: !`ls -d src/*.Api 2>/dev/null || echo "no API found"`
18
+ - Web directory: !`ls -d web/*-web 2>/dev/null || echo "no web found"`
19
+ - Local config: !`cat src/*.Api/appsettings.Local.json 2>/dev/null || echo "no local config"`
20
+ - Frontend env standalone: !`cat web/*-web/.env.standalone 2>/dev/null || echo "no .env.standalone"`
21
+ - Frontend env development: !`cat web/*-web/.env.development 2>/dev/null || echo "no .env.development"`
22
+
23
+ <objective>
24
+ Launch the SmartStack development environment in one command: detect configuration, validate config coherence, start backend + frontend, and provide admin credentials.
25
+ Idempotent: safe to run multiple times. Detects already-running processes.
26
+ </objective>
27
+
28
+ <quick_start>
29
+ ```bash
30
+ /dev-start # Launch everything (idempotent)
31
+ /dev-start --stop # Stop backend + frontend
32
+ /dev-start --reset # Force admin password reset
33
+ /dev-start --backend-only # Launch only the backend
34
+ /dev-start --frontend-only # Launch only the frontend
35
+ ```
36
+ </quick_start>
37
+
38
+ <subcommands>
39
+
40
+ | Command | Description |
41
+ |---------|-------------|
42
+ | `/dev-start` | Launch all (idempotent) + validate configs |
43
+ | `/dev-start --stop` | Stop backend + frontend via taskkill.exe |
44
+ | `/dev-start --reset` | Force reset admin password |
45
+ | `/dev-start --backend-only` | Launch only backend |
46
+ | `/dev-start --frontend-only` | Launch only frontend |
47
+
48
+ </subcommands>
49
+
50
+ <workflow>
51
+
52
+ ## Handle --stop
53
+
54
+ If `--stop` argument detected, execute the stop workflow and exit:
55
+
56
+ 1. Find backend PID:
57
+ ```bash
58
+ netstat.exe -ano | grep ":${API_PORT} .*LISTENING" | awk '{print $5}' | head -1
59
+ ```
60
+ 2. If found: `taskkill.exe /PID $PID /F`
61
+ 3. Find frontend PID:
62
+ ```bash
63
+ netstat.exe -ano | grep ":${WEB_PORT} .*LISTENING" | awk '{print $5}' | head -1
64
+ ```
65
+ 4. If found: `taskkill.exe /PID $PID /F`
66
+ 5. Display stopped status and exit.
67
+
68
+ If no processes found, display "Nothing running" and exit.
69
+
70
+ ## Step 1: Detect project
71
+
72
+ Find the API and Web directories:
73
+ - API: `src/*.Api` (first match)
74
+ - Web: `web/*-web` (first match)
75
+
76
+ If neither found, display error and exit.
77
+
78
+ ## Step 2: Detect environment and ports
79
+
80
+ Check if `appsettings.Local.json` exists in the API directory:
81
+
82
+ | Condition | Mode | Backend | Frontend | Backend command | Frontend command |
83
+ |-----------|------|---------|----------|-----------------|------------------|
84
+ | `appsettings.Local.json` exists | **Local** | Port from `Kestrel:Endpoints:Http:Url` | Port from `.env.standalone` or `package.json` local script | `dotnet run --launch-profile Local` | `npm run local` |
85
+ | No local config | **Development** | Port 5142 (launchSettings http profile) | Port 6173 (vite default) | `dotnet run --environment Development` | `npm run dev` |
86
+
87
+ **Reading ports:**
88
+ - **Local backend port**: Parse `appsettings.Local.json` -> `Kestrel.Endpoints.Http.Url` -> extract port from URL (e.g., `http://localhost:5290` -> `5290`)
89
+ - **Local frontend port**: Parse `.env.standalone` -> look for port in `VITE_API_URL`, or parse `package.json` -> `scripts.local` for `--port` flag
90
+ - **Dev backend port**: Default `5142`
91
+ - **Dev frontend port**: Default `6173`
92
+
93
+ ## Step 3: Validate config coherence
94
+
95
+ **CRITICAL: Verify that backend and frontend configs are consistent.**
96
+
97
+ ### Rules to check:
98
+
99
+ | Rule | Backend source | Frontend source | Validation |
100
+ |------|---------------|-----------------|------------|
101
+ | Frontend calls correct backend | Port API (Kestrel or launchSettings) | `VITE_API_URL` in `.env.standalone` (Local) or `.env.development` (Dev) | Port in VITE_API_URL must match API port |
102
+ | Backend allows frontend (CORS) | `SmartStack:CorsOrigins[]` in appsettings | Frontend port | CorsOrigins must contain `http://localhost:{WEB_PORT}` |
103
+ | OAuth redirects to correct frontend | `Authentication:FrontendUrl` in appsettings | Frontend port | FrontendUrl must point to `http://localhost:{WEB_PORT}` |
104
+ | Local files exist in pairs | `appsettings.Local.json` | `.env.standalone` | If one exists, the other must too |
105
+
106
+ ### In Local mode:
107
+
108
+ 1. Read `appsettings.Local.json` -> extract `Kestrel:Endpoints:Http:Url` (API port)
109
+ 2. Read `appsettings.Local.json` -> extract `Authentication:FrontendUrl` (expected frontend URL)
110
+ 3. Read `.env.standalone` -> extract `VITE_API_URL` (expected API URL by frontend)
111
+ 4. Verify `VITE_API_URL` port matches Kestrel port
112
+ 5. Verify `Authentication:FrontendUrl` port matches frontend port
113
+ 6. If `appsettings.Local.json` exists but NOT `.env.standalone` -> **create `.env.standalone`** with correct values (ask user first)
114
+ 7. If `.env.standalone` exists but NOT `appsettings.Local.json` -> **warn** (config incomplete)
115
+
116
+ ### In Development mode:
117
+
118
+ 1. Verify `.env.development` contains `VITE_API_URL=http://localhost:5142` (NOT 5290 which is Local profile)
119
+ 2. Verify `appsettings.json` -> `SmartStack:CorsOrigins` contains `http://localhost:6173`
120
+ 3. Verify `Authentication:FrontendUrl` = `http://localhost:6173`
121
+
122
+ ### If incoherence detected:
123
+
124
+ 1. Display a clear WARNING with detected values vs expected values
125
+ 2. Propose to auto-correct (write the correct files)
126
+ 3. If user refuses, continue with a warning
127
+
128
+ ### Output format for coherence:
129
+
130
+ ```
131
+ Config Coherence Check:
132
+ [OK] VITE_API_URL matches backend port (5290)
133
+ [OK] CORS allows frontend (http://localhost:3000)
134
+ [OK] OAuth redirect matches frontend
135
+ [OK] Local config files paired
136
+
137
+ -- OR --
138
+
139
+ [WARN] VITE_API_URL=http://localhost:5290 but backend runs on port 5142
140
+ Expected: VITE_API_URL=http://localhost:5142
141
+ [WARN] .env.standalone missing (appsettings.Local.json exists)
142
+ -> Creating .env.standalone with correct values...
143
+ ```
144
+
145
+ ## Step 4: Check if already running
146
+
147
+ ```bash
148
+ netstat.exe -ano | grep ":${API_PORT} .*LISTENING"
149
+ netstat.exe -ano | grep ":${WEB_PORT} .*LISTENING"
150
+ ```
151
+
152
+ If `--backend-only`: skip frontend checks.
153
+ If `--frontend-only`: skip backend checks.
154
+
155
+ ## Step 5: Launch backend
156
+
157
+ If NOT already running and NOT `--frontend-only`:
158
+
159
+ ```bash
160
+ # cd into the API directory first
161
+ cd {API_DIR} && dotnet run {LAUNCH_ARGS}
162
+ ```
163
+
164
+ Use `Bash(run_in_background=true)` so it runs in background.
165
+
166
+ ## Step 6: Launch frontend
167
+
168
+ If NOT already running and NOT `--backend-only`:
169
+
170
+ ```bash
171
+ # cd into the Web directory first
172
+ cd {WEB_DIR} && npm run {SCRIPT}
173
+ ```
174
+
175
+ Use `Bash(run_in_background=true)` so it runs in background.
176
+
177
+ ## Step 7: Handle admin password
178
+
179
+ 1. If `.claude/dev-session.json` exists and contains a password and `--reset` NOT specified -> reuse stored credentials
180
+ 2. If `--reset` OR no stored password:
181
+ a. If backend was just launched, wait for it to be UP first:
182
+ ```bash
183
+ # Poll up to 30 seconds (10 attempts x 3s)
184
+ for i in $(seq 1 10); do
185
+ netstat.exe -ano | grep ":${API_PORT} .*LISTENING" && break
186
+ sleep 3
187
+ done
188
+ ```
189
+ b. Run: `smartstack admin reset --force --json`
190
+ c. Parse JSON output to extract email and password
191
+ d. Write `.claude/dev-session.json`:
192
+ ```json
193
+ {
194
+ "admin_password": "...",
195
+ "admin_email": "local.admin@smartstack.local",
196
+ "last_reset": "2026-03-10T14:30:00Z",
197
+ "api_port": 5142,
198
+ "web_port": 6173,
199
+ "environment": "Development"
200
+ }
201
+ ```
202
+ e. Verify `.claude/dev-session.json` is in `.gitignore`. If not, warn the user (contains credentials).
203
+
204
+ ## Step 8: Display connection info
205
+
206
+ ```
207
+ ================================================================
208
+ SMARTSTACK DEV ENVIRONMENT
209
+ ================================================================
210
+
211
+ Backend: http://localhost:{API_PORT} [RUNNING|STARTING]
212
+ Frontend: http://localhost:{WEB_PORT} [RUNNING|STARTING]
213
+ Swagger: http://localhost:{API_PORT}/scalar
214
+
215
+ Email: local.admin@smartstack.local
216
+ Password: xxxxxxxxxxxxxx
217
+
218
+ Environment: {Development|Local}
219
+ Config: OK | WARNING (details)
220
+ ================================================================
221
+ ```
222
+
223
+ </workflow>
224
+
225
+ <important_notes>
226
+
227
+ 1. **Backend must be UP before admin reset** - Poll the port (2-3s intervals, max 30s) before attempting password reset
228
+ 2. **Cross-worktree support** - Detect the correct API directory regardless of which worktree we're in
229
+ 3. **No MCP required** - This skill only uses bash commands and the `smartstack` CLI
230
+ 4. **Config coherence is critical** - Mismatched ports between backend/frontend is the #1 cause of "it doesn't work" issues
231
+ 5. **Idempotent** - Running twice should detect already-running processes and reuse stored credentials
232
+ 6. **dev-session.json contains secrets** - Must be in `.gitignore`
233
+
234
+ </important_notes>
235
+
236
+ <success_criteria>
237
+ - Backend and frontend detected and launched (or already running)
238
+ - Config coherence validated (or warnings displayed)
239
+ - Admin credentials available (reused or freshly reset)
240
+ - Connection info displayed in clear format
241
+ - No errors on repeated invocations (idempotent)
242
+ </success_criteria>
@@ -156,7 +156,7 @@ export function EntityListPage() {
156
156
  setLoading(true);
157
157
  setError(null);
158
158
  const result = await entityApi.getAll();
159
- setData(result.items);
159
+ setData(result?.items ?? []);
160
160
  } catch (err: any) {
161
161
  setError(err.message || t('{module}:errors.loadFailed', 'Failed to load data'));
162
162
  } finally {
@@ -49,7 +49,7 @@ export function UsersPage() {
49
49
  setLoading(true);
50
50
  setError(null);
51
51
  const result = await usersApi.getAll();
52
- setUsers(result.items);
52
+ setUsers(result?.items ?? []);
53
53
  } catch (err: any) {
54
54
  setError(err.message || t('users:errors.loadFailed', 'Failed to load users'));
55
55
  } finally {