@atlashub/smartstack-cli 4.75.0 → 4.79.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 (81) hide show
  1. package/dist/index.js +87 -41
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/project/claude-md/root.CLAUDE.md.template +1 -1
  5. package/templates/skills/ai-prompt/SKILL.md +64 -0
  6. package/templates/skills/ai-prompt/references/ai-agent-modes.md +89 -0
  7. package/templates/skills/ai-prompt/references/eval-framework.md +129 -0
  8. package/templates/skills/apex/SKILL.md +2 -2
  9. package/templates/skills/apex/references/checks/frontend-checks.sh +123 -11
  10. package/templates/skills/apex/references/checks/seed-checks.sh +81 -7
  11. package/templates/skills/apex/references/core-seed-data.md +27 -22
  12. package/templates/skills/apex/references/domain-events-pattern.md +45 -0
  13. package/templates/skills/apex/references/entity-hooks-pattern.md +68 -0
  14. package/templates/skills/apex/references/licensing-enforcement.md +52 -0
  15. package/templates/skills/apex/references/post-checks.md +18 -1
  16. package/templates/skills/apex/references/smartstack-api.md +116 -5
  17. package/templates/skills/apex/references/smartstack-frontend.md +1 -1
  18. package/templates/skills/apex/references/smartstack-layers.md +6 -6
  19. package/templates/skills/apex/steps/step-00-init.md +1 -1
  20. package/templates/skills/apex/steps/step-03b-layer1-seed.md +26 -0
  21. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +124 -2
  22. package/templates/skills/apex/steps/step-04-examine.md +163 -0
  23. package/templates/skills/apex-verify/SKILL.md +110 -0
  24. package/templates/skills/apex-verify/references/audit-rules.md +50 -0
  25. package/templates/skills/apex-verify/steps/step-00-init.md +119 -0
  26. package/templates/skills/apex-verify/steps/step-01-nav-audit.md +96 -0
  27. package/templates/skills/apex-verify/steps/step-02-crud-audit.md +127 -0
  28. package/templates/skills/apex-verify/steps/step-03-perm-audit.md +119 -0
  29. package/templates/skills/apex-verify/steps/step-04-route-audit.md +98 -0
  30. package/templates/skills/apex-verify/steps/step-05-report.md +110 -0
  31. package/templates/skills/application/references/contexts-cheatsheet.md +86 -0
  32. package/templates/skills/application/references/extensions-system.md +158 -0
  33. package/templates/skills/application/references/frontend-route-naming.md +7 -5
  34. package/templates/skills/application/references/frontend-verification.md +7 -5
  35. package/templates/skills/application/references/provider-template.md +4 -2
  36. package/templates/skills/application/references/smartstack-provider.md +118 -0
  37. package/templates/skills/application/references/themes-db-driven.md +484 -0
  38. package/templates/skills/application/templates-frontend.md +2 -2
  39. package/templates/skills/application/templates-seed.md +4 -2
  40. package/templates/skills/audit-route/references/routing-pattern.md +3 -1
  41. package/templates/skills/business-analyse/SKILL.md +3 -3
  42. package/templates/skills/business-analyse/_shared.md +37 -0
  43. package/templates/skills/business-analyse/react/components.md +30 -28
  44. package/templates/skills/business-analyse/references/03-json-schemas.md +11 -3
  45. package/templates/skills/business-analyse/references/03-post-check-validation.md +64 -0
  46. package/templates/skills/business-analyse/references/canonical-json-formats.md +7 -3
  47. package/templates/skills/business-analyse/references/robustness-checks.md +1 -1
  48. package/templates/skills/business-analyse/references/validation-checklist.md +5 -5
  49. package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +15 -4
  50. package/templates/skills/business-analyse/steps/step-03-specify.md +162 -4
  51. package/templates/skills/business-analyse/steps/step-04-consolidate.md +211 -1
  52. package/templates/skills/business-analyse/templates-react.md +15 -15
  53. package/templates/skills/business-analyse-handoff/references/agent-handoff-transform-prompt.md +3 -0
  54. package/templates/skills/business-analyse-html/html/ba-interactive.html +198 -16
  55. package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +64 -0
  56. package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +80 -11
  57. package/templates/skills/business-analyse-html/html/src/scripts/06-render-consolidation.js +2 -2
  58. package/templates/skills/business-analyse-html/html/src/scripts/06-render-mockups.js +6 -3
  59. package/templates/skills/business-analyse-html/html/src/scripts/12-render-diagrams.js +46 -0
  60. package/templates/skills/business-analyse-html/references/02-feature-data-building.md +4 -2
  61. package/templates/skills/business-analyse-html/references/data-build.md +2 -0
  62. package/templates/skills/business-analyse-html/references/data-mapping.md +88 -21
  63. package/templates/skills/business-analyse-html/steps/step-02-build-data.md +6 -0
  64. package/templates/skills/business-analyse-html/steps/step-04-verify.md +92 -3
  65. package/templates/skills/business-analyse-quick/SKILL.md +807 -0
  66. package/templates/skills/{sketch → business-analyse-quick}/references/domain-heuristics.md +59 -3
  67. package/templates/skills/business-analyse-quick/references/prd-schema.md +268 -0
  68. package/templates/skills/business-analyse-review/references/review-data-mapping.md +6 -0
  69. package/templates/skills/cli-app-sync/SKILL.md +105 -4
  70. package/templates/skills/cli-app-sync/references/comparison-map.md +13 -0
  71. package/templates/skills/cli-app-sync/references/diff-entities.md +162 -0
  72. package/templates/skills/dev-start/SKILL.md +7 -7
  73. package/templates/skills/documentation/templates.md +16 -16
  74. package/templates/skills/migrate/SKILL.md +312 -0
  75. package/templates/skills/migrate/references/v3.34-to-v3.46.md +289 -0
  76. package/templates/skills/sketch/SKILL.md +15 -153
  77. package/templates/skills/smoke-generation/SKILL.md +313 -0
  78. package/templates/skills/ui-components/SKILL.md +11 -1
  79. package/templates/skills/ui-components/patterns/data-table.md +1 -1
  80. package/templates/skills/ui-components/references/component-catalog.md +82 -0
  81. package/templates/skills/workflow/SKILL.md +70 -1
