@atlashub/smartstack-cli 4.76.0 → 4.80.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 (34) hide show
  1. package/package.json +1 -1
  2. package/templates/project/claude-md/root.CLAUDE.md.template +1 -1
  3. package/templates/skills/ai-prompt/SKILL.md +64 -0
  4. package/templates/skills/ai-prompt/references/ai-agent-modes.md +89 -0
  5. package/templates/skills/ai-prompt/references/eval-framework.md +129 -0
  6. package/templates/skills/apex/references/checks/frontend-checks.sh +97 -11
  7. package/templates/skills/apex/references/checks/seed-checks.sh +34 -0
  8. package/templates/skills/apex/references/core-seed-data.md +7 -4
  9. package/templates/skills/apex/references/domain-events-pattern.md +45 -0
  10. package/templates/skills/apex/references/entity-hooks-pattern.md +68 -0
  11. package/templates/skills/apex/references/licensing-enforcement.md +52 -0
  12. package/templates/skills/apex/references/smartstack-api.md +112 -1
  13. package/templates/skills/apex-verify/steps/step-01-nav-audit.md +4 -0
  14. package/templates/skills/application/references/contexts-cheatsheet.md +86 -0
  15. package/templates/skills/application/references/extensions-system.md +158 -0
  16. package/templates/skills/application/references/frontend-route-naming.md +7 -5
  17. package/templates/skills/application/references/frontend-verification.md +7 -5
  18. package/templates/skills/application/references/provider-template.md +4 -2
  19. package/templates/skills/application/references/smartstack-provider.md +118 -0
  20. package/templates/skills/application/references/themes-db-driven.md +484 -0
  21. package/templates/skills/application/templates-seed.md +4 -2
  22. package/templates/skills/audit-route/references/routing-pattern.md +3 -1
  23. package/templates/skills/business-analyse/react/components.md +30 -28
  24. package/templates/skills/business-analyse/templates-react.md +15 -15
  25. package/templates/skills/cli-app-sync/SKILL.md +105 -4
  26. package/templates/skills/cli-app-sync/references/comparison-map.md +13 -0
  27. package/templates/skills/cli-app-sync/references/diff-entities.md +162 -0
  28. package/templates/skills/documentation/templates.md +16 -16
  29. package/templates/skills/migrate/SKILL.md +312 -0
  30. package/templates/skills/migrate/references/v3.34-to-v3.46.md +289 -0
  31. package/templates/skills/smoke-generation/SKILL.md +313 -0
  32. package/templates/skills/ui-components/SKILL.md +10 -0
  33. package/templates/skills/ui-components/references/component-catalog.md +82 -0
  34. package/templates/skills/workflow/SKILL.md +70 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "4.76.0",
3
+ "version": "4.80.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -96,7 +96,7 @@ Application → Module → Section → Resource
96
96
  Permission: myapp.orders.{action}
