@atlashub/smartstack-cli 1.37.0 → 2.1.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/.documentation/agents.html +147 -40
- package/.documentation/apex.html +1 -1
- package/.documentation/business-analyse.html +3 -3
- package/.documentation/cli-commands.html +2 -2
- package/.documentation/commands.html +14 -14
- package/.documentation/efcore.html +14 -14
- package/.documentation/gitflow.html +12 -12
- package/.documentation/hooks.html +41 -3
- package/.documentation/index.html +1 -1
- package/.documentation/init.html +2 -2
- package/.documentation/installation.html +11 -11
- package/.documentation/js/app.js +1 -1
- package/.documentation/ralph-loop.html +1 -1
- package/.documentation/test-web.html +4 -4
- package/config/mcp-defaults.json +62 -0
- package/dist/index.js +58 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +70010 -0
- package/dist/mcp-entry.mjs.map +1 -0
- package/package.json +14 -5
- package/templates/agents/gitflow/start.md +5 -4
- package/templates/agents/mcp-healthcheck.md +15 -13
- package/templates/mcp-scaffolding/component.tsx.hbs +298 -0
- package/templates/mcp-scaffolding/controller.cs.hbs +184 -0
- package/templates/mcp-scaffolding/entity-extension.cs.hbs +231 -0
- package/templates/mcp-scaffolding/frontend/api-client.ts.hbs +116 -0
- package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +133 -0
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +134 -0
- package/templates/mcp-scaffolding/migrations/seed-roles.cs.hbs +261 -0
- package/templates/mcp-scaffolding/service-extension.cs.hbs +53 -0
- package/templates/mcp-scaffolding/tests/controller.test.cs.hbs +413 -0
- package/templates/mcp-scaffolding/tests/entity.test.cs.hbs +239 -0
- package/templates/mcp-scaffolding/tests/repository.test.cs.hbs +441 -0
- package/templates/mcp-scaffolding/tests/security.test.cs.hbs +442 -0
- package/templates/mcp-scaffolding/tests/service.test.cs.hbs +390 -0
- package/templates/mcp-scaffolding/tests/validator.test.cs.hbs +428 -0
- package/templates/ralph/README.md +3 -3
- package/templates/ralph/ralph.config.yaml +2 -2
- package/templates/skills/admin/SKILL.md +42 -0
- package/templates/skills/business-analyse/_shared.md +79 -15
- package/templates/skills/business-analyse/questionnaire/01-context.md +4 -4
- package/templates/skills/business-analyse/questionnaire/02-stakeholders.md +3 -3
- package/templates/skills/business-analyse/questionnaire/03-scope.md +4 -4
- package/templates/skills/business-analyse/questionnaire/04-data.md +7 -7
- package/templates/skills/business-analyse/questionnaire/05-integrations.md +1 -1
- package/templates/skills/business-analyse/questionnaire/06-security.md +3 -3
- package/templates/skills/business-analyse/questionnaire/07-ui.md +1 -1
- package/templates/skills/business-analyse/questionnaire/08-performance.md +3 -3
- package/templates/skills/business-analyse/questionnaire/09-constraints.md +4 -4
- package/templates/skills/business-analyse/questionnaire/10-documentation.md +2 -2
- package/templates/skills/business-analyse/questionnaire/11-data-lifecycle.md +2 -2
- package/templates/skills/business-analyse/questionnaire/12-migration.md +1 -1
- package/templates/skills/business-analyse/questionnaire/13-cross-module.md +2 -2
- package/templates/skills/business-analyse/steps/step-01-discover.md +50 -25
- package/templates/skills/business-analyse/steps/step-03-specify.md +63 -0
- package/templates/skills/business-analyse/steps/step-04-validate.md +23 -1
- package/templates/skills/business-analyse/steps/step-05-handoff.md +248 -66
- package/templates/skills/business-analyse/templates/tpl-handoff.md +99 -23
- package/templates/skills/cc-agent/SKILL.md +129 -0
- package/templates/skills/cc-agent/references/agent-frontmatter.md +213 -0
- package/templates/skills/cc-agent/references/permission-modes.md +102 -0
- package/templates/skills/cc-agent/references/tools-reference.md +144 -0
- package/templates/skills/cc-agent/steps/step-00-init.md +134 -0
- package/templates/skills/cc-agent/steps/step-01-design.md +186 -0
- package/templates/skills/cc-agent/steps/step-02-generate.md +204 -0
- package/templates/skills/cc-agent/steps/step-03-validate.md +130 -0
- package/templates/skills/cc-agent/templates/agent-categorized.md +67 -0
- package/templates/skills/cc-agent/templates/agent-standalone.md +56 -0
- package/templates/skills/cc-agent/templates/agent-with-skills.md +94 -0
- package/templates/skills/cc-audit/SKILL.md +108 -0
- package/templates/skills/cc-audit/references/agent-checklist.md +91 -0
- package/templates/skills/cc-audit/references/hook-checklist.md +110 -0
- package/templates/skills/cc-audit/references/skill-checklist.md +70 -0
- package/templates/skills/cc-audit/steps/step-00-init.md +98 -0
- package/templates/skills/cc-audit/steps/step-01-scan.md +142 -0
- package/templates/skills/cc-audit/steps/step-02-analyze.md +158 -0
- package/templates/skills/cc-audit/steps/step-03-report.md +142 -0
- package/templates/skills/cc-skill/SKILL.md +134 -0
- package/templates/skills/cc-skill/references/best-practices.md +167 -0
- package/templates/skills/cc-skill/references/frontmatter-reference.md +182 -0
- package/templates/skills/cc-skill/references/skill-patterns.md +199 -0
- package/templates/skills/cc-skill/steps/step-00-init.md +119 -0
- package/templates/skills/cc-skill/steps/step-01-design.md +199 -0
- package/templates/skills/cc-skill/steps/step-02-generate.md +145 -0
- package/templates/skills/cc-skill/steps/step-03-steps.md +151 -0
- package/templates/skills/cc-skill/steps/step-04-validate.md +124 -0
- package/templates/skills/cc-skill/templates/skill-forked.md +85 -0
- package/templates/skills/cc-skill/templates/skill-progressive.md +102 -0
- package/templates/skills/cc-skill/templates/skill-simple.md +75 -0
- package/templates/skills/cc-skill/templates/step-template.md +82 -0
- package/templates/skills/check-version/SKILL.md +6 -0
- package/templates/skills/controller/templates.md +82 -0
- package/templates/skills/debug/SKILL.md +4 -0
- package/templates/skills/documentation/SKILL.md +1 -0
- package/templates/skills/efcore/SKILL.md +5 -0
- package/templates/skills/efcore/references/zero-downtime-patterns.md +227 -0
- package/templates/skills/efcore/steps/db/step-deploy.md +26 -5
- package/templates/skills/efcore/steps/migration/step-03-validate.md +19 -0
- package/templates/skills/efcore/steps/shared/step-00-init.md +21 -7
- package/templates/skills/explore/SKILL.md +28 -32
- package/templates/skills/feature-full/SKILL.md +1 -0
- package/templates/skills/gitflow/SKILL.md +8 -0
- package/templates/skills/gitflow/steps/step-start.md +45 -10
- package/templates/skills/mcp/SKILL.md +38 -18
- package/templates/skills/quick-search/SKILL.md +8 -1
- package/templates/skills/ralph-loop/SKILL.md +1 -1
- package/templates/skills/ralph-loop/steps/step-00-init.md +8 -68
- package/templates/skills/ralph-loop/steps/step-04-check.md +1 -1
- package/templates/skills/refactor/SKILL.md +1 -0
- package/templates/skills/review-code/SKILL.md +11 -3
- package/templates/skills/review-code/references/owasp-api-top10.md +243 -0
- package/templates/skills/review-code/references/security-checklist.md +86 -1
- package/templates/skills/review-code/references/smartstack-conventions.md +166 -0
- package/templates/skills/ui-components/SKILL.md +31 -438
- package/templates/skills/ui-components/accessibility.md +170 -0
- package/templates/skills/ui-components/patterns/data-table.md +39 -0
- package/templates/skills/ui-components/patterns/entity-card.md +77 -0
- package/templates/skills/ui-components/patterns/grid-layout.md +91 -0
- package/templates/skills/ui-components/patterns/kanban.md +43 -0
- package/templates/skills/ui-components/style-guide.md +86 -0
- package/templates/skills/utils/SKILL.md +1 -0
- package/templates/skills/validate/SKILL.md +1 -0
- package/templates/skills/workflow/SKILL.md +27 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
{{!-- SmartStack Validator Test Template --}}
|
|
2
|
+
{{!-- Generates unit tests for FluentValidation validators following SmartStack conventions --}}
|
|
3
|
+
|
|
4
|
+
using FluentAssertions;
|
|
5
|
+
using FluentValidation.TestHelper;
|
|
6
|
+
using Xunit;
|
|
7
|
+
using {{namespace}}.Application.DTOs;
|
|
8
|
+
using {{namespace}}.Application.Validators;
|
|
9
|
+
|
|
10
|
+
namespace {{namespace}}.Tests.Unit.Validators;
|
|
11
|
+
|
|
12
|
+
/// <summary>
|
|
13
|
+
/// Unit tests for <see cref="{{name}}Validator"/>.
|
|
14
|
+
/// Follows SmartStack testing conventions: {Method}_When{Condition}_Should{Result}
|
|
15
|
+
/// </summary>
|
|
16
|
+
public class {{name}}ValidatorTests
|
|
17
|
+
{
|
|
18
|
+
private readonly Create{{name}}RequestValidator _createValidator;
|
|
19
|
+
private readonly Update{{name}}RequestValidator _updateValidator;
|
|
20
|
+
|
|
21
|
+
public {{name}}ValidatorTests()
|
|
22
|
+
{
|
|
23
|
+
_createValidator = new Create{{name}}RequestValidator();
|
|
24
|
+
_updateValidator = new Update{{name}}RequestValidator();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#region Create Request - Code Validation
|
|
28
|
+
|
|
29
|
+
[Fact]
|
|
30
|
+
public void CreateValidator_WhenCodeIsValid_ShouldNotHaveErrors()
|
|
31
|
+
{
|
|
32
|
+
// Arrange
|
|
33
|
+
var request = new Create{{name}}Request { Code = "VALID-001" };
|
|
34
|
+
|
|
35
|
+
// Act
|
|
36
|
+
var result = _createValidator.TestValidate(request);
|
|
37
|
+
|
|
38
|
+
// Assert
|
|
39
|
+
result.ShouldNotHaveValidationErrorFor(x => x.Code);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[Theory]
|
|
43
|
+
[InlineData(null)]
|
|
44
|
+
[InlineData("")]
|
|
45
|
+
[InlineData(" ")]
|
|
46
|
+
public void CreateValidator_WhenCodeIsNullOrEmpty_ShouldHaveError(string? invalidCode)
|
|
47
|
+
{
|
|
48
|
+
// Arrange
|
|
49
|
+
var request = new Create{{name}}Request { Code = invalidCode! };
|
|
50
|
+
|
|
51
|
+
// Act
|
|
52
|
+
var result = _createValidator.TestValidate(request);
|
|
53
|
+
|
|
54
|
+
// Assert
|
|
55
|
+
result.ShouldHaveValidationErrorFor(x => x.Code)
|
|
56
|
+
.WithErrorMessage("*required*");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
[Fact]
|
|
60
|
+
public void CreateValidator_WhenCodeExceedsMaxLength_ShouldHaveError()
|
|
61
|
+
{
|
|
62
|
+
// Arrange
|
|
63
|
+
var request = new Create{{name}}Request
|
|
64
|
+
{
|
|
65
|
+
Code = new string('A', 101) // Exceeds 100 char limit
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Act
|
|
69
|
+
var result = _createValidator.TestValidate(request);
|
|
70
|
+
|
|
71
|
+
// Assert
|
|
72
|
+
result.ShouldHaveValidationErrorFor(x => x.Code)
|
|
73
|
+
.WithErrorMessage("*100 characters*");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
[Theory]
|
|
77
|
+
[InlineData("valid-code")]
|
|
78
|
+
[InlineData("VALID_CODE_123")]
|
|
79
|
+
[InlineData("Code.With.Dots")]
|
|
80
|
+
public void CreateValidator_WhenCodeHasValidFormat_ShouldNotHaveErrors(string validCode)
|
|
81
|
+
{
|
|
82
|
+
// Arrange
|
|
83
|
+
var request = new Create{{name}}Request { Code = validCode };
|
|
84
|
+
|
|
85
|
+
// Act
|
|
86
|
+
var result = _createValidator.TestValidate(request);
|
|
87
|
+
|
|
88
|
+
// Assert
|
|
89
|
+
result.ShouldNotHaveValidationErrorFor(x => x.Code);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
[Theory]
|
|
93
|
+
[InlineData("code with spaces")]
|
|
94
|
+
[InlineData("code<script>")]
|
|
95
|
+
[InlineData("code;drop")]
|
|
96
|
+
public void CreateValidator_WhenCodeHasInvalidCharacters_ShouldHaveError(string invalidCode)
|
|
97
|
+
{
|
|
98
|
+
// Arrange
|
|
99
|
+
var request = new Create{{name}}Request { Code = invalidCode };
|
|
100
|
+
|
|
101
|
+
// Act
|
|
102
|
+
var result = _createValidator.TestValidate(request);
|
|
103
|
+
|
|
104
|
+
// Assert
|
|
105
|
+
result.ShouldHaveValidationErrorFor(x => x.Code)
|
|
106
|
+
.WithErrorMessage("*invalid characters*");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#endregion
|
|
110
|
+
|
|
111
|
+
#region Create Request - Name Validation
|
|
112
|
+
|
|
113
|
+
[Fact]
|
|
114
|
+
public void CreateValidator_WhenNameIsValid_ShouldNotHaveErrors()
|
|
115
|
+
{
|
|
116
|
+
// Arrange
|
|
117
|
+
var request = new Create{{name}}Request
|
|
118
|
+
{
|
|
119
|
+
Code = "TEST",
|
|
120
|
+
Name = "Valid Name"
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Act
|
|
124
|
+
var result = _createValidator.TestValidate(request);
|
|
125
|
+
|
|
126
|
+
// Assert
|
|
127
|
+
result.ShouldNotHaveValidationErrorFor(x => x.Name);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[Fact]
|
|
131
|
+
public void CreateValidator_WhenNameExceedsMaxLength_ShouldHaveError()
|
|
132
|
+
{
|
|
133
|
+
// Arrange
|
|
134
|
+
var request = new Create{{name}}Request
|
|
135
|
+
{
|
|
136
|
+
Code = "TEST",
|
|
137
|
+
Name = new string('A', 256) // Exceeds 255 char limit
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Act
|
|
141
|
+
var result = _createValidator.TestValidate(request);
|
|
142
|
+
|
|
143
|
+
// Assert
|
|
144
|
+
result.ShouldHaveValidationErrorFor(x => x.Name)
|
|
145
|
+
.WithErrorMessage("*255 characters*");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#endregion
|
|
149
|
+
|
|
150
|
+
#region Update Request Validation
|
|
151
|
+
|
|
152
|
+
[Fact]
|
|
153
|
+
public void UpdateValidator_WhenValidData_ShouldNotHaveErrors()
|
|
154
|
+
{
|
|
155
|
+
// Arrange
|
|
156
|
+
var request = new Update{{name}}Request
|
|
157
|
+
{
|
|
158
|
+
Name = "Updated Name",
|
|
159
|
+
// Add other updatable properties
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Act
|
|
163
|
+
var result = _updateValidator.TestValidate(request);
|
|
164
|
+
|
|
165
|
+
// Assert
|
|
166
|
+
result.ShouldNotHaveAnyValidationErrors();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
[Fact]
|
|
170
|
+
public void UpdateValidator_WhenOptionalFieldsAreNull_ShouldNotHaveErrors()
|
|
171
|
+
{
|
|
172
|
+
// Arrange
|
|
173
|
+
var request = new Update{{name}}Request
|
|
174
|
+
{
|
|
175
|
+
Name = null, // Optional field can be null
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Act
|
|
179
|
+
var result = _updateValidator.TestValidate(request);
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
result.ShouldNotHaveAnyValidationErrors();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#endregion
|
|
186
|
+
|
|
187
|
+
#region Date Validation (if applicable)
|
|
188
|
+
|
|
189
|
+
[Fact]
|
|
190
|
+
public void CreateValidator_WhenStartDateIsInPast_ShouldHaveError()
|
|
191
|
+
{
|
|
192
|
+
// Arrange
|
|
193
|
+
var request = new Create{{name}}Request
|
|
194
|
+
{
|
|
195
|
+
Code = "TEST",
|
|
196
|
+
StartDate = DateTime.UtcNow.AddDays(-1)
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Act
|
|
200
|
+
var result = _createValidator.TestValidate(request);
|
|
201
|
+
|
|
202
|
+
// Assert
|
|
203
|
+
result.ShouldHaveValidationErrorFor(x => x.StartDate)
|
|
204
|
+
.WithErrorMessage("*future*");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
[Fact]
|
|
208
|
+
public void CreateValidator_WhenEndDateBeforeStartDate_ShouldHaveError()
|
|
209
|
+
{
|
|
210
|
+
// Arrange
|
|
211
|
+
var request = new Create{{name}}Request
|
|
212
|
+
{
|
|
213
|
+
Code = "TEST",
|
|
214
|
+
StartDate = DateTime.UtcNow.AddDays(10),
|
|
215
|
+
EndDate = DateTime.UtcNow.AddDays(5)
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Act
|
|
219
|
+
var result = _createValidator.TestValidate(request);
|
|
220
|
+
|
|
221
|
+
// Assert
|
|
222
|
+
result.ShouldHaveValidationErrorFor(x => x.EndDate)
|
|
223
|
+
.WithErrorMessage("*after start date*");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#endregion
|
|
227
|
+
|
|
228
|
+
#region Numeric Validation (if applicable)
|
|
229
|
+
|
|
230
|
+
[Theory]
|
|
231
|
+
[InlineData(-1)]
|
|
232
|
+
[InlineData(-100)]
|
|
233
|
+
public void CreateValidator_WhenQuantityIsNegative_ShouldHaveError(int invalidQuantity)
|
|
234
|
+
{
|
|
235
|
+
// Arrange
|
|
236
|
+
var request = new Create{{name}}Request
|
|
237
|
+
{
|
|
238
|
+
Code = "TEST",
|
|
239
|
+
Quantity = invalidQuantity
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Act
|
|
243
|
+
var result = _createValidator.TestValidate(request);
|
|
244
|
+
|
|
245
|
+
// Assert
|
|
246
|
+
result.ShouldHaveValidationErrorFor(x => x.Quantity)
|
|
247
|
+
.WithErrorMessage("*greater than*0*");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
[Theory]
|
|
251
|
+
[InlineData(0)]
|
|
252
|
+
[InlineData(1)]
|
|
253
|
+
[InlineData(1000)]
|
|
254
|
+
public void CreateValidator_WhenQuantityIsValid_ShouldNotHaveErrors(int validQuantity)
|
|
255
|
+
{
|
|
256
|
+
// Arrange
|
|
257
|
+
var request = new Create{{name}}Request
|
|
258
|
+
{
|
|
259
|
+
Code = "TEST",
|
|
260
|
+
Quantity = validQuantity
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Act
|
|
264
|
+
var result = _createValidator.TestValidate(request);
|
|
265
|
+
|
|
266
|
+
// Assert
|
|
267
|
+
result.ShouldNotHaveValidationErrorFor(x => x.Quantity);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#endregion
|
|
271
|
+
|
|
272
|
+
#region Email Validation (if applicable)
|
|
273
|
+
|
|
274
|
+
[Theory]
|
|
275
|
+
[InlineData("valid@example.com")]
|
|
276
|
+
[InlineData("user.name@domain.org")]
|
|
277
|
+
[InlineData("user+tag@example.co.uk")]
|
|
278
|
+
public void CreateValidator_WhenEmailIsValid_ShouldNotHaveErrors(string validEmail)
|
|
279
|
+
{
|
|
280
|
+
// Arrange
|
|
281
|
+
var request = new Create{{name}}Request
|
|
282
|
+
{
|
|
283
|
+
Code = "TEST",
|
|
284
|
+
Email = validEmail
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Act
|
|
288
|
+
var result = _createValidator.TestValidate(request);
|
|
289
|
+
|
|
290
|
+
// Assert
|
|
291
|
+
result.ShouldNotHaveValidationErrorFor(x => x.Email);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
[Theory]
|
|
295
|
+
[InlineData("invalid")]
|
|
296
|
+
[InlineData("invalid@")]
|
|
297
|
+
[InlineData("@invalid.com")]
|
|
298
|
+
[InlineData("invalid@.com")]
|
|
299
|
+
public void CreateValidator_WhenEmailIsInvalid_ShouldHaveError(string invalidEmail)
|
|
300
|
+
{
|
|
301
|
+
// Arrange
|
|
302
|
+
var request = new Create{{name}}Request
|
|
303
|
+
{
|
|
304
|
+
Code = "TEST",
|
|
305
|
+
Email = invalidEmail
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Act
|
|
309
|
+
var result = _createValidator.TestValidate(request);
|
|
310
|
+
|
|
311
|
+
// Assert
|
|
312
|
+
result.ShouldHaveValidationErrorFor(x => x.Email)
|
|
313
|
+
.WithErrorMessage("*valid email*");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#endregion
|
|
317
|
+
|
|
318
|
+
#region Collection Validation (if applicable)
|
|
319
|
+
|
|
320
|
+
[Fact]
|
|
321
|
+
public void CreateValidator_WhenItemsCollectionIsEmpty_ShouldHaveError()
|
|
322
|
+
{
|
|
323
|
+
// Arrange
|
|
324
|
+
var request = new Create{{name}}Request
|
|
325
|
+
{
|
|
326
|
+
Code = "TEST",
|
|
327
|
+
Items = new List<string>()
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Act
|
|
331
|
+
var result = _createValidator.TestValidate(request);
|
|
332
|
+
|
|
333
|
+
// Assert
|
|
334
|
+
result.ShouldHaveValidationErrorFor(x => x.Items)
|
|
335
|
+
.WithErrorMessage("*at least one item*");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
[Fact]
|
|
339
|
+
public void CreateValidator_WhenItemsExceedMaxCount_ShouldHaveError()
|
|
340
|
+
{
|
|
341
|
+
// Arrange
|
|
342
|
+
var request = new Create{{name}}Request
|
|
343
|
+
{
|
|
344
|
+
Code = "TEST",
|
|
345
|
+
Items = Enumerable.Range(1, 101).Select(i => $"Item-{i}").ToList()
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Act
|
|
349
|
+
var result = _createValidator.TestValidate(request);
|
|
350
|
+
|
|
351
|
+
// Assert
|
|
352
|
+
result.ShouldHaveValidationErrorFor(x => x.Items)
|
|
353
|
+
.WithErrorMessage("*maximum*100*");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
#endregion
|
|
357
|
+
|
|
358
|
+
#region Cross-Field Validation
|
|
359
|
+
|
|
360
|
+
[Fact]
|
|
361
|
+
public void CreateValidator_WhenDependentFieldsMissing_ShouldHaveError()
|
|
362
|
+
{
|
|
363
|
+
// Arrange
|
|
364
|
+
var request = new Create{{name}}Request
|
|
365
|
+
{
|
|
366
|
+
Code = "TEST",
|
|
367
|
+
RequiresApproval = true,
|
|
368
|
+
ApproverEmail = null // Required when RequiresApproval is true
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// Act
|
|
372
|
+
var result = _createValidator.TestValidate(request);
|
|
373
|
+
|
|
374
|
+
// Assert
|
|
375
|
+
result.ShouldHaveValidationErrorFor(x => x.ApproverEmail)
|
|
376
|
+
.WithErrorMessage("*required when*");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
#endregion
|
|
380
|
+
|
|
381
|
+
#region Security Validation
|
|
382
|
+
|
|
383
|
+
[Theory]
|
|
384
|
+
[InlineData("<script>alert('xss')</script>")]
|
|
385
|
+
[InlineData("'; DROP TABLE Users; --")]
|
|
386
|
+
[InlineData("{{'{{'}}constructor{{'}}'}}")]
|
|
387
|
+
public void CreateValidator_WhenInputContainsMaliciousContent_ShouldHaveError(string maliciousInput)
|
|
388
|
+
{
|
|
389
|
+
// Arrange
|
|
390
|
+
var request = new Create{{name}}Request
|
|
391
|
+
{
|
|
392
|
+
Code = "TEST",
|
|
393
|
+
Description = maliciousInput
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Act
|
|
397
|
+
var result = _createValidator.TestValidate(request);
|
|
398
|
+
|
|
399
|
+
// Assert
|
|
400
|
+
result.ShouldHaveValidationErrorFor(x => x.Description)
|
|
401
|
+
.WithErrorMessage("*invalid characters*");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
#endregion
|
|
405
|
+
|
|
406
|
+
#region Error Message Format
|
|
407
|
+
|
|
408
|
+
[Fact]
|
|
409
|
+
public void CreateValidator_WhenMultipleErrors_ShouldReturnAllErrors()
|
|
410
|
+
{
|
|
411
|
+
// Arrange
|
|
412
|
+
var request = new Create{{name}}Request
|
|
413
|
+
{
|
|
414
|
+
Code = "", // Invalid
|
|
415
|
+
Name = new string('A', 500), // Too long
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// Act
|
|
419
|
+
var result = _createValidator.TestValidate(request);
|
|
420
|
+
|
|
421
|
+
// Assert
|
|
422
|
+
result.Errors.Should().HaveCountGreaterThan(1);
|
|
423
|
+
result.ShouldHaveValidationErrorFor(x => x.Code);
|
|
424
|
+
result.ShouldHaveValidationErrorFor(x => x.Name);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#endregion
|
|
428
|
+
}
|
|
@@ -27,15 +27,15 @@ Edit `ralph.config.yaml` to customize:
|
|
|
27
27
|
|
|
28
28
|
Ralph **requires** these MCP servers to function:
|
|
29
29
|
|
|
30
|
-
### 1.
|
|
30
|
+
### 1. Install packages
|
|
31
31
|
```bash
|
|
32
|
-
npm install -g @atlashub/smartstack-
|
|
32
|
+
npm install -g @atlashub/smartstack-cli
|
|
33
33
|
npm install -g @upstash/context7-mcp
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
### 2. Register in Claude Code
|
|
37
37
|
```bash
|
|
38
|
-
claude mcp add smartstack -- npx @atlashub/smartstack-mcp
|
|
38
|
+
claude mcp add smartstack -- npx -p @atlashub/smartstack-cli smartstack-mcp
|
|
39
39
|
claude mcp add context7 -- npx @upstash/context7-mcp
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -13,8 +13,8 @@ mcp:
|
|
|
13
13
|
health_check: true
|
|
14
14
|
|
|
15
15
|
- name: smartstack
|
|
16
|
-
description: "SmartStack validation and scaffolding"
|
|
17
|
-
install_command: "
|
|
16
|
+
description: "SmartStack validation and scaffolding (bundled in CLI)"
|
|
17
|
+
install_command: "claude mcp add smartstack -- npx -p @atlashub/smartstack-cli smartstack-mcp"
|
|
18
18
|
health_check: true
|
|
19
19
|
|
|
20
20
|
# Behavior when MCP is unavailable
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: admin
|
|
3
|
+
description: Admin account management - reset passwords, manage local admin accounts
|
|
4
|
+
argument-hint: "<subcommand> [options]"
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<objective>
|
|
9
|
+
Manage SmartStack admin accounts. Wraps the `smartstack admin` CLI command with guided workflows.
|
|
10
|
+
|
|
11
|
+
**WARNING:** This skill modifies database records directly. Always confirm before executing.
|
|
12
|
+
</objective>
|
|
13
|
+
|
|
14
|
+
<quick_start>
|
|
15
|
+
```bash
|
|
16
|
+
/admin reset # Reset local admin password (guided)
|
|
17
|
+
/admin reset --force # Reset without confirmation
|
|
18
|
+
```
|
|
19
|
+
</quick_start>
|
|
20
|
+
|
|
21
|
+
<workflow>
|
|
22
|
+
|
|
23
|
+
## Subcommands
|
|
24
|
+
|
|
25
|
+
| Command | Description |
|
|
26
|
+
|---------|-------------|
|
|
27
|
+
| `/admin reset` | Reset the localAdmin account password |
|
|
28
|
+
|
|
29
|
+
## /admin reset
|
|
30
|
+
|
|
31
|
+
1. **Detect SmartStack.app** - Look for `.smartstack/config.json` or `SmartStack.Api/` directory
|
|
32
|
+
2. **Run CLI command:**
|
|
33
|
+
```bash
|
|
34
|
+
smartstack admin reset
|
|
35
|
+
```
|
|
36
|
+
3. **If `--force` flag passed:**
|
|
37
|
+
```bash
|
|
38
|
+
smartstack admin reset --force
|
|
39
|
+
```
|
|
40
|
+
4. **Report result** - Show new credentials or error message
|
|
41
|
+
|
|
42
|
+
</workflow>
|
|
@@ -70,14 +70,15 @@ Route pattern : `/business/{application}/{module}/{section}`
|
|
|
70
70
|
|
|
71
71
|
### Classes de Base des Entités
|
|
72
72
|
|
|
73
|
-
| Classe | Champs auto-inclus | Quand utiliser |
|
|
74
|
-
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
73
|
+
| Classe | Interface | Champs auto-inclus | Quand utiliser |
|
|
74
|
+
|--------|-----------|---------------------|----------------|
|
|
75
|
+
| `BaseEntity` | - | `Id` (Guid), `TenantId`, `CreatedAt`, `UpdatedAt` | **Défaut pour toute entité métier** |
|
|
76
|
+
| `BaseEntity` | `IAuditableEntity` | + `CreatedBy`, `UpdatedBy` | Entité avec audit complet |
|
|
77
|
+
| `SystemEntity` | - | `Id` (Guid), `CreatedAt` (pas de TenantId) | Entités système (navigation, permissions) |
|
|
78
|
+
| `BaseEntity` | `ISoftDeletable` | + `IsDeleted`, `DeletedBy`, `DeletedAt` | Entité avec suppression logique |
|
|
79
|
+
| `HierarchicalEntity` | - | + `ParentId`, `Level`, `Path` | Arborescences (catégories, menus) |
|
|
79
80
|
|
|
80
|
-
> **IMPORTANT :** Ne JAMAIS proposer `CreatedBy`, `
|
|
81
|
+
> **IMPORTANT :** Ne JAMAIS proposer `CreatedBy`, `UpdatedBy`, `IsDeleted`, `TenantId` comme champs explicites — ils sont AUTOMATIQUES via la classe de base ou l'interface.
|
|
81
82
|
|
|
82
83
|
### Services d'Intégration Disponibles
|
|
83
84
|
|
|
@@ -91,13 +92,53 @@ Route pattern : `/business/{application}/{module}/{section}`
|
|
|
91
92
|
|
|
92
93
|
### Conventions Base de Données
|
|
93
94
|
|
|
94
|
-
| Aspect | Convention |
|
|
95
|
-
|
|
96
|
-
| Schema | `core` (socle) vs `
|
|
97
|
-
| Tables | PascalCase
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
95
|
+
| Aspect | Convention | Exemple |
|
|
96
|
+
|--------|------------|---------|
|
|
97
|
+
| Schema | `core` (socle SmartStack) vs `extensions` (entités client/métier) | `core.auth_Roles`, `extensions.bik_FreeBickes` |
|
|
98
|
+
| Tables | `{prefix}_{EntityPlural}` (préfixe domaine + PascalCase pluriel) | `auth_Users`, `nav_Applications`, `supp_Tickets` |
|
|
99
|
+
| Préfixes domaine | `auth_` (auth), `nav_` (navigation), `cfg_` (config), `ref_` (références), `wkf_` (workflows), `ai_` (IA), `loc_` (i18n), `lic_` (licences), `tenant_` (tenants), `support_` (support) | Pour un nouveau module métier : choisir un préfixe court (3-5 chars) |
|
|
100
|
+
| FK | `{Entity}Id` (ex: `ClientId`, `OrderId`) | |
|
|
101
|
+
| Index | `IX_{Table}_{Column}` | `IX_bik_FreeBickes_TenantId_Code` |
|
|
102
|
+
| Migration | 1 migration par feature, nommée via MCP (`suggest_migration`) | `extensions_v1.0.0_001_AddFreeBicke` |
|
|
103
|
+
|
|
104
|
+
### Conventions de Dossiers (Clean Architecture)
|
|
105
|
+
|
|
106
|
+
| Couche | Pattern de dossiers | Exemple (module FreeBicke dans business > operations) |
|
|
107
|
+
|--------|--------------------|---------------------------------------------------------|
|
|
108
|
+
| **Domain** | `{Project}.Domain/Business/{Application}/{Module}/` | `Domain/Business/Operations/FreeBicke/FreeBicke.cs` |
|
|
109
|
+
| **Application/DTOs** | `{Project}.Application/Business/{Application}/{Module}/DTOs/` | `Application/Business/Operations/FreeBicke/DTOs/FreeBickeDto.cs` |
|
|
110
|
+
| **Application/Interfaces** | `{Project}.Application/Common/Interfaces/` | `Application/Common/Interfaces/IFreeBickeService.cs` |
|
|
111
|
+
| **Infrastructure/Config** | `{Project}.Infrastructure/Persistence/Configurations/{Module}/` | `Configurations/FreeBicke/FreeBickeConfiguration.cs` |
|
|
112
|
+
| **Infrastructure/Services** | `{Project}.Infrastructure/Services/{Module}/` | `Services/FreeBicke/FreeBickeService.cs` |
|
|
113
|
+
| **Infrastructure/SeedData** | `{Project}.Infrastructure/Persistence/Seeding/Data/{Module}/` | `Seeding/Data/FreeBicke/FreeBickeSeedData.cs` |
|
|
114
|
+
| **API/Controllers** | `{Project}.Api/Controllers/Business/{Application}/` | `Controllers/Business/Operations/FreeBickeController.cs` |
|
|
115
|
+
| **Frontend/Pages** | `web/src/pages/business/{application}/{module}/` | `pages/business/operations/freebicke/page.tsx` |
|
|
116
|
+
| **Frontend/i18n** | `web/src/i18n/locales/{lang}/{module}.json` | `locales/fr/freebicke.json` |
|
|
117
|
+
|
|
118
|
+
> **IMPORTANT :** Chaque module a son **propre sous-dossier** dans chaque couche. Ne JAMAIS mettre tous les fichiers à la racine d'une couche.
|
|
119
|
+
|
|
120
|
+
### SeedData Core (Obligatoire pour chaque feature)
|
|
121
|
+
|
|
122
|
+
> **Chaque nouvelle feature nécessite des SeedData dans 5 fichiers core** pour être fonctionnelle (navigation visible, permissions actives, rôles assignés).
|
|
123
|
+
|
|
124
|
+
| # | Fichier | Contenu | Mécanisme |
|
|
125
|
+
|---|---------|---------|-----------|
|
|
126
|
+
| 1 | `NavigationModuleConfiguration.cs` | Entrée module dans `nav_Modules` (HasData) | EF Core Migration |
|
|
127
|
+
| 2 | `NavigationTranslationConfiguration.cs` | Traductions du module (4 langues × chaque entité nav) | EF Core Migration |
|
|
128
|
+
| 3 | `PermissionConfiguration.cs` | Permissions CRUD + wildcards dans `nav_Permissions` (HasData) | EF Core Migration |
|
|
129
|
+
| 4 | `Permissions.cs` (Application layer) | Constantes compile-time pour `[RequirePermission]` | Code |
|
|
130
|
+
| 5 | `RolePermissionConfiguration.cs` | Associations rôle-permission dans `auth_RolePermissions` (HasData) | EF Core Migration |
|
|
131
|
+
|
|
132
|
+
**Rôles par défaut pour chaque application (4 niveaux) :**
|
|
133
|
+
|
|
134
|
+
| Rôle | Permissions | Description |
|
|
135
|
+
|------|-------------|-------------|
|
|
136
|
+
| **{App} Admin** | `{context}.{app}.*` (wildcard) | Accès complet à l'application |
|
|
137
|
+
| **{App} Manager** | `read`, `create`, `update`, `assign` | Gestion complète sans suppression |
|
|
138
|
+
| **{App} Contributor** | `read`, `create`, `update` | Contribution active |
|
|
139
|
+
| **{App} Viewer** | `read` uniquement | Consultation seule |
|
|
140
|
+
|
|
141
|
+
> **CRITIQUE :** Si les SeedData ne sont pas créés, le module sera invisible dans la navigation et les permissions retourneront 403.
|
|
101
142
|
|
|
102
143
|
### Contraintes Non-Négociables
|
|
103
144
|
|
|
@@ -214,6 +255,26 @@ generate_feature_id():
|
|
|
214
255
|
|
|
215
256
|
### AskUserQuestion Format
|
|
216
257
|
|
|
258
|
+
> **⚠️ FORMATTING CRITICAL: `AskUserQuestion` does NOT support markdown or line breaks.**
|
|
259
|
+
> Le champ `question` est rendu en texte brut (plain text). Il n'y a pas de retour à la ligne,
|
|
260
|
+
> pas de gras, pas de puces. Une question simple reste lisible meme longue. Mais du contenu
|
|
261
|
+
> structuré (listes, résumés multi-points, tableaux) devient un mur de texte illisible.
|
|
262
|
+
|
|
263
|
+
| Field | Rule |
|
|
264
|
+
|-------|------|
|
|
265
|
+
| `question` | **1 question ou 1 phrase.** Jamais de liste, résumé multi-points ou contenu structuré. |
|
|
266
|
+
| `header` | 1-2 mots (max 12 chars) |
|
|
267
|
+
| `label` (option) | Court et clair (~20 chars) |
|
|
268
|
+
| `description` (option) | 1 phrase explicative (~60 chars) |
|
|
269
|
+
|
|
270
|
+
> **Pour afficher du contenu structuré** (résumés, listes, tableaux, synthèses) :
|
|
271
|
+
> **TOUJOURS** utiliser du **texte direct** (output text) AVANT l'appel `AskUserQuestion`.
|
|
272
|
+
> Le texte direct supporte le markdown et sera correctement formaté avec titres, puces, gras, etc.
|
|
273
|
+
>
|
|
274
|
+
> **Exemple — Validation de synthèse :**
|
|
275
|
+
> 1. Afficher le résumé structuré en markdown (texte direct)
|
|
276
|
+
> 2. Puis poser une question courte : "Cette synthèse est-elle correcte ?"
|
|
277
|
+
|
|
217
278
|
```
|
|
218
279
|
AskUserQuestion({
|
|
219
280
|
questions: [
|
|
@@ -469,7 +530,7 @@ Le champ `language` dans `.business-analyse/config.json` définit la langue :
|
|
|
469
530
|
| Relances et follow-ups (Elicitation Guide) | **OUI** — traduire les probes à la volée |
|
|
470
531
|
| Documents générés (discovery, BRD, FRD, handoff) | **OUI** — rédigés dans `{language}` |
|
|
471
532
|
| Référence technique interne (patterns, architecture) | NON — reste en langue du fichier |
|
|
472
|
-
| Noms de code (entités, permissions, paths) | NON — toujours en anglais (convention
|
|
533
|
+
| Noms de code (entités, permissions, paths) | NON — toujours en anglais (convention plateforme) |
|
|
473
534
|
|
|
474
535
|
### Règle
|
|
475
536
|
|
|
@@ -480,6 +541,9 @@ Le champ `language` dans `.business-analyse/config.json` définit la langue :
|
|
|
480
541
|
→ Si {language} ≠ "fr", les traduire à la volée
|
|
481
542
|
4. Les documents générés sont rédigés dans {language}
|
|
482
543
|
5. Le code, les noms d'entités et les paths restent TOUJOURS en anglais
|
|
544
|
+
6. Ne JAMAIS utiliser "SmartStack" dans les communications utilisateur
|
|
545
|
+
→ Utiliser "la plateforme", "l'application" ou "le système" à la place
|
|
546
|
+
→ SmartStack est le nom interne du framework, le projet client a son propre nom
|
|
483
547
|
```
|
|
484
548
|
|
|
485
549
|
---
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
|---|----------|-------------|
|
|
21
21
|
| Q1.5 | What ROI do you expect? (time, money, efficiency) | Quantitative |
|
|
22
22
|
| Q1.6 | What savings or gains will this module bring? | Estimation |
|
|
23
|
-
| Q1.7 | What
|
|
24
|
-
| Q1.8 | What is the
|
|
23
|
+
| Q1.7 | What triggered this need? (urgency, regulation, roadmap) | Trigger type |
|
|
24
|
+
| Q1.8 | What is the expected delivery timeline? | Timeline |
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
| Q1.3 (TO-BE) | Vague ("ça ira mieux") | "Concrètement, quel geste ou tâche sera différent ? Combien de clics/minutes en moins ?" |
|
|
37
37
|
| Q1.4 (KPIs) | No measurable metric | "Comment saurez-vous dans 3 mois que cette fonctionnalité a réussi ? Quel chiffre regarderez-vous ?" |
|
|
38
38
|
| Q1.5 (ROI) | "I don't know" | "Combien de temps passez-vous sur ce processus aujourd'hui par semaine ? À combien de personnes ?" |
|
|
39
|
-
| Q1.7 (
|
|
39
|
+
| Q1.7 (trigger) | Vague ("c'est utile") | "Quel événement ou quelle situation a déclenché ce besoin maintenant ? Obligation, inefficacité, opportunité ?" |
|
|
40
40
|
|
|
41
41
|
### Anti-patterns to Detect
|
|
42
42
|
|
|
@@ -45,4 +45,4 @@
|
|
|
45
45
|
| "Il faudrait un bouton/écran/champ..." | **Solution disguised as need** | Apply Solution → Problem Reframing |
|
|
46
46
|
| "C'est évident", "tout le monde sait" | **Implicit assumption** | "Pouvez-vous le formuler explicitement pour que je puisse le documenter ?" |
|
|
47
47
|
| KPIs are feelings ("ça sera mieux") | **Non-measurable success** | "Quel chiffre regarderez-vous pour savoir si c'est réussi ?" |
|
|
48
|
-
| Q1.7 = "
|
|
48
|
+
| Q1.7 = "Opportunité" sans urgence | **Déclencheur à clarifier** | "Quel gain concret attendez-vous ? Comment mesurerez-vous l'amélioration ?" |
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
|
|
28
28
|
## Role -> Permission Mapping
|
|
29
29
|
|
|
30
|
-
> **⚠️ RBAC RULE: Roles are
|
|
30
|
+
> **⚠️ RBAC RULE: Roles are platform permissions, NOT entity attributes.**
|
|
31
31
|
>
|
|
32
32
|
> When stakeholders mention "roles" (admin, manager, user...), these MUST be mapped
|
|
33
|
-
> to
|
|
33
|
+
> to the platform's RBAC permission system (`business.{app}.{module}.{action}`).
|
|
34
34
|
>
|
|
35
35
|
> **NEVER** model a role as:
|
|
36
36
|
> - A property/column on a User entity (e.g. `User.Role`, `User.RoleType`)
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
>
|
|
40
40
|
> **ALWAYS** map roles to permission sets in the matrix below:
|
|
41
41
|
|
|
42
|
-
| Discovered Role |
|
|
42
|
+
| Discovered Role | Platform Permission |
|
|
43
43
|
|-----------------|----------------------|
|
|
44
44
|
| Admin | `*.admin` |
|
|
45
45
|
| Manager | `*.read,create,update` |
|