@@ -34,19 +34,20 @@ import {
34
34
  import type { FeatureJson, FeatureStatus } from '@/types/docs';
35
35
  import { loadFeature, listVersions } from '@/services/docs';
36
36
 
37
- // Status Badge Component (supports both module and application statuses)
37
+ // Status Badge Component (uses SmartStack theme CSS vars — adapts to dark mode and custom themes)
38
+ // Maps the 8 BA statuses to the 4 canonical status tokens (info/warning/success) + neutral + accent.
38
39
  function StatusBadge({ status }: { status: FeatureStatus }) {
39
40
  const config: Record<FeatureStatus, { color: string; label: string }> = {
40
- 'draft': { color: 'bg-gray-500/10 text-gray-600', label: 'Draft' },
41
- 'framed': { color: 'bg-sky-500/10 text-sky-600', label: 'Framed' },
42
- 'analysed': { color: 'bg-blue-500/10 text-blue-600', label: 'Analysed' },
43
- 'decomposed': { color: 'bg-indigo-500/10 text-indigo-600', label: 'Decomposed' },
44
- 'specified': { color: 'bg-yellow-500/10 text-yellow-600', label: 'Specified' },
45
- 'consolidated': { color: 'bg-orange-500/10 text-orange-600', label: 'Consolidated' },
46
- 'approved': { color: 'bg-green-500/10 text-green-600', label: 'Approved' },
47
- 'handed-off': { color: 'bg-purple-500/10 text-purple-600', label: 'Handed Off' }
41
+ 'draft': { color: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]', label: 'Draft' },
42
+ 'framed': { color: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]', label: 'Framed' },
43
+ 'analysed': { color: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]', label: 'Analysed' },
44
+ 'decomposed': { color: 'bg-[var(--accent-bg)] text-[var(--accent-text)] border border-[var(--accent-border)]', label: 'Decomposed' },
45
+ 'specified': { color: 'bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]', label: 'Specified' },
46
+ 'consolidated': { color: 'bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]', label: 'Consolidated' },
47
+ 'approved': { color: 'bg-[var(--success-bg)] text-[var(--success-text)] border border-[var(--success-border)]', label: 'Approved' },
48
+ 'handed-off': { color: 'bg-[var(--accent-bg)] text-[var(--accent-text)] border border-[var(--accent-border)]', label: 'Handed Off' }
48
49
  };
49
- const { color, label } = config[status] || { color: 'bg-gray-500/10 text-gray-600', label: status };
50
+ const { color, label } = config[status] || { color: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]', label: status };
50
51
  return (
51
52
  <span className={`px-2 py-0.5 rounded text-xs font-medium ${color}`}>
52
53
  {label}
@@ -54,15 +55,15 @@ function StatusBadge({ status }: { status: FeatureStatus }) {
54
55
  );
55
56
  }
56
57
 
57
- // Priority Badge Component
58
+ // Priority Badge Component (MoSCoW → status tokens)
58
59
  function PriorityBadge({ priority }: { priority: string }) {
59
60
  const colors: Record<string, string> = {
60
- Must: 'bg-red-500/10 text-red-600',
61
- Should: 'bg-yellow-500/10 text-yellow-600',
62
- Could: 'bg-blue-500/10 text-blue-600'
61
+ Must: 'bg-[var(--error-bg)] text-[var(--error-text)] border border-[var(--error-border)]',
62
+ Should: 'bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]',
63
+ Could: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]'
63
64
  };
64
65
  return (
65
- <span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[priority] || 'bg-gray-500/10 text-gray-600'}`}>
66
+ <span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[priority] || 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]'}`}>
66
67
  {priority}
67
68
  </span>
68
69
  );