97
97
  ```
98
98
 
99
- > **Note**: Applications have an `ApplicationZone` property (enum: `Platform`, `Personal`, `Business`) used for UI layout grouping only. It does NOT appear in routes or permissions.
99
+ > **Note (v3.46+)**: The legacy `ApplicationZone` enum has been removed. Applications now have two boolean flags : `IsPersonal` (default `false`) — when `true`, the app belongs to the user's personal scope (e.g. myspace) and bypasses tenant catalog filtering / permission checks ; `IsOpen` (default `false`) when `true`, the app is accessible without permission checks (replaces the legacy hardcoded OPEN_APPS list in the frontend RouteGuard). Neither flag appears in routes or permission paths — they only steer access control.
100
100
 
101
101
  ### Permission Actions
102
102
  `access` | `read` | `create` | `update` | `delete` | `export` | `import` | `approve` | `reject` | `assign` | `execute`
@@ -58,6 +58,61 @@ Configured instance: Code, Name, SystemContext, DefaultModel, MonthlyBudgetLimit
58
58
  ### OutputSchema
59
59
  JSON Schema for validation: Code, Name, JsonSchema, DotNetType
60
60
 
61
+ ### AiAgent (R17 — v3.46+)
62
+ Multi-step agent with reasoning modes. See [references/ai-agent-modes.md](references/ai-agent-modes.md).
63
+
64
+ - **AiAgentMode** : `Sequential` (0), `ReAct` (1), `PlanAndExecute` (2), `SelfCorrection` (3)
65
+ - **AiAgentStepRole** : `Worker` (0), `Planner` (1), `Reasoner` (2), `Critic` (3), `Synthesizer` (4)
66
+ - **MaxIterations** (default 5, range 1-20) — protects against infinite loops
67
+ - **QualityThreshold** (default 0.8) — used by `SelfCorrection` mode
68
+ - **AiSkill.CacheTtlSeconds** + **AiSkillBlock.IsCacheable** — caching for idempotent outputs
69
+
70
+ Default to `Sequential` ; switch to `ReAct`/`PlanAndExecute`/`SelfCorrection` only when needed (cost/latency impact).
71
+
72
+ ### AiTool — function calling (R2 — v3.46+)
73
+
74
+ Server-side function the model can decide to invoke during a completion. Tools turn skills from one-shot prompts into agentic workflows that can read live data, call external APIs, and condition output on the result.
75
+
76
+ ```csharp
77
+ class AiTool : BaseEntity, IAuditableEntity
78
+ {
79
+ public string Name { get; } // Snake_case, presented to model: "search_database"
80
+ public string DisplayName { get; } // Admin UI label
81
+ public string Description { get; } // Drives model decision — short, action-oriented
82
+ public string ParametersSchemaJson { get; } // JSON Schema of tool arguments
83
+ public string HandlerKey { get; } // Stable key → IToolHandler dispatcher
84
+ public Guid ModuleId { get; } // Module owns the tool (permissions + admin grouping)
85
+ public bool IsActive { get; }
86
+ }
87
+ ```
88
+
89
+ - **Many-to-many** with `AiSkill` via `AiSkillTool` (kept as own entity for future per-attachment overrides : required flag, parameter aliasing, max-call budget).
90
+ - **HandlerKey** decouples definition from C# class name (handlers can be renamed without breaking the catalog).
91
+ - When `IsActive = false`, the orchestrator skips the tool even if a skill still references it.
92
+
93
+ **DO / DON'T:**
94
+ - DO author tool descriptions for the model — short, action-oriented, with preconditions
95
+ - DO snake_case the `Name` (LLM convention)
96
+ - DON'T expose long-running or heavy tools via function calling — prefer Workflow steps
97
+ - DON'T duplicate tool logic across skills — attach the same `AiTool` via `AiSkillTool`
98
+
99
+ ### AiAgentExecution — runtime tracking
100
+
101
+ Each execution of an `AiAgent` is recorded via `AiAgentExecution` with:
102
+ - `Status` : `Running` / `Completed` / `Failed` / `PartiallyCompleted` / `Cancelled`
103
+ - `ExecutedSteps` / `TotalSteps`
104
+ - `InputStateJson` / `FinalStateJson` (audit trail)
105
+ - **Usage tracking** : `TotalInputTokens`, `TotalOutputTokens`, `TotalCost` (decimal), `TotalExecutionTimeMs`
106
+ - `_stepExecutions` : per-step `AiAgentStepExecution` (with own status, tokens, cost)
107
+
108
+ Service methods : `IncrementExecutedSteps()`, `MarkCompleted(json)`, `MarkFailed(msg)`, `MarkPartiallyCompleted(json, msg)`, `MarkCancelled(msg)`, `AccumulateUsage(in, out, cost, ms)`, `AddStepExecution(step)`.
109
+
110
+ **Use** : surface in admin UI (run history, cost dashboard, debug trace). Each agent run produces one `AiAgentExecution` + N `AiAgentStepExecution`.
111
+
112
+ ### AI Evaluation Framework (R10)
113
+
114
+ For benchmarking skills against expected outputs, see [references/eval-framework.md](references/eval-framework.md). Pattern: `AiEvalDataset` (test cases) + `AiEvaluation` (one run) + `AiEvalResult` (per-item outcome). Provider/model agnostic — same dataset can compare 2 versions of the same skill code.
115
+
61
116
  ## Implementation
62
117
 
63
118
  Start with [steps/step-00-init.md](steps/step-00-init.md) to gather requirements, then proceed to [steps/step-01-implementation.md](steps/step-01-implementation.md) for:
@@ -95,7 +150,16 @@ Start with [steps/step-00-init.md](steps/step-00-init.md) to gather requirements
95
150
  | `Domain/AI/Prompts/Prompt.cs` | Prompt entity |
96
151
  | `Domain/AI/Schemas/OutputSchema.cs` | Schema validation |
97
152
  | `Domain/AI/AiProviderInstance.cs` | Configured instance |
153
+ | `Domain/AI/Agents/AiAgent.cs` | Multi-step agent (R17 — v3.46+) |
154
+ | `Domain/AI/Agents/AiAgentStep.cs` | Agent step with `Role` (Worker/Planner/Reasoner/Critic/Synthesizer) |
155
+ | `Domain/AI/Agents/AiAgentExecution.cs` | Run tracking (status, tokens, cost, step executions) |
156
+ | `Domain/AI/Skills/AiSkill.cs` | Reusable skill (`CacheTtlSeconds` for cache hint) |
157
+ | `Domain/AI/Tools/AiTool.cs` | Function-calling tool (R2 — v3.46+) |
158
+ | `Domain/AI/Tools/AiSkillTool.cs` | Many-to-many AiSkill ↔ AiTool |
159
+ | `Domain/AI/Evaluations/AiEvalDataset.cs` | Eval dataset (R10 — v3.46+) |
160
+ | `Domain/AI/Evaluations/AiEvaluation.cs` | Eval run with rollup stats |
98
161
  | `Application/Common/Interfaces/IAiCompletionService.cs` | Service interface |
162
+ | `Application/Common/Interfaces/IToolHandler.cs` | Tool handler interface (resolved via HandlerKey) |
99
163
  | `Infrastructure/Services/AI/AiCompletionService.cs` | Implementation |
100
164
  | `web/src/services/api/aiApi.ts` | Frontend API |
101
165
 
@@ -0,0 +1,89 @@
1
+ # AI Agent Modes & Step Roles (R17 — v3.46)
2
+
3
+ > **Reference for `ai-prompt` skill** — how to choose `AiAgentMode` and `AiAgentStepRole` when scaffolding multi-step AI agents.
4
+
5
+ ## When This Reference Applies
6
+
7
+ - You create an `AiAgent` with more than one step
8
+ - You need a non-Sequential reasoning mode (loop, plan-then-execute, self-correct)
9
+ - The user asks "how do I make the agent retry", "how do I make it plan first", "what is ReAct"
10
+
11
+ For single-prompt skills, the basic `Prompt + Block + OutputSchema` pattern is enough — no agent, no mode.
12
+
13
+ ---
14
+
15
+ ## `AiAgentMode` (4 values)
16
+
17
+ | Mode | Value | Use case | Latency | Cost |
18
+ |---|---|---|---|---|
19
+ | `Sequential` | 0 | Known DAG fan-out / fan-in (analyze → summarize → email) | Low | Low |
20
+ | `ReAct` | 1 | Reasoning + tool loop, plan unknown ahead of time | High | High |
21
+ | `PlanAndExecute` | 2 | Task that can be planned upfront then executed | Medium | Medium |
22
+ | `SelfCorrection` | 3 | Output that can be critiqued and replayed | Medium | Medium-High |
23
+
24
+ ### Recommended configuration
25
+
26
+ | Field | Default | Range |
27
+ |---|---|---|
28
+ | `MaxIterations` | 5 | [1..20] — protects against infinite loops |
29
+ | `QualityThreshold` | 0.8 | [0..1] — used by `SelfCorrection` to decide replay |
30
+
31
+ ---
32
+
33
+ ## `AiAgentStepRole` (5 values)
34
+
35
+ | Role | Value | Expected output schema |
36
+ |---|---|---|
37
+ | `Worker` | 0 | Standard skill output (used in `Sequential` mode) |
38
+ | `Planner` | 1 | `{ plan: [{ skillCode, label }] }` — produces the plan in `PlanAndExecute` |
39
+ | `Reasoner` | 2 | `{ action, input }` or `{ final_answer }` — used in `ReAct` |
40
+ | `Critic` | 3 | `{ score: 0..1, feedback?: string }` — used in `SelfCorrection` |
41
+ | `Synthesizer` | 4 | Final aggregated output (`PlanAndExecute` after parallel workers) |
42
+
43
+ ---
44
+
45
+ ## Cache hint (R-cache)
46
+
47
+ | Field | Type | Purpose |
48
+ |---|---|---|
49
+ | `AiSkill.CacheTtlSeconds` | `int?` | TTL for caching this skill's output (per input hash). Null = no cache. |
50
+ | `AiSkillBlock.IsCacheable` | `bool` | Marks an idempotent block whose output can be reused across executions. |
51
+
52
+ Only mark a skill / block cacheable when the output is **deterministic for a given input** — otherwise stale answers will be served.
53
+
54
+ ---
55
+
56
+ ## Choosing a mode (decision tree)
57
+
58
+ ```
59
+ Question: do I know the steps in advance?
60
+ Yes → Sequential
61
+ No → Question: can the LLM decide the steps once, then execute?
62
+ Yes → PlanAndExecute
63
+ No → Question: must I let the LLM iterate with tools?
64
+ Yes → ReAct
65
+ No → SelfCorrection (single attempt + critique loop)
66
+ ```
67
+
68
+ ---
69
+
70
+ ## DO / DON'T
71
+
72
+ | ✅ DO | ❌ DON'T |
73
+ |---|---|
74
+ | Start with `Sequential` — switch to other modes only when needed | Default to `ReAct` (expensive, hard to debug) |
75
+ | Cap `MaxIterations` at the smallest number that satisfies your case | Leave the default 5 for `ReAct` on long-running tasks (set 10-15) |
76
+ | Set `QualityThreshold` based on prompt evaluation results | Pick `0.8` blindly — measure on your eval dataset |
77
+ | Use `Critic` role with a structured output schema | Let the critic return free-form text |
78
+ | Cache only deterministic, idempotent skills | Cache anything that uses `now()` or randomness |
79
+
80
+ ---
81
+
82
+ ## Reference source files (read-only)
83
+
84
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Agents\AiAgent.cs`
85
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Agents\AiAgentMode.cs`
86
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Agents\AiAgentStep.cs`
87
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Agents\AiAgentStepRole.cs`
88
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Skills\AiSkill.cs` (CacheTtlSeconds)
89
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Skills\AiSkillBlock.cs` (IsCacheable)
@@ -0,0 +1,129 @@
1
+ # AI Evaluation Framework (R10 — v3.46+)
2
+
3
+ > **Reference for `ai-prompt` skill** — how to evaluate AI skills against expected outputs.
4
+
5
+ ## When This Reference Applies
6
+
7
+ - You are about to ship a new prompt or change an existing one
8
+ - You need to compare two versions of the same skill (`v1.0.0` vs `v1.1.0`)
9
+ - You want to benchmark the same prompt across providers/models (OpenAI vs Claude vs Gemini)
10
+ - The user asks "how do I test my prompt", "how do I prevent regression", "how do I A/B prompts"
11
+
12
+ For one-shot manual testing during development, the eval framework is overkill — call the skill directly. Use this framework when you want **reproducible, scored, auditable** runs.
13
+
14
+ ---
15
+
16
+ ## The 4 entities
17
+
18
+ ### `AiEvalDataset` — collection of test cases
19
+
20
+ ```csharp
21
+ class AiEvalDataset : BaseEntity, IAuditableEntity
22
+ {
23
+ public string Code { get; } // kebab-case stable id, e.g. "support-classification-v1"
24
+ public string Name { get; }
25
+ public string? Description { get; }
26
+ public IReadOnlyCollection<AiEvalDatasetItem> Items { get; }
27
+ }
28
+ ```
29
+
30
+ **Provider/model agnostic** : the same dataset can be replayed against multiple skills, providers and models to compare quality and cost.
31
+
32
+ ### `AiEvalDatasetItem` — one test case
33
+
34
+ ```csharp
35
+ class AiEvalDatasetItem
36
+ {
37
+ public Guid DatasetId { get; }
38
+ public string Label { get; } // Human-readable name for the case
39
+ public string VariablesJson { get; } // Inputs to feed into the skill
40
+ public string ExpectedOutputJson { get; } // Expected result (for grading)
41
+ }
42
+ ```
43
+
44
+ ### `AiEvaluation` — one run
45
+
46
+ ```csharp
47
+ class AiEvaluation : BaseEntity, IAuditableEntity
48
+ {
49
+ public Guid SkillId { get; } // Which skill was tested
50
+ public Guid DatasetId { get; } // Which dataset was replayed
51
+ public AiEvaluationStatus Status { get; } // Running / Completed / Failed
52
+ public DateTime StartedAt { get; }
53
+ public DateTime? CompletedAt { get; }
54
+ public int ProcessedItems { get; }
55
+ public int TotalItems { get; }
56
+ public int PassedItems { get; } // Items whose actual output matched expected
57
+ public double? AverageScore { get; } // [0..1], null while in progress
58
+ public IReadOnlyCollection<AiEvalResult> Results { get; }
59
+ }
60
+
61
+ enum AiEvaluationStatus { Running = 0, Completed = 1, Failed = 2 }
62
+ ```
63
+
64
+ Service methods : `RecordResult(AiEvalResult)`, `MarkCompleted()`, `MarkFailed(reason)`. The aggregate computes `AverageScore` from the per-item `Score` values when completed.
65
+
66
+ ### `AiEvalResult` — per-item outcome
67
+
68
+ Records actual output vs expected, the score (0..1), pass/fail flag, and any tokens/cost incurred.
69
+
70
+ ---
71
+
72
+ ## Workflow
73
+
74
+ ```
75
+ 1. Curate dataset
76
+ └─ AiEvalDataset.Create("support-classification-v1", "Support classification — 50 cases")
77
+ └─ AddItem("simple billing question", { "ticket": "..." }, { "category": "billing" })
78
+ └─ AddItem("urgent outage report", { "ticket": "..." }, { "category": "incident" })
79
+ └─ … 48 more items
80
+
81
+ 2. Run evaluation against current skill version
82
+ └─ AiEvaluation.Create(skillId, datasetId, totalItems: 50)
83
+ └─ For each item: invoke skill, grade output, RecordResult(actual, expected, score)
84
+ └─ MarkCompleted()
85
+ └─ Stored AverageScore = 0.86 (43/50 passed)
86
+
87
+ 3. Iterate — change prompt / blocks / model
88
+ └─ New AiEvaluation against same dataset
89
+ └─ Compare AverageScore : 0.86 → 0.92 (+0.06)
90
+
91
+ 4. Decide
92
+ └─ If new score significantly higher : promote prompt version
93
+ └─ If equal or lower : revert
94
+ ```
95
+
96
+ ---
97
+
98
+ ## DO / DON'T
99
+
100
+ | ✅ DO | ❌ DON'T |
101
+ |---|---|
102
+ | Build datasets from real production examples (anonymised) | Use synthetic / hallucinated test cases as the only ones |
103
+ | Stabilise dataset.Code — never rename, only add new datasets | Mutate dataset items after first run (compromises history) |
104
+ | Grade with structured comparison (JSON match, key fields, semantic distance) | Grade with substring search / regex |
105
+ | Track cost per evaluation — reject prompt changes that 3× cost for marginal score | Optimise for score alone |
106
+ | Run evals as part of the prompt-change PR review | Ship prompt changes without an eval baseline |
107
+ | Keep `AverageScore` as the single headline metric | Multiply scoring criteria — pick one and stick to it |
108
+
109
+ ---
110
+
111
+ ## Wiring into a release pipeline
112
+
113
+ A prompt change should run the relevant evaluation as part of CI:
114
+
115
+ 1. PR opens with a new prompt version (`v1.1.0`).
116
+ 2. CI invokes a Command that creates an `AiEvaluation(skillId, datasetId, totalItems)` against `v1.1.0`.
117
+ 3. CI compares `AverageScore` of `v1.1.0` vs the latest passing eval of `v1.0.0`.
118
+ 4. PR is annotated with the delta. Score regression > 5% → PR blocked.
119
+
120
+ ---
121
+
122
+ ## Reference source files (read-only)
123
+
124
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Evaluations\AiEvalDataset.cs`
125
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Evaluations\AiEvalDatasetItem.cs`
126
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Evaluations\AiEvaluation.cs`
127
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\AI\Evaluations\AiEvalResult.cs`
128
+
129
+ See also [ai-agent-modes.md](ai-agent-modes.md) for `MaxIterations` / `QualityThreshold` (used by `SelfCorrection` mode in conjunction with eval scores).
@@ -191,28 +191,74 @@ if [ -n "$ALL_PAGES" ]; then
191
191
  fi
192
192
  fi
193
193
 
194
- # POST-CHECK C9: No hardcoded Tailwind colors in generated pages (WARNING)
194
+ # POST-CHECK C9: No hardcoded Tailwind colors in generated pages (BLOCKING)
195
+ # v3.46+ : tightened enforcement. Theme system via CSS variables is mandatory.
195
196
  ALL_PAGES=$(find src/pages/ src/components/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
196
197
  if [ -n "$ALL_PAGES" ]; then
197
- HARDCODED=$(grep -Pn '(bg|text|border)-(?!\[)(red|blue|green|gray|white|black|slate|zinc|neutral|stone)-' $ALL_PAGES 2>/dev/null || true)
198
+ # Extended palette: catches Tailwind 22 named colors + white/black
199
+ # Excludes status `text-white`/`text-black` ONLY when used as foreground over a CSS-var background (rare)
200
+ HARDCODED=$(grep -Pn '(bg|text|border)-(?!\[)(red|blue|green|gray|white|black|slate|zinc|neutral|stone|amber|yellow|orange|purple|indigo|violet|cyan|pink|emerald|rose|sky|teal|lime|fuchsia)-' $ALL_PAGES 2>/dev/null || true)
198
201
  if [ -n "$HARDCODED" ]; then
199
- echo "WARNING: Pages MUST use CSS variables instead of hardcoded Tailwind colors"
200
- echo "SmartStack uses a theme system — hardcoded colors break dark mode and custom themes"
202
+ echo "BLOCKING (C9): Pages MUST use CSS variables instead of hardcoded Tailwind colors."
203
+ echo "SmartStack uses a theme system — hardcoded colors break dark mode and custom themes."
201
204
  echo ""
202
- echo "Fix mapping:"
205
+ echo "Fix mapping (canonical SmartStack tokens):"
203
206
  echo " bg-white → bg-[var(--bg-card)]"
204
207
  echo " bg-gray-50 → bg-[var(--bg-primary)]"
208
+ echo " bg-gray-100 → bg-[var(--bg-secondary)]"
209
+ echo " bg-gray-200 → bg-[var(--bg-tertiary)]"
205
210
  echo " text-gray-900 → text-[var(--text-primary)]"
206
- echo " text-gray-500 → text-[var(--text-secondary)]"
211
+ echo " text-gray-700 → text-[var(--text-secondary)]"
212
+ echo " text-gray-500 → text-[var(--text-tertiary)]"
213
+ echo " text-gray-400 → text-[var(--text-muted)]"
207
214
  echo " border-gray-200 → border-[var(--border-color)]"
208
- echo " bg-blue-600 bg-[var(--color-accent-500)]"
209
- echo " text-blue-600 text-[var(--color-accent-500)]"
210
- echo " text-red-500 text-[var(--error-text)]"
211
- echo " bg-green-500 bg-[var(--success-bg)]"
215
+ echo " border-gray-100 border-[var(--border-subtle)]"
216
+ echo " bg-blue-* bg-[var(--info-bg)] (semantic info)"
217
+ echo " bg-blue-600 bg-[var(--color-accent-600)] (action button)"
218
+ echo " text-blue-600 text-[var(--color-accent-600)]"
219
+ echo " text-red-* → text-[var(--error-text)]"
220
+ echo " bg-red-500/10 → bg-[var(--error-bg)]"
221
+ echo " text-yellow-* → text-[var(--warning-text)]"
222
+ echo " bg-amber-500/10 → bg-[var(--warning-bg)]"
223
+ echo " bg-green-* → bg-[var(--success-bg)]"
224
+ echo " text-green-* → text-[var(--success-text)]"
212
225
  echo ""
213
- echo "See references/smartstack-frontend.md section 4 for full variable reference"
226
+ echo "Status badges use the 4 status token sets : --success-*, --warning-*, --error-*, --info-*"
227
+ echo " Example: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]'"
228
+ echo ""
229
+ echo "Avoid Tailwind \`dark:\` prefix — SmartStack toggles a \`.dark\` class on <html> and CSS vars adapt automatically."
230
+ echo ""
231
+ echo "References:"
232
+ echo " - templates/skills/application/references/themes-db-driven.md (full token list)"
233
+ echo " - templates/skills/ui-components/style-guide.md (DO/DON'T)"
214
234
  echo ""
215
235
  echo "$HARDCODED"
236
+ FAIL=true
237
+ fi
238
+ fi
239
+
240
+ # POST-CHECK C9b: Avoid Tailwind dark: prefix — SmartStack uses .dark class on root (BLOCKING)
241
+ ALL_PAGES=$(find src/pages/ src/components/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
242
+ if [ -n "$ALL_PAGES" ]; then
243
+ DARK_PREFIX=$(grep -PnH '\bdark:(bg|text|border)-' $ALL_PAGES 2>/dev/null || true)
244
+ if [ -n "$DARK_PREFIX" ]; then
245
+ echo "BLOCKING (C9b): Tailwind 'dark:' prefix detected — incompatible with SmartStack theme system."
246
+ echo "SmartStack toggles a '.dark' class on <html> ; CSS vars (var(--bg-*), var(--text-*)) adapt automatically."
247
+ echo "Fix: remove 'dark:' prefix and rely on CSS vars instead."
248
+ echo "$DARK_PREFIX"
249
+ FAIL=true
250
+ fi
251
+ fi
252
+
253
+ # POST-CHECK C9c: No hex colors in className (BLOCKING)
254
+ ALL_PAGES=$(find src/pages/ src/components/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
255
+ if [ -n "$ALL_PAGES" ]; then
256
+ HEX_CN=$(grep -PnH 'className=[^>]*\[(#[0-9a-fA-F]{3,8})\]' $ALL_PAGES 2>/dev/null || true)
257
+ if [ -n "$HEX_CN" ]; then
258
+ echo "BLOCKING (C9c): Hex colors in className detected — must use CSS vars."
259
+ echo "Fix: replace #xxxxxx by var(--xxx) from the SmartStack token set."
260
+ echo "$HEX_CN"
261
+ FAIL=true
216
262
  fi
217
263
  fi
218
264
 
@@ -324,6 +370,46 @@ if [ -n "$TSX_PAGES" ] && [ -z "$DOC_DATA" ]; then
324
370
  echo "The DocToggleButton in page headers will link to this documentation"
325
371
  fi
326
372
 
373
+ # POST-CHECK C28: i18n namespace consistency across the 4 languages (BLOCKING)
374
+ # Each language directory must have the same set of JSON namespaces.
375
+ # Reference language is the one with the most files (usually fr or en).
376
+ I18N_BASE=$(find src/i18n/locales -maxdepth 1 -type d 2>/dev/null | grep -E "/(fr|en|it|de)$" || true)
377
+ if [ -n "$I18N_BASE" ]; then
378
+ # Build sorted file lists per language (basename only, .json suffix)
379
+ FR_FILES=$(find src/i18n/locales/fr -maxdepth 1 -name "*.json" -exec basename {} \; 2>/dev/null | sort -u || true)
380
+ EN_FILES=$(find src/i18n/locales/en -maxdepth 1 -name "*.json" -exec basename {} \; 2>/dev/null | sort -u || true)
381
+ IT_FILES=$(find src/i18n/locales/it -maxdepth 1 -name "*.json" -exec basename {} \; 2>/dev/null | sort -u || true)
382
+ DE_FILES=$(find src/i18n/locales/de -maxdepth 1 -name "*.json" -exec basename {} \; 2>/dev/null | sort -u || true)
383
+
384
+ # Pick the largest set as the canonical reference (excludes *_tmp.json which is intentionally orphan)
385
+ REF_FILES=$(echo -e "$FR_FILES\n$EN_FILES\n$IT_FILES\n$DE_FILES" | grep -v "_tmp\." | sort -u)
386
+
387
+ if [ -n "$REF_FILES" ]; then
388
+ FAIL_C28=false
389
+ for LANG in fr en it de; do
390
+ LANG_FILES=$(find src/i18n/locales/$LANG -maxdepth 1 -name "*.json" -exec basename {} \; 2>/dev/null | grep -v "_tmp\." | sort -u || true)
391
+ MISSING=$(comm -23 <(echo "$REF_FILES") <(echo "$LANG_FILES") || true)
392
+ if [ -n "$MISSING" ]; then
393
+ echo "BLOCKING (C28): i18n language '$LANG' is missing namespaces present in other languages:"
394
+ echo "$MISSING" | sed 's/^/ - /'
395
+ echo " Fix: create the missing JSON file(s) in src/i18n/locales/$LANG/ — even a stub {} is better than the runtime crash on a missing namespace."
396
+ FAIL_C28=true
397
+ fi
398
+ done
399
+
400
+ # Detect orphan _tmp.json files (intentional but should be cleaned up)
401
+ ORPHAN=$(find src/i18n/locales -name "*_tmp.json" 2>/dev/null || true)
402
+ if [ -n "$ORPHAN" ]; then
403
+ echo "WARNING (C28): orphan *_tmp.json file(s) detected — clean up after migration:"
404
+ echo "$ORPHAN" | sed 's/^/ - /'
405
+ fi
406
+
407
+ if [ "$FAIL_C28" = true ]; then
408
+ FAIL=true
409
+ fi
410
+ fi
411
+ fi
412
+
327
413
  # POST-CHECK C36: Frontend navigate() calls must have matching route definitions (CRITICAL)
328
414
  PAGE_FILES=$(find web/ -name "*.tsx" -path "*/pages/*" ! -name "*.test.tsx" 2>/dev/null || true)
329
415
  if [ -n "$PAGE_FILES" ]; then
@@ -571,6 +571,40 @@ if [ -n "$SEED_NAV_FILES" ]; then
571
571
  done
572
572
  fi
573
573
 
574
+ # POST-CHECK C66: ApplicationZone enum removed in v3.46 (BLOCKING)
575
+ # Detects any leftover reference to the removed ApplicationZone enum or Zone column.
576
+ SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
577
+ DOMAIN_FILES=$(find src/ -path "*/Domain/Navigation/*" -name "*.cs" 2>/dev/null)
578
+ INFRA_FILES=$(find src/ -path "*/Infrastructure/Persistence/Configurations/Navigation/*" -name "*.cs" 2>/dev/null)
579
+ APP_FILES=$(find src/ -path "*/Application/Navigation/*" -name "*.cs" 2>/dev/null)
580
+ ALL_NAV_FILES="$SEED_FILES $DOMAIN_FILES $INFRA_FILES $APP_FILES"
581
+ if [ -n "$ALL_NAV_FILES" ]; then
582
+ BAD_ZONE=$(grep -Pn 'ApplicationZone\.\w+|public\s+ApplicationZone\s+Zone|HasColumnName\("Zone"\)|Zone\s*=\s*ApplicationZone' $ALL_NAV_FILES 2>/dev/null || true)
583
+ if [ -n "$BAD_ZONE" ]; then
584
+ echo "BLOCKING (C66): ApplicationZone enum has been removed in SmartStack v3.46."
585
+ echo "Replace by IsPersonal (bool, true => myspace scope) and/or IsOpen (bool, true => bypass permissions)."
586
+ echo "$BAD_ZONE"
587
+ echo "Fix: NavigationApplication.Create(code, label, ..., isOpen: false, isPersonal: false)"
588
+ echo "Reference: templates/skills/apex/references/core-seed-data.md (v3.46+ section)"
589
+ FAIL=true
590
+ fi
591
+ fi
592
+
593
+ # POST-CHECK C67: Legacy domain-specific layouts removed in v3.46 (BLOCKING)
594
+ # AdminLayout / BusinessLayout / UserLayout / HRLayout / SalesLayout no longer exist.
595
+ TSX_FILES=$(find web/ -name "*.tsx" -not -path "*/node_modules/*" 2>/dev/null; find src/ -name "*.tsx" -not -path "*/node_modules/*" 2>/dev/null)
596
+ if [ -n "$TSX_FILES" ]; then
597
+ BAD_LAYOUT=$(grep -PnH '<\s*(AdminLayout|BusinessLayout|UserLayout|HRLayout|SalesLayout)\b|from\s+["'\''][^"'\'']*\/(AdminLayout|BusinessLayout|UserLayout|HRLayout|SalesLayout)["'\'']' $TSX_FILES 2>/dev/null || true)
598
+ if [ -n "$BAD_LAYOUT" ]; then
599
+ echo "BLOCKING (C67): Legacy layouts (AdminLayout/BusinessLayout/UserLayout/HRLayout/SalesLayout) have been removed in v3.46."
600
+ echo "Use AppLayout — the sole shell for authenticated application routes."
601
+ echo "$BAD_LAYOUT"
602
+ echo "Fix: Replace by <AppLayout /> (no domain-specific shell anymore)."
603
+ echo "Reference: templates/skills/application/references/frontend-route-naming.md"
604
+ FAIL=true
605
+ fi
606
+ fi
607
+
574
608
  if [ "$FAIL" = true ]; then
575
609
  exit 1
576
610
  fi
@@ -109,7 +109,6 @@ public static class NavigationApplicationSeedData
109
109
  return new NavigationApplicationSeedEntry
110
110
  {
111
111
  Id = ApplicationId,
112
- Zone = ApplicationZone.Business,
113
112
  Code = "{appCode}",
114
113
  Label = "{appLabel_en}",
115
114
  Description = "{appDesc_en}",
@@ -117,7 +116,9 @@ public static class NavigationApplicationSeedData
117
116
  IconType = IconType.Lucide,
118
117
  Route = ToKebabCase("/{appCode}"),
119
118
  DisplayOrder = 1,
120
- IsActive = true
119
+ IsActive = true,
120
+ IsOpen = false, // true => bypass permission checks (system apps only)
121
+ IsPersonal = false // true => belongs to user personal scope (myspace)
121
122
  };
122
123
  }
123
124
 
@@ -199,7 +200,6 @@ public static class NavigationApplicationSeedData
199
200
  public class NavigationApplicationSeedEntry
200
201
  {
201
202
  public Guid Id { get; init; }
202
- public ApplicationZone Zone { get; init; }
203
203
  public string Code { get; init; } = null!;
204
204
  public string Label { get; init; } = null!;
205
205
  public string? Description { get; init; }
@@ -208,6 +208,9 @@ public class NavigationApplicationSeedEntry
208
208
  public string? Route { get; init; }
209
209
  public int DisplayOrder { get; init; }
210
210
  public bool IsActive { get; init; }
211
+ // v3.46+ : ApplicationZone enum removed. Replaced by 2 boolean flags.
212
+ public bool IsOpen { get; init; } // true => app accessible without permission checks
213
+ public bool IsPersonal { get; init; } // true => app in user personal scope (myspace)
211
214
  }
212
215
  ```
