@atlashub/smartstack-cli 3.21.0 → 3.22.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/dist/index.js +17 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +68 -3
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/skills/application/references/application-roles-template.md +2 -2
- package/templates/skills/application/steps/step-05-frontend.md +40 -35
- package/templates/skills/application/templates-frontend.md +64 -36
- package/templates/skills/business-analyse/html/ba-interactive.html +80 -6
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +38 -6
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
- package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
- package/templates/skills/business-analyse/references/deploy-data-build.md +5 -3
- package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
- package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
- package/templates/skills/business-analyse/references/validate-incremental-html.md +26 -4
- package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
- package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
- package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
- package/templates/skills/business-analyse/steps/step-03c-compile.md +114 -0
- package/templates/skills/business-analyse/steps/step-03d-validate.md +144 -22
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +114 -2
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +28 -0
- package/templates/skills/ralph-loop/references/category-rules.md +5 -2
- package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
- package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
- package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
- package/templates/skills/ralph-loop/steps/step-02-execute.md +81 -0
|
@@ -46,8 +46,8 @@ Validate the module specification for completeness and consistency, write to fea
|
|
|
46
46
|
| entities | 1 | `analysis.entities.length >= 1` | PASS/FAIL |
|
|
47
47
|
| entitySchemaFormat | attributes[] not fields[] (BLOCKING) | `analysis.entities.every(e => e.attributes?.length > 0)` | PASS/FAIL |
|
|
48
48
|
| entityAttributeTypes | ALL attributes have `type` field (BLOCKING) | `analysis.entities.every(e => e.attributes.every(a => a.type))` | PASS/FAIL |
|
|
49
|
-
| wireframes | 1 per section (BLOCKING) | `specification.wireframes.length >= specification.sections.length` (count REAL elements) | PASS/FAIL |
|
|
50
|
-
| wireframeSchema | All required fields present (BLOCKING) | `specification.wireframes.every(w => w.screen && w.section && (w.mockup \|\| w.content))` | PASS/FAIL |
|
|
49
|
+
| wireframes | 1 per section (BLOCKING) | `(specification.uiWireframes \|\| specification.wireframes \|\| []).length >= (specification.sections \|\| []).length` (count REAL elements, check BOTH key names) | PASS/FAIL |
|
|
50
|
+
| wireframeSchema | All required fields present (BLOCKING) | `(specification.uiWireframes \|\| specification.wireframes \|\| []).every(w => (w.screen \|\| w.title) && w.section && (w.mockup \|\| w.ascii \|\| w.content))` | PASS/FAIL |
|
|
51
51
|
| sections | 1 (BLOCKING) | `specification.sections.length >= 1` (EVERY module needs at least 1 section) | PASS/FAIL |
|
|
52
52
|
| gherkinScenarios | 1 array entry | `Array.isArray(specification.gherkinScenarios) && specification.gherkinScenarios.length >= 1` | PASS/FAIL |
|
|
53
53
|
| gherkinFormat | Array not object (BLOCKING) | `Array.isArray(specification.gherkinScenarios)` (NOT a single object) | PASS/FAIL |
|
|
@@ -97,9 +97,74 @@ IF failures.length > 0:
|
|
|
97
97
|
- Entity names PascalCase
|
|
98
98
|
- Field names camelCase
|
|
99
99
|
- Entity attribute format: `attributes[]` with {name, description}, NOT `fields[]` with {name, type} — entities must NOT have tableName or primaryKey
|
|
100
|
-
- Wireframe structure: `screen` (not `name`), `componentMapping` is array of {wireframeElement, reactComponent} (not plain key-value object), `layout` is object with regions (not string)
|
|
100
|
+
- Wireframe structure: `screen` (not `name`/`title`), `mockup` (not `ascii`/`content`), `componentMapping` is array of {wireframeElement, reactComponent} (not plain key-value object), `layout` is object with regions (not string)
|
|
101
|
+
- Wireframe field naming: uses `screen` (not `title`), `mockup` (not `ascii`) — auto-fix if wrong (see 9c-fix below)
|
|
101
102
|
- Permission paths dot-separated lowercase
|
|
102
103
|
|
|
104
|
+
#### 9c-fix. Auto-Fix Wireframe Field Names (MANDATORY before writing)
|
|
105
|
+
|
|
106
|
+
> **CRITICAL:** The agent may use non-canonical field names (`title`, `ascii`, `name`, `content`).
|
|
107
|
+
> These MUST be normalized to canonical names before writing to feature.json.
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// AUTO-FIX: Normalize wireframe field names before writing
|
|
111
|
+
const wireframes = specification.uiWireframes || specification.wireframes || [];
|
|
112
|
+
for (const wf of wireframes) {
|
|
113
|
+
if (wf.title && !wf.screen) { wf.screen = wf.title; delete wf.title; }
|
|
114
|
+
if (wf.ascii && !wf.mockup) { wf.mockup = wf.ascii; delete wf.ascii; }
|
|
115
|
+
if (wf.content && !wf.mockup) { wf.mockup = wf.content; delete wf.content; }
|
|
116
|
+
if (wf.name && !wf.screen) { wf.screen = wf.name; delete wf.name; }
|
|
117
|
+
}
|
|
118
|
+
// Store normalized wireframes back under canonical key
|
|
119
|
+
specification.uiWireframes = wireframes;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### 9c-fix-entities. Auto-Fix Entity Attribute Types (MANDATORY before writing)
|
|
123
|
+
|
|
124
|
+
> **DEFENSE-IN-DEPTH:** step-03c POST-CHECK should catch attributes without `type`,
|
|
125
|
+
> but if any slip through to step-03d, auto-fix them here before writing to feature.json.
|
|
126
|
+
> Uses the same type inference algorithm as step-03c POST-CHECK.
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
// AUTO-FIX: Ensure all entity attributes have structured type
|
|
130
|
+
const entities = analysis.entities || [];
|
|
131
|
+
let entityAutoFixCount = 0;
|
|
132
|
+
for (const entity of entities) {
|
|
133
|
+
for (const attr of entity.attributes || []) {
|
|
134
|
+
if (!attr.type) {
|
|
135
|
+
// Infer type from attribute name pattern
|
|
136
|
+
if (attr.validation?.match(/max\s*\d+/i) || attr.name.match(/name|title|code|description|label|email|phone|address/i)) {
|
|
137
|
+
attr.type = "string";
|
|
138
|
+
const maxMatch = attr.validation?.match(/max\s*(\d+)/i);
|
|
139
|
+
if (maxMatch) attr.maxLength = parseInt(maxMatch[1]);
|
|
140
|
+
} else if (attr.name.match(/id$/i)) {
|
|
141
|
+
attr.type = "Guid";
|
|
142
|
+
} else if (attr.name.match(/date|At$/i)) {
|
|
143
|
+
attr.type = "DateTime";
|
|
144
|
+
} else if (attr.name.match(/is[A-Z]|has[A-Z]|active|enabled/)) {
|
|
145
|
+
attr.type = "bool";
|
|
146
|
+
} else if (attr.name.match(/amount|salary|rate|price|total/i)) {
|
|
147
|
+
attr.type = "decimal";
|
|
148
|
+
} else if (attr.name.match(/count|number|order|sort|index/i)) {
|
|
149
|
+
attr.type = "int";
|
|
150
|
+
} else {
|
|
151
|
+
attr.type = "string"; // safe default
|
|
152
|
+
}
|
|
153
|
+
// Normalize free-text maxLength
|
|
154
|
+
if (typeof attr.validation === 'string' && !attr.maxLength) {
|
|
155
|
+
const m = attr.validation.match(/max\s*(\d+)/i);
|
|
156
|
+
if (m) attr.maxLength = parseInt(m[1]);
|
|
157
|
+
}
|
|
158
|
+
if (attr.required === undefined) attr.required = true;
|
|
159
|
+
entityAutoFixCount++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (entityAutoFixCount > 0) {
|
|
164
|
+
console.warn(`DEFENSE-IN-DEPTH: auto-fixed ${entityAutoFixCount} attributes without type in step-03d`);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
103
168
|
#### 9d. Decision
|
|
104
169
|
|
|
105
170
|
IF validation PASS:
|
|
@@ -165,10 +230,10 @@ ba-writer.enrichSection({
|
|
|
165
230
|
|
|
166
231
|
**Execute the comprehensive validation checklist:**
|
|
167
232
|
|
|
168
|
-
Run the
|
|
233
|
+
Run the 29-check validation process across 10 categories:
|
|
169
234
|
- Data Model (4 checks) | Business Rules (3 checks) | Use Cases & FRs (4 checks)
|
|
170
235
|
- Permissions (3 checks) | UI & Navigation (4 checks) | I18N & Messages (3 checks)
|
|
171
|
-
- Seed Data (2 checks) | API Endpoints (2 checks) | Validations (1 check) | Gherkin (
|
|
236
|
+
- Seed Data (2 checks) | API Endpoints (2 checks) | Validations (1 check) | Gherkin (3 checks)
|
|
172
237
|
|
|
173
238
|
```javascript
|
|
174
239
|
const blockingFailures = checks.filter(c => c.blocking && c.status === "FAIL");
|
|
@@ -243,18 +308,13 @@ Display comprehensive summary:
|
|
|
243
308
|
|
|
244
309
|
→ Validation: {PASS/FAIL}
|
|
245
310
|
═══════════════════════════════════════════════════════════
|
|
246
|
-
```
|
|
247
311
|
|
|
248
|
-
|
|
312
|
+
{IF validation PASS}:
|
|
313
|
+
→ Module validé automatiquement
|
|
314
|
+
→ Passage automatique au module suivant (section 12)
|
|
249
315
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
header: "Module"
|
|
253
|
-
options:
|
|
254
|
-
- label: "Validé"
|
|
255
|
-
description: "Passer au module suivant"
|
|
256
|
-
- label: "Réviser"
|
|
257
|
-
description: "Modifier des éléments du module"
|
|
316
|
+
{IF validation FAIL}:
|
|
317
|
+
→ Afficher les erreurs et offrir les options de correction (section 9d)
|
|
258
318
|
```
|
|
259
319
|
|
|
260
320
|
---
|
|
@@ -319,7 +379,7 @@ const written = ba-reader.read({module_feature_id});
|
|
|
319
379
|
const checks = [
|
|
320
380
|
{ key: "specification.actors", actual: written.specification?.actors?.length, min: 2 },
|
|
321
381
|
{ key: "specification.useCases", actual: written.specification?.useCases?.length, min: 2 },
|
|
322
|
-
{ key: "specification.wireframes", actual: written.specification?.wireframes?.length,
|
|
382
|
+
{ key: "specification.wireframes", actual: (written.specification?.uiWireframes?.length || written.specification?.wireframes?.length || 0), min: 1 },
|
|
323
383
|
{ key: "specification.sections", actual: written.specification?.sections?.length, min: 1 },
|
|
324
384
|
{ key: "specification.seedDataCore", actual: Object.keys(written.specification?.seedDataCore || {}).length, min: 7 },
|
|
325
385
|
{ key: "specification.lifeCycles", actual: written.specification?.lifeCycles?.length, min: 0 },
|
|
@@ -334,16 +394,78 @@ IF failures.length > 0:
|
|
|
334
394
|
→ Re-execute section 11 write with ALL specification data
|
|
335
395
|
→ DO NOT proceed to next module until ALL checks pass
|
|
336
396
|
|
|
337
|
-
// SPECIAL CHECK: wireframes content verification
|
|
338
|
-
|
|
339
|
-
|
|
397
|
+
// SPECIAL CHECK: wireframes content verification (check BOTH key names)
|
|
398
|
+
const wireframes = written.specification?.uiWireframes || written.specification?.wireframes || [];
|
|
399
|
+
IF wireframes.length > 0:
|
|
400
|
+
const emptyMockups = wireframes.filter(wf => !wf.mockup && !wf.ascii && !wf.content);
|
|
340
401
|
IF emptyMockups.length > 0:
|
|
341
402
|
WARNING: "{emptyMockups.length} wireframes have empty mockup content — verify step-03b data"
|
|
403
|
+
|
|
404
|
+
// SPECIAL CHECK: wireframes >= sections
|
|
405
|
+
const sectionCount = written.specification?.sections?.length || 0;
|
|
406
|
+
IF wireframes.length < sectionCount:
|
|
407
|
+
BLOCKING ERROR: "{wireframes.length} wireframes < {sectionCount} sections — wireframes MISSING for some sections"
|
|
408
|
+
→ Re-read wireframes from conversation context and re-write to feature.json
|
|
342
409
|
```
|
|
343
410
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
>
|
|
411
|
+
#### 11-POST-CHECK-BASH: Acceptance Criteria Verification (BLOCKING)
|
|
412
|
+
|
|
413
|
+
> **CRITICAL:** The pseudocode checks above are interpreted by the model — they can be "passed" incorrectly.
|
|
414
|
+
> This bash check reads the REAL file on disk and provides an objective verification.
|
|
415
|
+
> See [references/acceptance-criteria.md](../references/acceptance-criteria.md) for the full acceptance criteria definition.
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
MODULE_JSON="{module_feature_json_path}"
|
|
419
|
+
node -e "
|
|
420
|
+
const fs = require('fs');
|
|
421
|
+
const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8'));
|
|
422
|
+
const spec = data.specification || {};
|
|
423
|
+
const analysis = data.analysis || {};
|
|
424
|
+
const wf = spec.uiWireframes || spec.wireframes || [];
|
|
425
|
+
const sections = spec.sections || [];
|
|
426
|
+
const checks = [
|
|
427
|
+
['entities >= 1', (analysis.entities||[]).length, 1],
|
|
428
|
+
['useCases >= 2', (spec.useCases||[]).length, 2],
|
|
429
|
+
['FRs >= 4', (spec.functionalRequirements||[]).length,4],
|
|
430
|
+
['wireframes >= 1', wf.length, 1],
|
|
431
|
+
['wireframes >= sections', wf.length, sections.length],
|
|
432
|
+
['sections >= 1', sections.length, 1],
|
|
433
|
+
['seedDataCore 7 arrays', Object.keys(spec.seedDataCore||{}).filter(k=>(spec.seedDataCore||{})[k]&&(spec.seedDataCore||{})[k].length>0).length, 7],
|
|
434
|
+
['gherkin is array', Array.isArray(spec.gherkinScenarios)?1:0, 1],
|
|
435
|
+
['apiEndpoints >= 1', (spec.apiEndpoints||[]).length, 1],
|
|
436
|
+
['messages >= 4', (spec.messages||[]).length, 4],
|
|
437
|
+
['validations >= 1', (spec.validations||[]).length, 1]
|
|
438
|
+
];
|
|
439
|
+
const fails = checks.filter(c => c[1] < c[2]);
|
|
440
|
+
fails.forEach(f => console.error('FAIL: ' + f[0] + ' = ' + f[1] + ' (min: ' + f[2] + ')'));
|
|
441
|
+
// Check wireframe content
|
|
442
|
+
const emptyWf = wf.filter(w => !w.mockup && !w.ascii && !w.content);
|
|
443
|
+
if (emptyWf.length > 0) { fails.push(['wireframe content', 0, 1]); console.error('FAIL: ' + emptyWf.length + ' wireframes have EMPTY content'); }
|
|
444
|
+
// Check entity attribute types
|
|
445
|
+
const badAttrs = (analysis.entities||[]).flatMap(e => (e.attributes||[]).filter(a => !a.type).map(a => e.name+'.'+a.name));
|
|
446
|
+
if (badAttrs.length > 0) { fails.push(['attr.type', 0, 1]); console.error('FAIL: attributes without type: ' + badAttrs.join(', ')); }
|
|
447
|
+
// AC-15: Validation rules format
|
|
448
|
+
const badRules = (spec.validations||[]).filter(v => v.rules && !Array.isArray(v.rules));
|
|
449
|
+
if (badRules.length > 0) { fails.push(['AC-15: rules not array', badRules.length, 0]); console.error('FAIL: AC-15: ' + badRules.length + ' validations have rules as string'); }
|
|
450
|
+
// AC-16: Messages must have message field
|
|
451
|
+
const noMsg = (spec.messages||[]).filter(m => !m.message);
|
|
452
|
+
if (noMsg.length > 0) { fails.push(['AC-16: message missing', noMsg.length, 0]); console.error('FAIL: AC-16: ' + noMsg.length + ' messages missing message field'); }
|
|
453
|
+
// AC-17: Gherkin content structure
|
|
454
|
+
if (Array.isArray(spec.gherkinScenarios)) { const badG = spec.gherkinScenarios.filter(g => !g.feature || !Array.isArray(g.scenarios)); if (badG.length > 0) { fails.push(['AC-17: gherkin content', badG.length, 0]); console.error('FAIL: AC-17: ' + badG.length + ' gherkin entries invalid'); } }
|
|
455
|
+
if (fails.length > 0) { console.error('BLOCKING: ' + fails.length + ' acceptance criteria failed'); process.exit(1); }
|
|
456
|
+
console.log('PASS: All acceptance criteria met');
|
|
457
|
+
" "$MODULE_JSON"
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
IF this check FAILS:
|
|
461
|
+
- Identify which criteria failed from the output
|
|
462
|
+
- Fix the corresponding data (re-read from conversation context, re-generate, or re-run the step that produces it)
|
|
463
|
+
- Re-write to feature.json
|
|
464
|
+
- Re-run the POST-CHECK until PASS
|
|
465
|
+
|
|
466
|
+
> **WHY:** Step-03b now writes wireframes intermediately (since fix), but step-03d section 11 does the FULL write.
|
|
467
|
+
> This bash POST-CHECK is the ultimate safety net — it reads the REAL file and verifies ALL acceptance criteria.
|
|
468
|
+
> The pseudocode checks (section 9a) catch issues early; this bash check catches anything the model missed.
|
|
347
469
|
|
|
348
470
|
---
|
|
349
471
|
|
|
@@ -200,6 +200,55 @@ See [references/handoff-file-templates.md](../references/handoff-file-templates.
|
|
|
200
200
|
| **4.6 SeedData** | `specification.seedDataCore` | 5 CORE + business + IClientSeedDataProvider |
|
|
201
201
|
| **4.7 Tests** | All layers | Unit, Integration, Security tests |
|
|
202
202
|
|
|
203
|
+
#### Route Convention (CRITICAL)
|
|
204
|
+
|
|
205
|
+
> **MANDATORY:** All NavigationModule/Section routes MUST use kebab-case transformation.
|
|
206
|
+
> See [references/naming-conventions.md](../references/naming-conventions.md) for complete guide.
|
|
207
|
+
|
|
208
|
+
**Problem:**
|
|
209
|
+
- Module codes in feature.json are **PascalCase** (e.g., `"HumanResources"`)
|
|
210
|
+
- Routes in seed data MUST be **kebab-case** (e.g., `"/business/human-resources"`)
|
|
211
|
+
- Failing to transform causes 404 when clicking menu items
|
|
212
|
+
|
|
213
|
+
**Solution:**
|
|
214
|
+
|
|
215
|
+
When generating `seedDataCore` for handoff, transform PascalCase codes to kebab-case routes:
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
// Transform module codes to kebab-case routes
|
|
219
|
+
function toKebabCase(code) {
|
|
220
|
+
return code
|
|
221
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
222
|
+
.toLowerCase();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Example usage in seedDataCore generation
|
|
226
|
+
const moduleCode = "TimeManagement"; // PascalCase (C#)
|
|
227
|
+
const appCode = "HumanResources"; // PascalCase (C#)
|
|
228
|
+
const contextCode = "business"; // lowercase
|
|
229
|
+
|
|
230
|
+
const route = `/${contextCode}/${toKebabCase(appCode)}/${toKebabCase(moduleCode)}`;
|
|
231
|
+
// Result: "/business/human-resources/time-management"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Include in task instructions:**
|
|
235
|
+
|
|
236
|
+
For NavigationSeedData tasks in `handoff.filesToCreate`, add:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"path": "Infrastructure/Persistence/Seeding/Data/{Module}/NavigationSeedData.cs",
|
|
241
|
+
"type": "SeedData",
|
|
242
|
+
"category": "core",
|
|
243
|
+
"instructions": "Generate NavigationModule seed data. CRITICAL: Use ToKebabCase() helper for Route property. Example: Route = ToKebabCase($\"/{context}/{app}/{module}\"). See core-seed-data.md template."
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Validation:**
|
|
248
|
+
- ralph-loop POST-CHECK validates routes after generation (see step-02-execute.md)
|
|
249
|
+
- MCP `validate_frontend_routes` detects case mismatches
|
|
250
|
+
- Convention documented in naming-conventions.md
|
|
251
|
+
|
|
203
252
|
**Critical rules:**
|
|
204
253
|
- All backend paths include `{ContextPascal}/{ApplicationName}/` hierarchy
|
|
205
254
|
- Frontend pages MUST have `linkedWireframes[]` + `wireframeAcceptanceCriteria`
|
|
@@ -249,7 +298,7 @@ Additional per-section pages (dashboard, import, etc.) are separate entries.
|
|
|
249
298
|
|
|
250
299
|
**Route wiring task (MANDATORY — separate entry):**
|
|
251
300
|
```json
|
|
252
|
-
{ "path": "src/App.tsx", "type": "RouteWiring", "module": "{moduleCode}", "description": "Wire {module} routes into App.tsx
|
|
301
|
+
{ "path": "src/App.tsx", "type": "RouteWiring", "module": "{moduleCode}", "description": "Wire {module} routes into App.tsx (detect pattern: contextRoutes array OR JSX Route wrappers). BLOCKING: pages without route wiring = blank pages." }
|
|
253
302
|
```
|
|
254
303
|
|
|
255
304
|
### 6c. Cross-Module PRD (MANDATORY for multi-module features)
|
|
@@ -359,7 +408,43 @@ Add to progress.txt after all module tasks.
|
|
|
359
408
|
```
|
|
360
409
|
// Derive seedDataCore from modules[], applicationRoles[], and coverageMatrix[]
|
|
361
410
|
|
|
411
|
+
// Helper: PascalCase → "Human Readable" (e.g., "HumanResources" → "Human Resources")
|
|
412
|
+
function toHumanReadable(pascalCase) {
|
|
413
|
+
return pascalCase
|
|
414
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
415
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const contextCode = master.metadata.context || "business";
|
|
419
|
+
const appCode = master.metadata.application;
|
|
420
|
+
const appLabel = toHumanReadable(appCode);
|
|
421
|
+
const appDesc = master.cadrage?.problem?.split('.')[0] || `Gestion ${appLabel}`;
|
|
422
|
+
const lang = master.metadata.language || "fr";
|
|
423
|
+
|
|
362
424
|
const seedDataCore = {
|
|
425
|
+
// Application-level navigation (MANDATORY — one entry per application)
|
|
426
|
+
navigationApplications: [{
|
|
427
|
+
code: appCode.toLowerCase(),
|
|
428
|
+
name: appCode,
|
|
429
|
+
labels: {
|
|
430
|
+
fr: lang === "fr" ? (master.cadrage?.applicationName || appLabel) : appLabel,
|
|
431
|
+
en: lang === "en" ? (master.cadrage?.applicationName || appLabel) : appLabel,
|
|
432
|
+
it: appLabel,
|
|
433
|
+
de: appLabel
|
|
434
|
+
},
|
|
435
|
+
description: {
|
|
436
|
+
fr: lang === "fr" ? appDesc : `Gestion ${appLabel}`,
|
|
437
|
+
en: lang === "en" ? appDesc : `${appLabel} management`,
|
|
438
|
+
it: `Gestione ${appLabel}`,
|
|
439
|
+
de: `${appLabel} Verwaltung`
|
|
440
|
+
},
|
|
441
|
+
icon: inferIconFromApplication(master) || "layout-grid",
|
|
442
|
+
iconType: "lucide",
|
|
443
|
+
context: contextCode,
|
|
444
|
+
route: `/${contextCode}/${toKebabCase(appCode)}`,
|
|
445
|
+
displayOrder: 1
|
|
446
|
+
}],
|
|
447
|
+
|
|
363
448
|
navigationModules: master.modules.map((m, i) => ({
|
|
364
449
|
code: m.code,
|
|
365
450
|
label: m.name || m.code,
|
|
@@ -433,6 +518,27 @@ const seedDataCore = {
|
|
|
433
518
|
)
|
|
434
519
|
};
|
|
435
520
|
|
|
521
|
+
// Icon inference for APPLICATION level (NEVER leave as null)
|
|
522
|
+
function inferIconFromApplication(master) {
|
|
523
|
+
const appLC = (master.metadata.application || '').toLowerCase();
|
|
524
|
+
const iconMap = {
|
|
525
|
+
'humanresource': 'users', 'rh': 'users', 'hr': 'users', 'personnel': 'users',
|
|
526
|
+
'sales': 'shopping-cart', 'vente': 'shopping-cart', 'commerce': 'shopping-cart',
|
|
527
|
+
'finance': 'wallet', 'comptabilite': 'calculator', 'accounting': 'calculator',
|
|
528
|
+
'inventory': 'warehouse', 'stock': 'package', 'logistic': 'truck',
|
|
529
|
+
'crm': 'building-2', 'customer': 'building', 'client': 'building-2',
|
|
530
|
+
'project': 'folder-kanban', 'task': 'check-square', 'gestion': 'layout-grid',
|
|
531
|
+
'admin': 'shield', 'support': 'headphones', 'maintenance': 'wrench',
|
|
532
|
+
'production': 'factory', 'quality': 'badge-check', 'document': 'file-text',
|
|
533
|
+
'fleet': 'car', 'vehicle': 'car', 'transport': 'truck',
|
|
534
|
+
'education': 'graduation-cap', 'formation': 'graduation-cap', 'training': 'graduation-cap'
|
|
535
|
+
};
|
|
536
|
+
for (const [keyword, icon] of Object.entries(iconMap)) {
|
|
537
|
+
if (appLC.includes(keyword)) return icon;
|
|
538
|
+
}
|
|
539
|
+
return "layout-grid"; // fallback — NEVER null
|
|
540
|
+
}
|
|
541
|
+
|
|
436
542
|
// Icon inference from module context (NEVER leave as null)
|
|
437
543
|
function inferIconFromModule(module) {
|
|
438
544
|
const codeLC = module.code.toLowerCase();
|
|
@@ -462,8 +568,14 @@ ba-writer.enrichSection({
|
|
|
462
568
|
})
|
|
463
569
|
```
|
|
464
570
|
|
|
465
|
-
**POST-CHECK (BLOCKING for icons, warning for counts):**
|
|
571
|
+
**POST-CHECK (BLOCKING for icons/application, warning for counts):**
|
|
466
572
|
```
|
|
573
|
+
IF !seedDataCore.navigationApplications || seedDataCore.navigationApplications.length === 0:
|
|
574
|
+
BLOCKING ERROR: "navigationApplications is empty — Application navigation will NOT be seeded. Menu will show Context → (nothing) → Modules."
|
|
575
|
+
IF seedDataCore.navigationApplications[0].icon === null || seedDataCore.navigationApplications[0].icon === undefined:
|
|
576
|
+
BLOCKING ERROR: "Application icon is null — ralph-loop will render empty navigation"
|
|
577
|
+
IF !seedDataCore.navigationApplications[0].labels?.fr || !seedDataCore.navigationApplications[0].labels?.en:
|
|
578
|
+
BLOCKING ERROR: "Application labels missing fr/en — translations will fail"
|
|
467
579
|
IF seedDataCore.navigationModules.length !== master.modules.length:
|
|
468
580
|
WARNING: seedDataCore has ${seedDataCore.navigationModules.length} nav modules but ${master.modules.length} modules exist
|
|
469
581
|
IF seedDataCore.permissions.length === 0:
|
|
@@ -238,6 +238,34 @@ See [references/deploy-data-build.md](../references/deploy-data-build.md) for:
|
|
|
238
238
|
|
|
239
239
|
---
|
|
240
240
|
|
|
241
|
+
### 7-bis. Generate SVG Wireframes (Parallel Enrichment)
|
|
242
|
+
|
|
243
|
+
> **After EMBEDDED_ARTIFACTS is built (sections 5-7) but BEFORE writing the HTML (section 8).**
|
|
244
|
+
> This step enriches each ASCII wireframe with a professional SVG version for dual-view rendering.
|
|
245
|
+
|
|
246
|
+
See [references/wireframe-svg-style-guide.md](../references/wireframe-svg-style-guide.md) for the complete SVG style specification, prompt template, and orchestration process.
|
|
247
|
+
|
|
248
|
+
**Process summary:**
|
|
249
|
+
|
|
250
|
+
1. **Read** `references/wireframe-svg-style-guide.md` to get the prompt template
|
|
251
|
+
2. **Collect** all wireframes across ALL modules in `EMBEDDED_ARTIFACTS.wireframes` where `content` exists and `svgContent` is null
|
|
252
|
+
3. **Spawn parallel Task(sonnet) agents** — ONE per wireframe, ALL in a single message
|
|
253
|
+
4. **Collect and validate** results: strip markdown fences if present, verify SVG starts with `<svg` and contains `</svg>`
|
|
254
|
+
5. **Inject** valid SVGs into `EMBEDDED_ARTIFACTS.wireframes[moduleCode][index].svgContent`
|
|
255
|
+
6. **Display summary:**
|
|
256
|
+
```
|
|
257
|
+
SVG wireframe generation:
|
|
258
|
+
Total wireframes: {total}
|
|
259
|
+
SVG generated: {successCount}/{total}
|
|
260
|
+
Fallback (ASCII only): {failCount}/{total}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
> **CRITICAL:** This step is NEVER blocking. If all SVG generations fail, deployment
|
|
264
|
+
> continues with ASCII-only wireframes. The HTML renderer checks for `svgContent` before
|
|
265
|
+
> showing the SVG view. SVG is an enhancement, not a requirement.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
241
269
|
### 8. Write and Verify
|
|
242
270
|
|
|
243
271
|
1. Write the populated HTML to the output directory: `docs/business/{app}/business-analyse/v{version}/ba-interactive.html`
|
|
@@ -304,7 +304,9 @@ fi
|
|
|
304
304
|
**Execution sequence (IN ORDER):**
|
|
305
305
|
1. `mcp__smartstack__scaffold_api_client` → API client + types + React Query hook
|
|
306
306
|
2. `mcp__smartstack__scaffold_routes` (with `outputFormat: "clientRoutes"`) → route registry + route fragments
|
|
307
|
-
3. **Wire routes to App.tsx
|
|
307
|
+
3. **Wire routes to App.tsx (detect pattern: `contextRoutes` array OR JSX `<Route>`):**
|
|
308
|
+
- **Pattern A** (`contextRoutes: ContextRouteExtensions` in App.tsx): add to `contextRoutes.{context}[]` with RELATIVE paths → auto-injected into both standard + tenant trees
|
|
309
|
+
- **Pattern B** (JSX `<Route>` in App.tsx): insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
|
|
308
310
|
4. Create pages using SmartStack components
|
|
309
311
|
5. Create preferences hook: `use{Module}Preferences.ts`
|
|
310
312
|
6. Generate i18n (4 languages: fr, en, it, de)
|
|
@@ -348,7 +350,8 @@ import axios from 'axios' → use @/services/api/apiClient
|
|
|
348
350
|
Only fr/en translations → MUST have 4 languages
|
|
349
351
|
src/pages/{Module}/ → MUST be src/pages/{Context}/{App}/{Module}/
|
|
350
352
|
Routes generated but NOT added to App.tsx → MUST wire routes to App.tsx after scaffold_routes
|
|
351
|
-
Routes in standard block only → MUST also add to /t/:slug/ tenant block
|
|
353
|
+
Routes in standard block only → MUST also add to /t/:slug/ tenant block (Pattern B only; Pattern A auto-handles)
|
|
354
|
+
Adding routes to clientRoutes[] instead of contextRoutes.{context}[] → MUST use contextRoutes for business/platform/personal routes
|
|
352
355
|
'00000000-0000-0000-0000-000000000000' → use dynamic ID from auth context or route params
|
|
353
356
|
const api = axios.create(...) → use @/services/api/apiClient (single instance)
|
|
354
357
|
useXxx with raw axios inside hooks → hooks MUST use the shared apiClient from @/services/api
|
|
@@ -110,7 +110,7 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
110
110
|
|----------|--------|
|
|
111
111
|
| `infrastructure` with `_migrationMeta` | Migration sequence: `suggest_migration` MCP → `dotnet ef migrations add` → `dotnet ef database update` → `dotnet build` |
|
|
112
112
|
| `infrastructure` with seed data keywords | **MANDATORY:** Read `references/core-seed-data.md` → implement templates → `dotnet build` |
|
|
113
|
-
| `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) → **wire to App.tsx (
|
|
113
|
+
| `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) → **wire to App.tsx (detect pattern: `contextRoutes` array OR JSX `<Route>`)** → create pages → `npm run typecheck && npm run lint` |
|
|
114
114
|
| `test` | **Create tests:** `scaffold_tests` MCP → **Run:** `dotnet test --verbosity normal` → **Fix loop:** if fail → fix SOURCE CODE → rebuild → retest → repeat until 100% pass |
|
|
115
115
|
| `validation` | `dotnet clean && dotnet restore && dotnet build` → `dotnet test` (full) → `validate_conventions` MCP |
|
|
116
116
|
|
|
@@ -203,6 +203,57 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
203
203
|
```
|
|
204
204
|
If FAIL → migration is broken → fix → rebuild → DO NOT commit
|
|
205
205
|
|
|
206
|
+
5bis. **Core Seed Data Integrity (BLOCKING — if seed data tasks in batch):**
|
|
207
|
+
```bash
|
|
208
|
+
# Verify NavigationApplicationSeedData.cs exists
|
|
209
|
+
APP_SEED=$(find . -path "*/Seeding/Data/NavigationApplicationSeedData.cs" 2>/dev/null | head -1)
|
|
210
|
+
if [ -z "$APP_SEED" ]; then
|
|
211
|
+
echo "❌ BLOCKING: NavigationApplicationSeedData.cs NOT FOUND"
|
|
212
|
+
echo "Without this: nav_Applications empty → menu invisible → modules inaccessible"
|
|
213
|
+
echo "Fix: Generate from core-seed-data.md section 1b"
|
|
214
|
+
# Create fix task
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# Verify no hardcoded placeholders in provider
|
|
218
|
+
PROVIDER=$(find . -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
|
|
219
|
+
if [ -n "$PROVIDER" ]; then
|
|
220
|
+
if grep -qE '\{appLabel_|\{appDesc_|\{appIcon\}|\{ApplicationGuid\}' "$PROVIDER" 2>/dev/null; then
|
|
221
|
+
echo "❌ BLOCKING: SeedDataProvider has unresolved placeholders"
|
|
222
|
+
echo "Fix: Use NavigationApplicationSeedData.GetApplicationEntry()"
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# Verify ApplicationRolesSeedData references real GUID
|
|
227
|
+
ROLES_SEED=$(find . -path "*/Seeding/Data/ApplicationRolesSeedData.cs" 2>/dev/null | head -1)
|
|
228
|
+
if [ -n "$ROLES_SEED" ]; then
|
|
229
|
+
if grep -q '{ApplicationGuid}' "$ROLES_SEED" 2>/dev/null; then
|
|
230
|
+
echo "❌ BLOCKING: ApplicationRolesSeedData still has {ApplicationGuid} placeholder"
|
|
231
|
+
echo "Fix: Replace with NavigationApplicationSeedData.ApplicationId"
|
|
232
|
+
fi
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# Quick startup test to verify seed data runs without exceptions
|
|
236
|
+
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
237
|
+
if [ -n "$API_PROJECT" ] && [ -n "$APP_SEED" ]; then
|
|
238
|
+
echo "Running seed data startup test..."
|
|
239
|
+
dotnet run --project "$API_PROJECT" --urls "http://localhost:0" -- --environment Development > /tmp/ralph-seed-check.log 2>&1 &
|
|
240
|
+
SEED_PID=$!
|
|
241
|
+
sleep 10
|
|
242
|
+
if ! kill -0 $SEED_PID 2>/dev/null; then
|
|
243
|
+
SEED_LOG=$(cat /tmp/ralph-seed-check.log 2>/dev/null | tail -20)
|
|
244
|
+
echo "❌ BLOCKING: Application crashed during startup — seed data likely failed"
|
|
245
|
+
echo "Last 20 lines: $SEED_LOG"
|
|
246
|
+
# Do NOT continue to frontend/test — fix seed data first
|
|
247
|
+
else
|
|
248
|
+
echo "✓ Seed data startup test passed"
|
|
249
|
+
kill $SEED_PID 2>/dev/null
|
|
250
|
+
wait $SEED_PID 2>/dev/null
|
|
251
|
+
fi
|
|
252
|
+
rm -f /tmp/ralph-seed-check.log
|
|
253
|
+
fi
|
|
254
|
+
```
|
|
255
|
+
If FAIL → fix seed data → rebuild → DO NOT continue to frontend/test tasks
|
|
256
|
+
|
|
206
257
|
**Error resolution cycle:**
|
|
207
258
|
1. Read the FULL error output (not just first line)
|
|
208
259
|
2. Identify root cause: file path + line number
|