@@ -240,8 +241,8 @@ export function BusinessAnalyseViewer() {
240
241
  {feature.discovery.risks.map((risk) => (
241
242
  <div key={risk.id} className="flex items-start gap-2 p-2 rounded bg-[var(--bg-secondary)] mb-1">
242
243
  <AlertTriangle className={`w-4 h-4 flex-shrink-0 mt-0.5 ${
243
- risk.severity === 'high' ? 'text-red-500' :
244
- risk.severity === 'medium' ? 'text-yellow-500' : 'text-blue-500'
244
+ risk.severity === 'high' ? 'text-[var(--error-text)]' :
245
+ risk.severity === 'medium' ? 'text-[var(--warning-text)]' : 'text-[var(--info-text)]'
245
246
  }`} />
246
247
  <div>
247
248
  <span className="text-sm font-medium">{risk.description}</span>
@@ -341,7 +342,7 @@ export function BusinessAnalyseViewer() {
341
342
  </ol>
342
343
  </div>
343
344
  <div className="flex items-center gap-2 text-xs">
344
- <Shield className="w-3 h-3 text-amber-500" />
345
+ <Shield className="w-3 h-3 text-[var(--warning-text)]" />
345
346
  <code className="bg-[var(--bg-secondary)] px-2 py-0.5 rounded">{uc.permission}</code>
346
347
  {uc.linkedRules.length > 0 && (
347
348
  <span className="text-[var(--text-secondary)]">
@@ -393,11 +394,12 @@ export function BusinessAnalyseViewer() {
393
394
  </thead>
394
395
  <tbody>
395
396
  {feature.specification.apiEndpoints.map((ep, idx) => {
397
+ // HTTP methods → status tokens (theme-aware)
396
398
  const methodColors: Record<string, string> = {
397
- GET: 'bg-green-500/10 text-green-600',
398
- POST: 'bg-blue-500/10 text-blue-600',
399
- PUT: 'bg-yellow-500/10 text-yellow-600',
400
- DELETE: 'bg-red-500/10 text-red-600'
399
+ GET: 'bg-[var(--success-bg)] text-[var(--success-text)] border border-[var(--success-border)]',
400
+ POST: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]',
401
+ PUT: 'bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]',
402
+ DELETE: 'bg-[var(--error-bg)] text-[var(--error-text)] border border-[var(--error-border)]'
401
403
  };
402
404
  return (
403
405
  <tr key={`${ep.method}-${ep.path}`} className={idx % 2 === 1 ? 'bg-[var(--bg-secondary)]/50' : ''}>
@@ -431,7 +433,7 @@ export function BusinessAnalyseViewer() {
431
433
  </h3>
432
434
  <div className="flex gap-1">
433
435
  {wf.permissionsRequired?.map((perm) => (
434
- <code key={perm} className="px-2 py-0.5 rounded bg-amber-500/10 text-amber-600 text-xs">
436
+ <code key={perm} className="px-2 py-0.5 rounded bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)] text-xs">
435
437
  {perm}
436
438
  </code>
437
439
  ))}
@@ -466,8 +468,8 @@ export function BusinessAnalyseViewer() {
466
468
  <div className="space-y-2">
467
469
  {feature.suggestions.map((s) => (
468
470
  <div key={s.code} className={`flex items-center justify-between p-3 rounded-lg border ${
469
- s.accepted === true ? 'border-green-500/30 bg-green-500/5' :
470
- s.accepted === false ? 'border-red-500/30 bg-red-500/5 opacity-50' :
471
+ s.accepted === true ? 'border-[var(--success-border)] bg-[var(--success-bg)]' :
472
+ s.accepted === false ? 'border-[var(--error-border)] bg-[var(--error-bg)] opacity-50' :
471
473
  'border-[var(--border-color)]'
472
474
  }`}>
473
475
  <div>
@@ -476,9 +478,9 @@ export function BusinessAnalyseViewer() {
476
478
  <p className="text-xs text-[var(--text-secondary)]">{s.reason}</p>
477
479
  </div>
478
480
  <span className={`px-2 py-0.5 rounded text-xs ${
479
- s.accepted === true ? 'bg-green-500/10 text-green-600' :
480
- s.accepted === false ? 'bg-red-500/10 text-red-600' :
481
- 'bg-gray-500/10 text-gray-600'
481
+ s.accepted === true ? 'bg-[var(--success-bg)] text-[var(--success-text)] border border-[var(--success-border)]' :
482
+ s.accepted === false ? 'bg-[var(--error-bg)] text-[var(--error-text)] border border-[var(--error-border)]' :
483
+ 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]'
482
484
  }`}>
483
485
  {s.accepted === true ? 'Accepted' : s.accepted === false ? 'Declined' : 'Pending'}
484
486
  </span>
@@ -38,6 +38,8 @@ Omit flags when `false` (default). Include explicitly when `true`.
38
38
 
39
39
  ## Business Rules Schema Format
40
40
 
41
+ > **Source of truth:** `schemas/sections/analysis-schema.json` property `businessRules`.
42
+
41
43
  ```json
42
44
  {
43
45
  "id": "BR-VAL-EMPLOYEES-001",
@@ -45,9 +47,10 @@ Omit flags when `false` (default). Include explicitly when `true`.
45
47
  "category": "validation",
46
48
  "sectionCode": "list",
47
49
  "statement": "La date d'embauche ne peut pas être dans le futur",
48
- "example": "Date embauche = 2027-01-01 → erreur car > date du jour",
50
+ "severity": "blocking",
49
51
  "entities": ["Employee"],
50
- "severity": "blocking"
52
+ "examples": [{ "input": "hireDate = 2027-01-01", "expected": "Erreur : date dans le futur" }],
53
+ "domainSpecific": false
51
54
  }
52
55
  ```
53
56
 
@@ -63,10 +66,15 @@ Categories: `validation`, `calculation`, `workflow`, `security`, `data`, `notifi
63
66
  ```json
64
67
  {
65
68
  "id": "BR-CALC-INV-003",
69
+ "name": "Calcul reste à payer",
66
70
  "category": "calculation",
71
+ "sectionCode": "detail",
67
72
  "statement": "Le reste à payer = total - montant payé",
73
+ "severity": "blocking",
68
74
  "formula": "remainingAmount = total - paidAmount",
69
- "entities": ["Invoice"]
75
+ "entities": ["Invoice"],
76
+ "examples": [{ "input": "total=1000, paidAmount=300", "expected": "remainingAmount = 700" }],
77
+ "domainSpecific": false
70
78
  }
71
79
  ```
72
80
 
@@ -129,6 +129,70 @@ for (const mod of modules) {
129
129
  }
130
130
  }
131
131
  }
132
+
133
+ // ── Business Rules Schema Conformity (C16-C21) ──────────────────────
134
+
135
+ // Quality gate: BR schema field conformity (C16 — BLOCKING)
136
+ for (const br of brs) {
137
+ if (!br.id) errors.push(mod.code + ": BR missing 'id' field");
138
+ else if (!/^BR-(VAL|CALC|WF|SEC|DATA|NOTIF)-[A-Z]{2,4}-\d{3}$/.test(br.id))
139
+ errors.push(mod.code + ": BR '" + br.id + "' does not match ID pattern BR-{CAT}-{MOD}-{NNN}");
140
+ if (!br.name) errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'name'");
141
+ if (!br.category) errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'category'");
142
+ if (!br.statement) errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'statement'");
143
+ if (!br.severity) errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'severity'");
144
+ if (!br.entities || br.entities.length === 0)
145
+ errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'entities[]'");
146
+ // Reject deprecated field names (common drift from sub-agent schema ignorance)
147
+ if (br.code && !br.id) errors.push(mod.code + ": BR uses 'code' instead of canonical 'id'");
148
+ if (br.label && !br.name) errors.push(mod.code + ": BR uses 'label' instead of canonical 'name'");
149
+ if (br.description && !br.statement) errors.push(mod.code + ": BR uses 'description' instead of canonical 'statement'");
150
+ if (br.rule && !br.statement) errors.push(mod.code + ": BR uses 'rule' instead of canonical 'statement'");
151
+ if (br.type && !br.category) errors.push(mod.code + ": BR uses 'type' instead of canonical 'category'");
152
+ }
153
+
154
+ // Quality gate: BR examples format (C17 — BLOCKING)
155
+ for (const br of brs) {
156
+ if (!br.examples || br.examples.length === 0)
157
+ errors.push(mod.code + ": BR '" + (br.id||'?') + "' missing 'examples[]' (min 1 required)");
158
+ if (br.example && !br.examples)
159
+ errors.push(mod.code + ": BR '" + (br.id||'?') + "' uses deprecated 'example' string — use 'examples[]' array of {input, expected}");
160
+ if (br.examples) {
161
+ for (const ex of br.examples) {
162
+ if (typeof ex === 'string')
163
+ errors.push(mod.code + ": BR '" + (br.id||'?') + "' has string in examples[] — must be {input, expected}");
164
+ }
165
+ }
166
+ }
167
+
168
+ // Quality gate: BR count minimum (C18 — BLOCKING if < 4)
169
+ if (brs.length > 0 && brs.length < 4) {
170
+ errors.push(mod.code + ": only " + brs.length + " business rules (minimum: 4)");
171
+ }
172
+
173
+ // Quality gate: Domain-specific ratio (C19 — WARNING if < 2)
174
+ const domainBRs = brs.filter(br => br.domainSpecific === true);
175
+ if (brs.length >= 4 && domainBRs.length < 2) {
176
+ warnings.push(mod.code + ": only " + domainBRs.length + " domain-specific rules out of " + brs.length +
177
+ " (minimum: 2 — see _shared.md rule 3)");
178
+ }
179
+
180
+ // Quality gate: domainSpecific flag presence (C20 — WARNING)
181
+ const brsMissingFlag = brs.filter(br => typeof br.domainSpecific === 'undefined');
182
+ if (brsMissingFlag.length > 0) {
183
+ warnings.push(mod.code + ": " + brsMissingFlag.length + "/" + brs.length +
184
+ " BRs missing 'domainSpecific' flag");
185
+ }
186
+
187
+ // Quality gate: Structured conditions for conditional rules (C21 — WARNING)
188
+ const conditionalBRs = brs.filter(br =>
189
+ br.category === 'workflow' || br.category === 'security' ||
190
+ (br.statement && /\b(IF|Si |si |Quand |quand |Lorsqu)/i.test(br.statement)));
191
+ const brsWithConditions = conditionalBRs.filter(br => br.conditions && br.conditions.length > 0);
192
+ if (conditionalBRs.length > 2 && brsWithConditions.length < 2) {
193
+ warnings.push(mod.code + ": " + brsWithConditions.length + "/" + conditionalBRs.length +
194
+ " conditional BRs have structured conditions[] (recommended: >= 2)");
195
+ }
132
196
  }
133
197
 
134
198
  if (errors.length > 0) {
@@ -83,9 +83,10 @@
83
83
  "category": "validation",
84
84
  "sectionCode": "list",
85
85
  "statement": "La date ne peut pas être dans le futur",
86
- "example": "Date = 2027-01-01 → erreur",
86
+ "severity": "blocking",
87
87
  "entities": ["Employee"],
88
- "severity": "blocking"
88
+ "examples": [{ "input": "date = 2027-01-01", "expected": "Erreur : date dans le futur" }],
89
+ "domainSpecific": false
89
90
  }
90
91
  ]
91
92
  }
@@ -95,7 +96,10 @@
95
96
  |---------------|------------------------|
96
97
  | `rules` (root) | `businessRules` |
97
98
  | `statement` | `description` |
98
- | `id` | `name` (as identifier) |
99
+ | `id` | `name` (as identifier), `code` |
100
+ | `examples` (array of `{input, expected}`) | `example` (singular string) |
101
+ | `category` | `type` |
102
+ | `name` | `label` |
99
103
 
100
104
  Categories: `validation`, `calculation`, `workflow`, `security`, `data`, `notification`
101
105
 
@@ -61,7 +61,7 @@ Ensures each module has ALL required components before being marked as "specifie
61
61
  | 5.1 | Sections count | ≥2 | YES | Modules need list + form minimum |
62
62
  | 5.2 | Wireframes count | 1 per section | YES | EVERY section MUST have wireframe |
63
63
  | 5.3 | Navigation entries | ≥1 | YES | Module must be accessible in menu |
64
- | 5.4 | Navigation route patterns | No `/list` or `/detail/:id` suffixes | YES | list route = module route (index), detail route = module + /:id. FORBIDDEN: `/module/list`, `/module/detail/:id` |
64
+ | 5.4 | Navigation route patterns | No `/detail/:id` suffix | YES | ALL sections use uniform route: module route + /{section-code}. list route MUST end with `/list`. FORBIDDEN: `/module/detail/:id` (detail is implicit) |
65
65
 
66
66
  #### SECTION 6: I18N & Messages (3 checks)
67
67
 
@@ -169,20 +169,20 @@ const checklist = {
169
169
  },
170
170
 
171
171
  navigationRoutePatterns: {
172
- check: "Navigation routes do NOT contain CRUD suffixes (/list, /detail/:id)",
172
+ check: "Navigation routes do NOT contain forbidden patterns (/detail/:id)",
173
173
  status: (() => {
174
174
  const allRoutes = [
175
175
  ...(specification.seedDataCore?.navigationSections || []).map(s => s.route),
176
176
  ...(specification.sections || []).map(s => s.route),
177
177
  ...(specification.navigation?.entries || []).map(e => e.route)
178
178
  ].filter(Boolean);
179
- const forbidden = allRoutes.filter(r => r.match(/\/list$/) || r.match(/\/detail\/:id$/));
179
+ const forbidden = allRoutes.filter(r => r.match(/\/detail\/:id$/));
180
180
  return forbidden.length === 0 ? "PASS" : "FAIL";
181
181
  })(),
182
182
  blocking: true,
183
- details: "FORBIDDEN: /module/list (use /module), /module/detail/:id (use /module/:id). " +
184
- "list route = module route (React Router index route). " +
185
- "detail route = module route + /:id (React Router :id child). " +
183
+ details: "FORBIDDEN: /module/detail/:id (detail is implicit, not a section). " +
184
+ "ALL sections use uniform route: module route + /{section-code}. " +
185
+ "list route = module route + /list. " +
186
186
  "Other sections (dashboard, approve) add /{section} normally."
187
187
  },
188
188
 
@@ -24,9 +24,9 @@
24
24
  "type": "object",
25
25
  "required": ["id", "name", "category", "statement"],
26
26
  "properties": {
27
- "id": { "type": "string", "pattern": "^BR-(VAL|CALC|WF|SEC|DATA)-[A-Z]{2,4}-\\d{3}$", "description": "Module-prefixed BR ID (e.g., BR-VAL-RM-001)" },
27
+ "id": { "type": "string", "pattern": "^BR-(VAL|CALC|WF|SEC|DATA|NOTIF)-[A-Z]{2,4}-\\d{3}$", "description": "Module-prefixed BR ID (e.g., BR-VAL-RM-001)" },
28
28
  "name": { "type": "string" },
29
- "category": { "type": "string", "enum": ["validation", "calculation", "workflow", "security", "data"] },
29
+ "category": { "type": "string", "enum": ["validation", "calculation", "workflow", "security", "data", "notification"] },
30
30
  "statement": { "type": "string", "description": "IF ... THEN ... ELSE ..." },
31
31
  "priority": { "type": "string", "enum": ["must", "should", "could"] },
32
32
  "conditions": {
@@ -48,15 +48,26 @@
48
48
  },
49
49
  "examples": {
50
50
  "type": "array",
51
+ "description": "Test-ready examples: each example = a test case with concrete values. Minimum 2 per rule (happy_path + error). These feed Gherkin generation and automated tests.",
51
52
  "items": {
52
53
  "type": "object",
53
54
  "required": ["input", "expected"],
54
55
  "properties": {
55
- "input": { "type": "string", "description": "Concrete input values" },
56
- "expected": { "type": "string", "description": "Expected result/behavior" }
56
+ "input": { "type": "string", "description": "LEGACY fallback. Prefer given/when/then format." },
57
+ "expected": { "type": "string", "description": "LEGACY fallback. Prefer given/when/then format." },
58
+ "scenario": { "type": "string", "enum": ["happy_path", "error", "boundary", "calculation", "security"], "description": "Test scenario type" },
59
+ "description": { "type": "string", "description": "Human-readable test case description" },
60
+ "given": { "type": "object", "description": "Initial state: Entity.field → concrete value. Ex: {\"Employee.code\": \"EMP-001\", \"existingCodes\": [\"EMP-001\"]}", "additionalProperties": true },
61
+ "when": { "type": "string", "description": "Triggering action. Ex: 'create', 'update', 'submit', 'calculate_remaining'" },
62
+ "then": { "type": "object", "description": "Expected outcome: {result: 'success'|'error', Entity.field: value, message: '...', field: '...'}", "additionalProperties": true }
57
63
  }
58
64
  }
59
65
  },
66
+ "entities": {
67
+ "type": "array",
68
+ "items": { "type": "string" },
69
+ "description": "Entity names this rule applies to"
70
+ },
60
71
  "testability": { "type": "string" },
61
72
  "formula": { "type": "string", "description": "BR-CALC calculation formulas" },
62
73
  "sectionCode": { "type": "string", "description": "Section code the rule applies to" },
@@ -31,6 +31,26 @@ function resolveModuleDir(appCode, moduleCode) {
31
31
  > **Module directory (ba-006):** `mod.dir = resolveModuleDir(applicationCode, module.code)`
32
32
  > Example: `docs/projet-rh/v1.0/human-resources/employees/`
33
33
 
34
+ ## ENFORCEMENT LANGUE (OBLIGATOIRE)
35
+
36
+ Toutes les valeurs textuelles générées dans les fichiers JSON de ce module DOIVENT être rédigées en `{language}` :
37
+
38
+ | Champ JSON | Langue | Exemple FR | Exemple EN |
39
+ |------------|--------|-----------|-----------|
40
+ | entity.description | `{language}` | "Représente un employé" | "Represents an employee" |
41
+ | attribute.description | `{language}` | "Code unique de l'employé" | "Unique employee code" |
42
+ | rule.statement | `{language}` | "Le code doit être unique" | "Code must be unique" |
43
+ | rule.examples[].input | `{language}` | "code='EMP-001', doublon" | "code='EMP-001', duplicate" |
44
+ | rule.examples[].expected | `{language}` | "Erreur : code déjà existant" | "Error: code already exists" |
45
+ | usecase.name | `{language}` | "Créer un employé" | "Create employee" |
46
+ | usecase.mainScenario[] | `{language}` | "L'utilisateur ouvre la page" | "User opens the page" |
47
+ | usecase.preconditions[] | `{language}` | "L'utilisateur est connecté" | "User is logged in" |
48
+ | permission.description | `{language}` | "Consulter la liste" | "View the list" |
49
+
50
+ **Identifiants techniques** (PascalCase) restent en anglais : `Employee`, `AbsenceBalance`, `status`, `BR-VAL-EMP-001`.
51
+
52
+ **VIOLATION = contenu rejeté à la consolidation (step-04).**
53
+
34
54
  ## Prerequisites
35
55
 
36
56
  - Step 02 (structure) completed
@@ -82,6 +102,11 @@ Display(`Scope locked: ${expectedApps} app(s), ${confirmedModules.length} module
82
102
  | - INTERDICTION de spawner des ba-writer pour des modules NON encore |
83
103
  | valides par le client |
84
104
  | |
105
+ | 6. JAMAIS "accelerer" en batchant des modules dans des sub-agents. |
106
+ | Sub-agents (Agent/Snipper) n'ont PAS acces au schema JSON canonique. |
107
+ | PREUVE: audit ba-012 → 4 dialectes JSON, conformite 95% → 35%. |
108
+ | Si > 10 modules: maintenir le sequentiel, le retravail coute plus. |
109
+ | |
85
110
  +==============================================================================+
86
111
 
87
112
  ## Sequential Execution
@@ -349,9 +374,12 @@ IF module has workflow states OR approval patterns:
349
374
  - BR-VAL-ABS-008: "Medical certificate required for absences > 3 days" [domainSpecific]
350
375
  - BR-SEC-ABS-001: "Manager cannot approve their own absences" [domainSpecific]
351
376
 
352
- ELSE (non-workflow module):
377
+ ELSE (non-workflow module with >= 2 entities):
378
+ SHOULD WebSearch: "{module_domain} data validation rules best practices"
353
379
  → Use entity patterns from A-bis for validation rules
354
380
  → Load archetype rules from module-completeness-challenge.md
381
+ → For each archetype rule relevant to this module type, ADD to proposal
382
+ → Mark rules from research with domainSpecific: true
355
383
  ```
356
384
 
357
385
  ### C-bis. Client Validation — Business Rules (OBLIGATOIRE)
@@ -363,13 +391,63 @@ ELSE (non-workflow module):
363
391
  - Patterns domaine (A-bis) : validations standards, calculs attendus
364
392
  - Cadrage : errorFlows, processus décrits
365
393
 
394
+ > **Schema:** ALL fields defined in `schemas/sections/analysis-schema.json` property `businessRules`. Use EXACT canonical field names (`id`, `name`, `category`, `statement`, `severity`, `entities`, `examples`, `sectionCode`, `domainSpecific`).
395
+
366
396
  **ENRICHISSEMENT OBLIGATOIRE pour chaque règle :**
367
397
 
368
398
  Pour chaque BR proposée, inclure ces champs :
369
399
  - `severity` : TOUJOURS spécifier (blocking/warning/info)
370
400
  - `sectionCode` : TOUJOURS spécifier (section où la règle s'applique)
371
- - `examples[]` : OBLIGATOIRE — au moins 1 exemple structuré `{input, expected}`
372
- Ex: `{ input: "debut=15/03 fin=12/03", expected: "Erreur : date de fin avant date de debut" }`
401
+ - `examples[]` : OBLIGATOIRE — format **test-ready** `{scenario, description, given, when, then}`
402
+ Chaque exemple est un **jeu de test** avec des valeurs concrètes (pas de prose).
403
+ Minimum **2 exemples** par règle : `happy_path` + `error` (ou `calculation` + `boundary`).
404
+
405
+ **Format par catégorie :**
406
+ - `validation` :
407
+ ```json
408
+ { "scenario": "happy_path", "description": "Code unique accepté",
409
+ "given": { "Employee.code": "EMP-00042", "existingCodes": [] },
410
+ "when": "create",
411
+ "then": { "result": "success" } }
412
+ { "scenario": "error", "description": "Code dupliqué rejeté",
413
+ "given": { "Employee.code": "EMP-00001", "existingCodes": ["EMP-00001"] },
414
+ "when": "create",
415
+ "then": { "result": "error", "message": "Code déjà existant", "field": "code" } }
416
+ ```
417
+ - `calculation` :
418
+ ```json
419
+ { "scenario": "calculation", "description": "Solde avec report",
420
+ "given": { "AbsenceBalance.entitled": 25, "AbsenceBalance.carriedForward": 3, "AbsenceBalance.taken": 12 },
421
+ "when": "calculate_remaining",
422
+ "then": { "AbsenceBalance.remaining": 16, "formula": "25 + 3 - 12 = 16" } }
423
+ { "scenario": "boundary", "description": "Solde à zéro",
424
+ "given": { "AbsenceBalance.entitled": 10, "AbsenceBalance.carriedForward": 0, "AbsenceBalance.taken": 10 },
425
+ "when": "calculate_remaining",
426
+ "then": { "AbsenceBalance.remaining": 0 } }
427
+ ```
428
+ - `workflow` :
429
+ ```json
430
+ { "scenario": "happy_path", "description": "Transition Draft→Submitted",
431
+ "given": { "Absence.status": "Draft" },
432
+ "when": "submit",
433
+ "then": { "Absence.status": "Submitted", "result": "success" } }
434
+ { "scenario": "error", "description": "Transition interdite Approved→Draft",
435
+ "given": { "Absence.status": "Approved" },
436
+ "when": "submit",
437
+ "then": { "result": "error", "message": "Transition non autorisée" } }
438
+ ```
439
+ - `security` :
440
+ ```json
441
+ { "scenario": "happy_path", "description": "Admin accède à toutes les fiches",
442
+ "given": { "user.role": "RH Admin", "Employee.departmentId": "any" },
443
+ "when": "read",
444
+ "then": { "result": "success", "scope": "all" } }
445
+ { "scenario": "error", "description": "Employé accède uniquement à sa fiche",
446
+ "given": { "user.role": "Employé", "target.employeeId": "other-user" },
447
+ "when": "read",
448
+ "then": { "result": "error", "message": "Accès limité à votre fiche" } }
449
+ ```
450
+ **Fallback** : `{input, expected}` accepté mais marqué WARNING "format non test-ready".
373
451
  - `conditions[]` : OBLIGATOIRE pour les règles conditionnelles (IF/THEN) — structuré `{entity, field, operator, value}`
374
452
  Ex: `[{ entity: "Absence", field: "type", operator: "==", value: "Maladie" }, { entity: "Absence", field: "duration", operator: ">", value: 3 }]`
375
453
  - `consequences[]` : OBLIGATOIRE pour les règles avec effets de bord
@@ -378,9 +456,20 @@ ELSE (non-workflow module):
378
456
  - `domainSpecific: true` si la règle vient de la recherche domaine (C-pre)
379
457
 
380
458
  **Minimums par module métier (hors Portal/config) :**
381
- - >= 4 règles avec `examples[]` structurés `{input, expected}`
459
+ - 100% des règles avec `examples[]` non vide (minimum 2 par règle : happy_path + error)
460
+ - >= 4 règles avec exemples test-ready `{scenario, given, when, then}`
382
461
  - >= 2 règles avec `conditions[]` structurés
383
462
  - >= 1 règle avec `consequences[]`
463
+ - Toute BR-CALC avec un exemple `calculation` contenant des valeurs numériques chiffrées
464
+
465
+ **Self-check AVANT présentation client :**
466
+ ```
467
+ domainCount = rules.filter(r => r.domainSpecific === true).length
468
+ IF domainCount < 2 AND module is NOT config/lookup/Portal:
469
+ SELF-CHECK: "Only {domainCount} domain-specific rules."
470
+ → Re-examine module-completeness-challenge.md archetypes for this module type
471
+ → Add at least 2 domain-specific rules before presenting to client
472
+ ```
384
473
 
385
474
  2. **Présenter le LOT complet** (markdown PUIS AskUserQuestion) :
386
475
  ```
@@ -398,6 +487,60 @@ ELSE (non-workflow module):
398
487
  - Q3.8 si les relations inter-champs sont complexes
399
488
  - Q3.9 si les données sensibles ne sont pas identifiées
400
489
 
490
+ 4. **POST-VALIDATION : Qualité test-ready des exemples (OBLIGATOIRE)**
491
+
492
+ Après validation client des règles métier, vérifier la qualité des exemples AVANT écriture :
493
+
494
+ ```javascript
495
+ // 1. Règles sans aucun exemple
496
+ const rulesWithoutExamples = module.rules.filter(r =>
497
+ !r.examples || r.examples.length === 0
498
+ );
499
+ if (rulesWithoutExamples.length > 0) {
500
+ // Auto-générer des exemples test-ready à partir du statement et des conditions
501
+ for (const rule of rulesWithoutExamples) {
502
+ rule.examples = generateTestReadyExamples(rule);
503
+ // Génère minimum: 1 happy_path + 1 error avec given/when/then
504
+ }
505
+ Display(`${rulesWithoutExamples.length} règle(s) complétée(s) avec exemples test-ready auto-générés.`);
506
+ }
507
+
508
+ // 2. Règles avec exemples en prose (ancien format {input, expected} sans given/when/then)
509
+ const proseExamples = module.rules.filter(r =>
510
+ r.examples?.length > 0 && !r.examples.some(e => e.given && e.when && e.then)
511
+ );
512
+ if (proseExamples.length > 0) {
513
+ // Convertir les exemples prose en format test-ready
514
+ for (const rule of proseExamples) {
515
+ rule.examples = rule.examples.map(e => convertToTestReady(e, rule));
516
+ }
517
+ Display(`${proseExamples.length} règle(s) converties du format prose au format test-ready.`);
518
+ }
519
+
520
+ // 3. BR-CALC sans exemple chiffré
521
+ const calcWithoutNumbers = module.rules.filter(r =>
522
+ r.category === 'calculation' &&
523
+ !r.examples?.some(e => e.scenario === 'calculation' || (e.then && Object.values(e.then).some(v => typeof v === 'number')))
524
+ );
525
+ if (calcWithoutNumbers.length > 0) {
526
+ Display(`⚠ ${calcWithoutNumbers.length} BR-CALC sans exemple chiffré — ajouter des valeurs numériques.`);
527
+ }
528
+
529
+ // 4. Vérification minimale
530
+ const testReady = module.rules.filter(r =>
531
+ r.examples?.some(e => e.given && e.when && e.then)
532
+ );
533
+ if (testReady.length < 4 && module.featureType !== 'portal') {
534
+ Display(`⚠ ${testReady.length}/4 règles au format test-ready {given, when, then}. Enrichir.`);
535
+ }
536
+ ```
537
+
538
+ **Minimum requis par module métier (hors Portal/config) :**
539
+ - 100% des règles avec `examples[]` non vide (minimum 2 par règle)
540
+ - >= 4 règles avec exemples test-ready `{scenario, given, when, then}`
541
+ - Toute BR-CALC avec un exemple `calculation` contenant des valeurs numériques
542
+ - Zéro exemple en prose vague (tout doit être en format `given/when/then`)
543
+
401
544
  INTERDICTION de passer à D (Use Cases) tant que le client n'a pas validé les règles.
402
545
 
403
546
  ### D. Use Cases
@@ -767,6 +910,21 @@ For EACH apiEndpoint in module:
767
910
 
768
911
  **criture :** Stocker dans `usecases.json` sous la cl `apiEndpoints[]` (enrichi).
769
912
 
913
+ ### H-pre. Schema Refresh (OBLIGATOIRE avant chaque ecriture)
914
+
915
+ AVANT d'ecrire les JSON pour le module courant :
916
+ → **Relire** `references/03-json-schemas.md` section "Business Rules Schema Format"
917
+ → Verifier que CHAQUE rule dans le buffer utilise les champs canoniques :
918
+ - `id` (pas `code`), `name` (pas `label`), `statement` (pas `description`/`rule`),
919
+ `category` (pas `type`), `severity` (blocking/warning/info, pas Error/error),
920
+ `examples[]` (array d'`{input, expected}`, pas string), `entities[]`, `sectionCode`
921
+ → SI un champ deprecated est detecte : corriger AVANT ecriture
922
+ → Ce refresh prend ~30 secondes et previent 100% des derives de schema
923
+
924
+ **POURQUOI ce refresh :** Apres 5+ modules, le contexte est compacte et les exemples
925
+ JSON du debut de conversation sont perdus. Ce refresh garantit que chaque module
926
+ est ecrit avec le format canonique, meme si le contexte a ete compresse.
927
+
770
928
  ### H. Write & Advance (SEULEMENT apres validation complete)
771
929
 
772
930
  > **GATE:** ba-writer ne peut etre lance QUE si le client a valide :