213
216
 
@@ -1369,7 +1372,7 @@ Before marking the task as completed, verify ALL:
1369
1372
  **Application-Level (FIRST — before modules):**
1370
1373
  - [ ] `NavigationApplicationSeedData.cs` created (once per application, at `Infrastructure/Persistence/Seeding/Data/`)
1371
1374
  - [ ] Application GUID is random (`Guid.NewGuid()`) — FK resolution is by Code lookup, not fixed ID
1372
- - [ ] GetApplicationEntry() takes no parameters, includes `Zone = ApplicationZone.Business`
1375
+ - [ ] GetApplicationEntry() takes no parameters, includes `IsOpen = false` and `IsPersonal = false` (set `IsPersonal = true` only for myspace-scoped apps; set `IsOpen = true` only for public/system apps that should bypass permission checks). v3.46+ : `ApplicationZone` enum is removed — do NOT add `Zone = ...`
1373
1376
  - [ ] Application translations created (4 languages: fr, en, it, de, EntityType = Application), using `app.Id` (actual DB ID) for EntityId
1374
1377
  - [ ] `IClientSeedDataProvider.SeedNavigationAsync()` uses `NavigationApplicationSeedData` (NO hardcoded `{appLabel_en}` / `{appIcon}` placeholders)
1375
1378
  - [ ] `ApplicationRolesSeedData.ApplicationId` references `NavigationApplicationSeedData.ApplicationId` (DTO only — provider code resolves from DB by Code)
