@atlashub/smartstack-cli 1.37.0 → 2.0.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/config/mcp-defaults.json +62 -0
- package/dist/index.js +57 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +16984 -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 +24 -1
- 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-05-handoff.md +133 -34
- 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/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/steps/db/step-deploy.md +26 -5
- 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 +7 -1
- 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
|
@@ -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>
|
|
@@ -214,6 +214,26 @@ generate_feature_id():
|
|
|
214
214
|
|
|
215
215
|
### AskUserQuestion Format
|
|
216
216
|
|
|
217
|
+
> **⚠️ FORMATTING CRITICAL: `AskUserQuestion` does NOT support markdown or line breaks.**
|
|
218
|
+
> Le champ `question` est rendu en texte brut (plain text). Il n'y a pas de retour à la ligne,
|
|
219
|
+
> pas de gras, pas de puces. Une question simple reste lisible meme longue. Mais du contenu
|
|
220
|
+
> structuré (listes, résumés multi-points, tableaux) devient un mur de texte illisible.
|
|
221
|
+
|
|
222
|
+
| Field | Rule |
|
|
223
|
+
|-------|------|
|
|
224
|
+
| `question` | **1 question ou 1 phrase.** Jamais de liste, résumé multi-points ou contenu structuré. |
|
|
225
|
+
| `header` | 1-2 mots (max 12 chars) |
|
|
226
|
+
| `label` (option) | Court et clair (~20 chars) |
|
|
227
|
+
| `description` (option) | 1 phrase explicative (~60 chars) |
|
|
228
|
+
|
|
229
|
+
> **Pour afficher du contenu structuré** (résumés, listes, tableaux, synthèses) :
|
|
230
|
+
> **TOUJOURS** utiliser du **texte direct** (output text) AVANT l'appel `AskUserQuestion`.
|
|
231
|
+
> Le texte direct supporte le markdown et sera correctement formaté avec titres, puces, gras, etc.
|
|
232
|
+
>
|
|
233
|
+
> **Exemple — Validation de synthèse :**
|
|
234
|
+
> 1. Afficher le résumé structuré en markdown (texte direct)
|
|
235
|
+
> 2. Puis poser une question courte : "Cette synthèse est-elle correcte ?"
|
|
236
|
+
|
|
217
237
|
```
|
|
218
238
|
AskUserQuestion({
|
|
219
239
|
questions: [
|
|
@@ -469,7 +489,7 @@ Le champ `language` dans `.business-analyse/config.json` définit la langue :
|
|
|
469
489
|
| Relances et follow-ups (Elicitation Guide) | **OUI** — traduire les probes à la volée |
|
|
470
490
|
| Documents générés (discovery, BRD, FRD, handoff) | **OUI** — rédigés dans `{language}` |
|
|
471
491
|
| 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
|
|
492
|
+
| Noms de code (entités, permissions, paths) | NON — toujours en anglais (convention plateforme) |
|
|
473
493
|
|
|
474
494
|
### Règle
|
|
475
495
|
|
|
@@ -480,6 +500,9 @@ Le champ `language` dans `.business-analyse/config.json` définit la langue :
|
|
|
480
500
|
→ Si {language} ≠ "fr", les traduire à la volée
|
|
481
501
|
4. Les documents générés sont rédigés dans {language}
|
|
482
502
|
5. Le code, les noms d'entités et les paths restent TOUJOURS en anglais
|
|
503
|
+
6. Ne JAMAIS utiliser "SmartStack" dans les communications utilisateur
|
|
504
|
+
→ Utiliser "la plateforme", "l'application" ou "le système" à la place
|
|
505
|
+
→ SmartStack est le nom interne du framework, le projet client a son propre nom
|
|
483
506
|
```
|
|
484
507
|
|
|
485
508
|
---
|
|
@@ -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` |
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
| Q3.1 | List ESSENTIAL features (Must-Have) | List |
|
|
13
13
|
| Q3.2 | List DESIRED features (Should-Have) | List |
|
|
14
14
|
| Q3.3 | List OPTIONAL features (Could-Have) | List |
|
|
15
|
-
| Q3.4 |
|
|
15
|
+
| Q3.4 | Any known constraints limiting v1 scope? | List of constraints |
|
|
16
16
|
|
|
17
17
|
## 3.2 Business Process
|
|
18
18
|
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
| Must | Without this feature, the module has no value |
|
|
33
33
|
| Should | Brings significant value but can wait for v2 |
|
|
34
34
|
| Could | Nice-to-have, can be implemented if time available |
|
|
35
|
-
| Won't |
|
|
35
|
+
| Won't | Deferred — will be considered for future versions |
|
|
36
36
|
|
|
37
37
|
## Elicitation Guide
|
|
38
38
|
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
| Q3.1 (Must-Have) | > 10 items listed | "Si vous ne pouviez en garder que 3 pour la v1, lesquels ? Les autres sont peut-être Should-Have." |
|
|
44
44
|
| Q3.1 (Must-Have) | Mélange CRUD + complexe | "Le CRUD de base (liste, création, modification, suppression) est-il un Must, ou certaines actions sont Could ?" |
|
|
45
45
|
| Q3.2 (Should-Have) | Vide | "Y a-t-il des fonctionnalités utiles mais qui peuvent attendre une v2 ? Export, filtres avancés, notifications ?" |
|
|
46
|
-
| Q3.4 (
|
|
46
|
+
| Q3.4 (limites) | Vide | "Y a-t-il des contraintes connues qui pourraient limiter le périmètre ? (budget, délai, dépendance)" |
|
|
47
47
|
| Q3.5 (main flow) | < 3 étapes | "Détaillez : l'utilisateur arrive sur l'écran → que voit-il ? → que clique-t-il ? → que se passe-t-il ?" |
|
|
48
48
|
| Q3.7 (alt flows) | "Il n'y en a pas" | "Que se passe-t-il si l'utilisateur annule ? S'il n'a pas la permission ? Si les données sont invalides ?" |
|
|
49
49
|
| Q3.8 (errors) | Liste vide | "Que se passe-t-il si le réseau coupe ? Si un champ obligatoire manque ? Si un doublon existe ?" |
|
|
@@ -53,6 +53,6 @@
|
|
|
53
53
|
| Signal | Anti-pattern | Action |
|
|
54
54
|
|--------|-------------|--------|
|
|
55
55
|
| Tout est Must-Have | **Scope non priorisé** | Appliquer le test : "Sans cette fonctionnalité, le module a-t-il ZÉRO valeur ?" |
|
|
56
|
-
| Q3.4
|
|
56
|
+
| Q3.4 pas de limite identifiée | **Scope à valider** | Le scope sera naturellement borné par les Must-Have/Should-Have. Vérifier que les Must-Have sont réalistes. |
|
|
57
57
|
| Flow linéaire sans alternative | **Happy path uniquement** | "Que se passe-t-il au step X si la condition Y n'est pas remplie ?" |
|
|
58
58
|
| Mélange fonctionnel/technique | **Solution dans le scope** | Séparer : "gérer les commandes" (fonctionnel) vs "utiliser Redis" (technique) |
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## Technical Mapping
|
|
29
29
|
|
|
30
|
-
| Business Concept |
|
|
30
|
+
| Business Concept | Platform |
|
|
31
31
|
|-----------------|------------|
|
|
32
32
|
| Main entity | Domain Entity |
|
|
33
33
|
| Required attribute | `[Required]` |
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
| 1:N relationship | Navigation property |
|
|
36
36
|
| Sensitive data | Encryption + audit |
|
|
37
37
|
|
|
38
|
-
> **⚠️ RBAC EXCLUSION: The following concepts are handled by
|
|
38
|
+
> **⚠️ RBAC EXCLUSION: The following concepts are handled by the platform's RBAC system
|
|
39
39
|
> and MUST NOT be modeled as entity attributes or separate entities:**
|
|
40
40
|
>
|
|
41
41
|
> | Concept | ❌ Wrong (entity attribute) | ✅ Correct (RBAC) |
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
| Question | If answer is vague/insufficient | Probe |
|
|
55
55
|
|----------|-------------------------------|-------|
|
|
56
56
|
| Q4.1 (entities) | Single entity listed | "Cette entité a-t-elle des sous-éléments ? (lignes de commande, pièces jointes, historique)" |
|
|
57
|
-
| Q4.1 (entities) | Mentions "User" as entity | "L'utilisateur est géré par
|
|
57
|
+
| Q4.1 (entities) | Mentions "User" as entity | "L'utilisateur est géré par la plateforme (Identity). Décrivez plutôt l'entité MÉTIER (Client, Employee, Contact...)" |
|
|
58
58
|
| Q4.2 (attributes) | Liste de champs techniques (ID, CreatedDate) | "Les champs techniques (Id, TenantId, audit) sont auto-gérés. Quels sont les attributs MÉTIER ?" |
|
|
59
59
|
| Q4.3 (relationships) | "1:N" without detail | "Un {Parent} peut avoir combien de {Children} max ? Un {Child} peut-il exister sans {Parent} ?" |
|
|
60
60
|
| Q4.4 (volume) | "Beaucoup" | "Ordre de grandeur : dizaines, centaines, milliers, millions ? Croissance par mois ?" |
|
|
@@ -65,14 +65,14 @@
|
|
|
65
65
|
|
|
66
66
|
| Signal | Anti-pattern | Action |
|
|
67
67
|
|--------|-------------|--------|
|
|
68
|
-
| Entity "User" avec attributs métier | **Confusion User/Identity** |
|
|
68
|
+
| Entity "User" avec attributs métier | **Confusion User/Identity** | La plateforme gère les Users via Identity. L'entité métier = Client, Employee, Contact |
|
|
69
69
|
| Attributs Id, TenantId, CreatedBy listés | **Champs techniques comme métier** | Ces champs sont auto-générés par `AuditableEntity`. Ne pas les lister. |
|
|
70
70
|
| Pas de soft delete mentionné | **Suppression non clarifiée** | "Les données supprimées sont-elles effacées définitivement ou archivées ?" → `SoftDeletableEntity` |
|
|
71
71
|
| Aucune contrainte d'unicité | **Unicité non vérifiée** | "Deux entités peuvent-elles avoir le même nom/code/email ?" |
|
|
72
72
|
|
|
73
|
-
###
|
|
73
|
+
### Entity Patterns
|
|
74
74
|
|
|
75
|
-
| Besoin métier | Base class
|
|
75
|
+
| Besoin métier | Base class plateforme | Inclut automatiquement |
|
|
76
76
|
|---------------|----------------------|----------------------|
|
|
77
77
|
| Entité simple | `Entity` | Id, TenantId |
|
|
78
78
|
| Avec audit | `AuditableEntity` | + CreatedBy, CreatedDate, ModifiedBy, ModifiedDate |
|