@@ -0,0 +1,45 @@
1
+ # Domain Events (lightweight pub-sub)
2
+
3
+ > Extracted from `smartstack-api.md` for clarity. Loaded only when generating code that needs events.
4
+
5
+ SmartStack uses a minimal domain event pattern in `SmartStack.Domain.Support.Events`.
6
+
7
+ ## Interface + base class
8
+
9
+ ```csharp
10
+ public interface IDomainEvent
11
+ {
12
+ DateTime OccurredAt { get; }
13
+ }
14
+
15
+ public abstract class DomainEvent : IDomainEvent
16
+ {
17
+ public DateTime OccurredAt { get; } = DateTime.UtcNow;
18
+ }
19
+ ```
20
+
21
+ ## When to raise an event vs use a hook
22
+
23
+ | Use case | Mechanism |
24
+ |---|---|
25
+ | Cross-cutting side effect tied to entity lifecycle (Create/Update/Delete) | Entity hook (`IAfterCreate<T>`, …) — see [entity-hooks-pattern.md](entity-hooks-pattern.md) |
26
+ | Business event independent of CRUD (e.g. `WorkflowExecuted`, `LicenseRenewed`) | Domain event |
27
+ | Need to handle multiple unrelated reactions to the same business fact | Domain event |
28
+ | Reaction must be transactional with the entity write | Neither — code it inline in the handler |
29
+
30
+ ## Convention
31
+
32
+ - One event class per business fact, named `{PastTense}Event` (e.g. `EmployeeOnboardedEvent`, `WorkflowExecutedEvent`).
33
+ - Place events in `Domain/{Aggregate}/Events/`.
34
+ - Inherit `DomainEvent` (gets `OccurredAt` for free).
35
+ - Dispatched by handlers (no automatic publication from `SaveChangesAsync` in v3.46) — keep dispatch explicit so the handler controls timing.
36
+
37
+ ## Example dispatch (handler)
38
+
39
+ ```csharp
40
+ await _mediator.Publish(new EmployeeOnboardedEvent(employee.Id, employee.TenantId), ct);
41
+ ```
42
+
43
+ ## Source
44
+
45
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Domain/Support/Events/IDomainEvent.cs`
@@ -0,0 +1,68 @@
1
+ # Entity Lifecycle Hooks (v3.46+)
2
+
3
+ > Extracted from `smartstack-api.md` for clarity. Loaded only when generating code that needs hooks.
4
+
5
+ SmartStack exposes 6 typed hook interfaces + 1 executor in `SmartStack.Application.Common.Interfaces.Hooks`.
6
+
7
+ ## Interfaces
8
+
9
+ ```csharp
10
+ public interface IBeforeCreate<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
11
+ public interface IAfterCreate<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
12
+ public interface IBeforeUpdate<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
13
+ public interface IAfterUpdate<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
14
+ public interface IBeforeDelete<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
15
+ public interface IAfterDelete<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
16
+
17
+ public interface IHookExecutor
18
+ {
19
+ Task ExecuteBeforeCreateAsync<T>(T entity, CancellationToken ct = default) where T : class;
20
+ Task ExecuteAfterCreateAsync<T>(T entity, CancellationToken ct = default) where T : class;
21
+ Task ExecuteBeforeUpdateAsync<T>(T entity, CancellationToken ct = default) where T : class;
22
+ Task ExecuteAfterUpdateAsync<T>(T entity, CancellationToken ct = default) where T : class;
23
+ Task ExecuteBeforeDeleteAsync<T>(T entity, CancellationToken ct = default) where T : class;
24
+ Task ExecuteAfterDeleteAsync<T>(T entity, CancellationToken ct = default) where T : class;
25
+ }
26
+ ```
27
+
28
+ ## Semantics
29
+
30
+ - **`IBefore*`** runs **before** the entity is persisted. May transform the entity. May `throw` to cancel the operation (the calling handler does NOT call `SaveChangesAsync`).
31
+ - **`IAfter*`** runs **after** `SaveChangesAsync` completes. Side-effects only (notifications, indexing, caches, integrations). Throwing here does NOT roll back the DB write — wrap risky calls.
32
+ - **`Order`** (default 0) — lower runs earlier. Use `Order = -100` to pre-empt validation, `Order = 100` to run last.
33
+
34
+ ## DI registration (Infrastructure DependencyInjection.cs)
35
+
36
+ ```csharp
37
+ services.AddScoped<IHookExecutor, HookExecutor>();
38
+ services.AddScoped<IBeforeCreate<Employee>, EmployeeValidationHook>();
39
+ services.AddScoped<IAfterCreate<Employee>, EmployeeWelcomeNotificationHook>();
40
+ services.AddScoped<IAfterUpdate<Employee>, EmployeeUpdatedAuditHook>();
41
+ ```
42
+
43
+ ## Usage in service / handler
44
+
45
+ ```csharp
46
+ public async Task<EmployeeDto> CreateAsync(CreateEmployeeDto dto, CancellationToken ct)
47
+ {
48
+ var entity = Employee.Create(dto.TenantId, dto.Code, dto.Name);
49
+ await _hooks.ExecuteBeforeCreateAsync(entity, ct); // may throw → cancel
50
+ _db.Employees.Add(entity);
51
+ await _db.SaveChangesAsync(ct);
52
+ await _hooks.ExecuteAfterCreateAsync(entity, ct); // post-commit side effects
53
+ return _mapper.Map<EmployeeDto>(entity);
54
+ }
55
+ ```
56
+
57
+ ## DO / DON'T
58
+
59
+ - DO use hooks for cross-cutting concerns (notifications, audit beyond `IAuditableEntity`, cache invalidation)
60
+ - DO keep hooks small and idempotent — `IAfter*` runs without DB transaction
61
+ - DON'T use hooks for business validation that varies per command — keep that in the handler / FluentValidation
62
+ - DON'T register the same hook twice — DI picks all of them, and they all execute
63
+
64
+ ## Sources
65
+
66
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Application/Common/Interfaces/Hooks/IBeforeCreate.cs`
67
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Application/Common/Interfaces/Hooks/IAfterCreate.cs`
68
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Application/Common/Interfaces/Hooks/IHookExecutor.cs` (and 4 more)