@atlashub/smartstack-cli 3.21.0 → 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +17 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +155 -162
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/skills/apex/SKILL.md +21 -0
- package/templates/skills/apex/references/smartstack-api.md +481 -0
- package/templates/skills/apex/references/smartstack-layers.md +85 -15
- package/templates/skills/apex/steps/step-00-init.md +27 -14
- package/templates/skills/apex/steps/step-01-analyze.md +18 -0
- package/templates/skills/apex/steps/step-03-execute.md +8 -6
- package/templates/skills/apex/steps/step-04-validate.md +92 -0
- package/templates/skills/apex/steps/step-07-tests.md +29 -5
- package/templates/skills/application/references/application-roles-template.md +2 -2
- package/templates/skills/application/steps/step-05-frontend.md +40 -35
- package/templates/skills/application/templates-frontend.md +64 -36
- package/templates/skills/business-analyse/html/ba-interactive.html +80 -6
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +38 -6
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
- package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
- package/templates/skills/business-analyse/references/deploy-data-build.md +5 -3
- package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
- package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
- package/templates/skills/business-analyse/references/validate-incremental-html.md +26 -4
- package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
- package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
- package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
- package/templates/skills/business-analyse/steps/step-03c-compile.md +114 -0
- package/templates/skills/business-analyse/steps/step-03d-validate.md +144 -22
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +114 -2
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +28 -0
- package/templates/skills/ralph-loop/references/category-rules.md +5 -2
- package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
- package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
- package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
- package/templates/skills/ralph-loop/steps/step-02-execute.md +81 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Naming Conventions - SmartStack
|
|
2
|
+
|
|
3
|
+
> **Loaded by:** business-analyse steps when generating codes and routes
|
|
4
|
+
> **Purpose:** Define transformation rules between code identifiers (PascalCase) and URL routes (kebab-case)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Module & Application Codes
|
|
9
|
+
|
|
10
|
+
### In feature.json
|
|
11
|
+
|
|
12
|
+
- `modules[].code` = **PascalCase** (e.g., `"HumanResources"`, `"TimeManagement"`)
|
|
13
|
+
- `metadata.application` = **PascalCase** (e.g., `"HumanResources"`)
|
|
14
|
+
- Used for: C# namespaces, file paths, class names, folder structure
|
|
15
|
+
|
|
16
|
+
### In URL Routes
|
|
17
|
+
|
|
18
|
+
- **kebab-case lowercase** (e.g., `"/business/human-resources/employees"`)
|
|
19
|
+
- Transform: `PascalCase` → `kebab-case` via helper function
|
|
20
|
+
- Used for: Web URLs, navigation routes, API paths
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Transformation Rules
|
|
25
|
+
|
|
26
|
+
| Source | Format | Example | Usage |
|
|
27
|
+
|--------|--------|---------|-------|
|
|
28
|
+
| Module code | `PascalCase` | `HumanResources` | C# files, classes, namespaces |
|
|
29
|
+
| Application code | `PascalCase` | `HumanResources` | Namespaces, folders |
|
|
30
|
+
| Context code | `lowercase` | `business` | URLs, routes, contexts |
|
|
31
|
+
| Route path | `kebab-case` | `/business/human-resources` | Web URLs, navigation |
|
|
32
|
+
| Section code | `kebab-case` | `time-tracking`, `approve` | URLs, API paths, sections |
|
|
33
|
+
| Resource code | `kebab-case` | `repair-grid`, `employee-form` | Component IDs, resources |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Why These Conventions?
|
|
38
|
+
|
|
39
|
+
### PascalCase for Code
|
|
40
|
+
- **C# Standard**: Namespaces, classes, properties follow PascalCase
|
|
41
|
+
- **File Structure**: Matches .NET project conventions
|
|
42
|
+
- **Compile-time**: Enforced by compiler and naming analyzers
|
|
43
|
+
|
|
44
|
+
### kebab-case for URLs
|
|
45
|
+
- **Web Standard**: Readable, SEO-friendly, case-insensitive
|
|
46
|
+
- **REST API Convention**: Lowercase routes with hyphens
|
|
47
|
+
- **Frontend Compatibility**: React Router, Vue Router expect lowercase
|
|
48
|
+
- **Database Storage**: Navigation routes stored lowercase prevent case mismatch errors
|
|
49
|
+
|
|
50
|
+
### Separation of Concerns
|
|
51
|
+
- **Code Identity** ≠ **URL Identity**
|
|
52
|
+
- Backend uses PascalCase internally
|
|
53
|
+
- URLs transform to kebab-case at persistence layer (seed data)
|
|
54
|
+
- Frontend consumes kebab-case routes from database
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Helper Functions
|
|
59
|
+
|
|
60
|
+
### C# (Backend Seed Data)
|
|
61
|
+
|
|
62
|
+
**Location:** `NavigationSeedData.cs` templates in core-seed-data.md
|
|
63
|
+
|
|
64
|
+
**Usage:**
|
|
65
|
+
```csharp
|
|
66
|
+
Route = ToKebabCase($"/{contextCode}/{appCode}/{moduleCode}")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Transformation Examples:**
|
|
70
|
+
- `HumanResources` → `human-resources`
|
|
71
|
+
- `TimeManagement` → `time-management`
|
|
72
|
+
- `Projects` → `projects` (single word, no change)
|
|
73
|
+
- `AI` → `ai` (acronym lowercase)
|
|
74
|
+
|
|
75
|
+
**Implementation:** See `templates/skills/ralph-loop/references/core-seed-data.md` line 162
|
|
76
|
+
|
|
77
|
+
### JavaScript (BA Handoff Generation)
|
|
78
|
+
|
|
79
|
+
**Location:** Used in step-05a-handoff.md when building seedDataCore
|
|
80
|
+
|
|
81
|
+
**Usage:**
|
|
82
|
+
```javascript
|
|
83
|
+
const route = `/${contextCode}/${toKebabCase(appCode)}/${toKebabCase(moduleCode)}`;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Implementation:**
|
|
87
|
+
```javascript
|
|
88
|
+
function toKebabCase(code) {
|
|
89
|
+
return code
|
|
90
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
91
|
+
.toLowerCase();
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### TypeScript (Frontend MCP)
|
|
96
|
+
|
|
97
|
+
**Location:** Used in scaffold_routes and validate_frontend_routes
|
|
98
|
+
|
|
99
|
+
**Usage:**
|
|
100
|
+
```typescript
|
|
101
|
+
const webPath = `/${navRoute.split('.').map(toKebabCase).join('/')}`;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Implementation:**
|
|
105
|
+
```typescript
|
|
106
|
+
function toKebabCase(str: string): string {
|
|
107
|
+
return str
|
|
108
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
109
|
+
.toLowerCase();
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Validation
|
|
116
|
+
|
|
117
|
+
### During Generation (ralph-loop)
|
|
118
|
+
|
|
119
|
+
POST-CHECK after NavigationSeedData generation:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Detect routes with uppercase
|
|
123
|
+
grep -E 'Route = "?/[^"]*[A-Z]' Infrastructure/Persistence/Seeding/Data/*/NavigationSeedData.cs
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If match found → ERROR, routes not transformed to kebab-case.
|
|
127
|
+
|
|
128
|
+
### Via MCP
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
validate_frontend_routes
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Detects:**
|
|
135
|
+
- Routes with uppercase in URLs
|
|
136
|
+
- Seed data routes not matching frontend expectations
|
|
137
|
+
- PascalCase URLs without kebab-case conversion
|
|
138
|
+
|
|
139
|
+
**Output Example:**
|
|
140
|
+
```
|
|
141
|
+
❌ Case mismatch detected (1 routes with uppercase):
|
|
142
|
+
Route "business.humanresources" uses PascalCase in URL.
|
|
143
|
+
Found: business/HumanResources → Expected: business/human-resources
|
|
144
|
+
Fix: Use ToKebabCase() in NavigationSeedData route generation
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Common Pitfalls
|
|
150
|
+
|
|
151
|
+
### ❌ Generating Routes Without Transformation
|
|
152
|
+
|
|
153
|
+
**Wrong (causes 404 on menu click):**
|
|
154
|
+
```csharp
|
|
155
|
+
Route = $"/{contextCode}/{appCode}/{moduleCode}"
|
|
156
|
+
// Result: /business/HumanResources/Projects
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Correct:**
|
|
160
|
+
```csharp
|
|
161
|
+
Route = ToKebabCase($"/{contextCode}/{appCode}/{moduleCode}")
|
|
162
|
+
// Result: /business/human-resources/projects
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### ❌ Hardcoding Routes in lowercase
|
|
166
|
+
|
|
167
|
+
**Wrong (loses traceability to code):**
|
|
168
|
+
```csharp
|
|
169
|
+
Route = "/business/humanresources" // Flattens multi-word into single word
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Correct:**
|
|
173
|
+
```csharp
|
|
174
|
+
Route = ToKebabCase($"/{contextCode}/{appCode}")
|
|
175
|
+
// If appCode = "HumanResources" → /business/human-resources
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### ❌ Inconsistent Section/Resource Codes
|
|
179
|
+
|
|
180
|
+
**Wrong (mixing conventions):**
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"code": "TimeTracking", // PascalCase
|
|
184
|
+
"route": "/time-tracking" // kebab-case route doesn't match
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Correct:**
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"code": "time-tracking", // kebab-case from start
|
|
192
|
+
"route": "/business/human-resources/time-tracking"
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Sections and resources ALWAYS use kebab-case (no transformation needed).
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Examples
|
|
201
|
+
|
|
202
|
+
### Full Hierarchy Example
|
|
203
|
+
|
|
204
|
+
**BA Input (feature.json):**
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"metadata": {
|
|
208
|
+
"context": "business",
|
|
209
|
+
"application": "HumanResources"
|
|
210
|
+
},
|
|
211
|
+
"modules": [
|
|
212
|
+
{ "code": "TimeManagement" },
|
|
213
|
+
{ "code": "Projects" }
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Generated Routes (seed data):**
|
|
219
|
+
```
|
|
220
|
+
Application: /business/human-resources
|
|
221
|
+
Module 1: /business/human-resources/time-management
|
|
222
|
+
Module 2: /business/human-resources/projects
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**C# Code Structure:**
|
|
226
|
+
```
|
|
227
|
+
src/Domain/Entities/Business/HumanResources/TimeManagement/TimeEntry.cs
|
|
228
|
+
src/Application/Services/Business/HumanResources/TimeManagement/TimeEntryService.cs
|
|
229
|
+
src/API/Controllers/Business/HumanResources/TimeManagementController.cs
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Frontend Routes (React Router):**
|
|
233
|
+
```tsx
|
|
234
|
+
<Route path="/business/human-resources/time-management" element={<TimeManagementPage />} />
|
|
235
|
+
<Route path="/business/human-resources/projects" element={<ProjectsPage />} />
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## References
|
|
241
|
+
|
|
242
|
+
- **Template:** `templates/skills/ralph-loop/references/core-seed-data.md`
|
|
243
|
+
- **Validation:** `src/mcp/tools/validate-frontend-routes.ts`
|
|
244
|
+
- **POST-CHECK:** `templates/skills/ralph-loop/steps/step-02-execute.md` (section 5)
|
|
245
|
+
- **Handoff:** `templates/skills/business-analyse/steps/step-05a-handoff.md` (section 4.6)
|
|
@@ -46,10 +46,11 @@ const EMBEDDED_ARTIFACTS = {
|
|
|
46
46
|
// FOR EACH completed module: extract wireframes with RENAMED fields
|
|
47
47
|
// SAFETY NET: check BOTH key names (agent may use either)
|
|
48
48
|
[moduleCode]: (moduleFeature.specification.uiWireframes || moduleFeature.specification.wireframes || []).map(wf => ({
|
|
49
|
-
screen: wf.screen,
|
|
50
|
-
section: wf.section,
|
|
49
|
+
screen: wf.screen || wf.name || wf.title || wf.id || "", // SAFETY NET: fallback name/title/id → screen
|
|
50
|
+
section: wf.section || "",
|
|
51
51
|
format: wf.mockupFormat || "ascii", // RENAME: mockupFormat → format
|
|
52
|
-
content: wf.mockup,
|
|
52
|
+
content: wf.mockup || wf.ascii || wf.content || "", // SAFETY NET: mockup/ascii/content → content
|
|
53
|
+
svgContent: null, // Populated by Step 3-bis SVG generation
|
|
53
54
|
description: wf.description || "",
|
|
54
55
|
elements: wf.elements || [],
|
|
55
56
|
actions: wf.actions || [],
|
|
@@ -74,6 +75,27 @@ const EMBEDDED_ARTIFACTS = {
|
|
|
74
75
|
> The HTML renderer reads `format` and `content`. You MUST rename these fields.
|
|
75
76
|
> Failure to rename = empty mockup display in the browser.
|
|
76
77
|
|
|
78
|
+
### Step 3-bis: Generate SVG Wireframes (Parallel Task Agents)
|
|
79
|
+
|
|
80
|
+
> **Purpose:** Enrich each ASCII wireframe with a professional SVG version for dual-view rendering.
|
|
81
|
+
> **Cost:** ~$0.01 per wireframe (Sonnet model). Negligible for 2-6 wireframes per module.
|
|
82
|
+
> **Fallback:** If ANY SVG generation fails, proceed with ASCII only (svgContent stays null).
|
|
83
|
+
|
|
84
|
+
See [references/wireframe-svg-style-guide.md](wireframe-svg-style-guide.md) for the complete SVG style specification, prompt template, and orchestration process.
|
|
85
|
+
|
|
86
|
+
**Process summary:**
|
|
87
|
+
|
|
88
|
+
1. **Read** `references/wireframe-svg-style-guide.md` to get the prompt template
|
|
89
|
+
2. **Collect** all wireframes in `EMBEDDED_ARTIFACTS.wireframes` where `content` exists and `svgContent` is null
|
|
90
|
+
3. **Spawn parallel Task(sonnet) agents** — ONE per wireframe, ALL in a single message
|
|
91
|
+
4. **Collect and validate** results: strip markdown fences if present, verify SVG starts with `<svg` and contains `</svg>`
|
|
92
|
+
5. **Inject** valid SVGs into `EMBEDDED_ARTIFACTS.wireframes[moduleCode][index].svgContent`
|
|
93
|
+
6. **Display** summary: `SVG wireframes: {generated}/{total} generated successfully`
|
|
94
|
+
|
|
95
|
+
> **CRITICAL:** This step is NEVER blocking. If all SVG generations fail, deployment
|
|
96
|
+
> continues with ASCII-only wireframes. The HTML renderer checks for `svgContent` before
|
|
97
|
+
> showing the SVG view. SVG is an enhancement, not a requirement.
|
|
98
|
+
|
|
77
99
|
### Step 4: Replace Placeholders in Template
|
|
78
100
|
|
|
79
101
|
- Serialize the FEATURE_DATA object as JSON (2-space indentation)
|
|
@@ -93,7 +115,7 @@ const EMBEDDED_ARTIFACTS = {
|
|
|
93
115
|
Modules included: {completedModules.length}/{totalModules} specified
|
|
94
116
|
- {completedModule1}: {uc_count} UCs, {br_count} BRs, {wireframe_count} wireframes
|
|
95
117
|
- {completedModule2}: ...
|
|
96
|
-
Visual artifacts: {total_wireframes} wireframes embedded
|
|
118
|
+
Visual artifacts: {total_wireframes} wireframes embedded ({svg_count} with SVG, {ascii_only_count} ASCII only)
|
|
97
119
|
Remaining: {pendingModules.join(', ')} (will be added after specification)
|
|
98
120
|
→ Client can open in browser to review completed modules now.
|
|
99
121
|
```
|
|
@@ -139,16 +139,16 @@ const checklist = {
|
|
|
139
139
|
|
|
140
140
|
wireframes: {
|
|
141
141
|
minimum: specification.sections.length, // 1 wireframe PER section
|
|
142
|
-
actual: specification.uiWireframes.length,
|
|
142
|
+
actual: (specification.uiWireframes || specification.wireframes || []).length, // Check BOTH key names
|
|
143
143
|
status: actual >= minimum ? "PASS" : "FAIL",
|
|
144
144
|
blocking: true,
|
|
145
|
-
details: "EVERY section MUST have a wireframe (ASCII/SVG)"
|
|
145
|
+
details: "EVERY section MUST have a wireframe (ASCII/SVG). Check both uiWireframes and wireframes keys."
|
|
146
146
|
},
|
|
147
147
|
|
|
148
148
|
wireframeSchemaCompliance: {
|
|
149
149
|
check: "All wireframes have required fields: screen, section, mockupFormat, elements[], componentMapping[], layout (object), permissionsRequired[]",
|
|
150
|
-
status: specification.uiWireframes.every(wf =>
|
|
151
|
-
wf.screen && wf.section && wf.mockupFormat &&
|
|
150
|
+
status: (specification.uiWireframes || specification.wireframes || []).every(wf =>
|
|
151
|
+
(wf.screen || wf.title) && wf.section && (wf.mockupFormat || wf.mockup || wf.ascii) &&
|
|
152
152
|
Array.isArray(wf.elements) && wf.elements.length > 0 &&
|
|
153
153
|
Array.isArray(wf.componentMapping) && wf.componentMapping.length > 0 &&
|
|
154
154
|
typeof wf.layout === 'object' && wf.layout !== null &&
|
|
@@ -156,8 +156,9 @@ const checklist = {
|
|
|
156
156
|
) ? "PASS" : "FAIL",
|
|
157
157
|
blocking: true,
|
|
158
158
|
details: "Wireframes missing metadata render as empty frames in HTML documentation. " +
|
|
159
|
-
"Check: screen (not name/id), section, mockupFormat, elements[] non-empty, " +
|
|
160
|
-
"componentMapping[] as array of {wireframeElement, reactComponent}, layout as object (not string)"
|
|
159
|
+
"Check: screen (not name/id/title), section, mockupFormat, elements[] non-empty, " +
|
|
160
|
+
"componentMapping[] as array of {wireframeElement, reactComponent}, layout as object (not string). " +
|
|
161
|
+
"Also see references/acceptance-criteria.md for bash-verifiable checks."
|
|
161
162
|
},
|
|
162
163
|
|
|
163
164
|
navigation: {
|
|
@@ -240,12 +241,31 @@ const checklist = {
|
|
|
240
241
|
details: "Modules need field validation rules"
|
|
241
242
|
},
|
|
242
243
|
|
|
243
|
-
// SECTION 10: GHERKIN SCENARIOS (WARNING)
|
|
244
|
-
|
|
244
|
+
// SECTION 10: GHERKIN SCENARIOS (BLOCKING for format, WARNING for count)
|
|
245
|
+
gherkinFormat: {
|
|
246
|
+
check: "gherkinScenarios is ARRAY (not single object)",
|
|
247
|
+
status: Array.isArray(specification.gherkinScenarios) ? "PASS" : "FAIL",
|
|
248
|
+
blocking: true,
|
|
249
|
+
details: "gherkinScenarios MUST be an array [{feature, scenarios}], not a single object. " +
|
|
250
|
+
"Auto-fix: wrap in array [gherkinScenarios]. See step-03c ABSOLUTE FORMAT CHECKS."
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
gherkinContent: {
|
|
254
|
+
check: "Each gherkin entry has feature (string) + scenarios (array)",
|
|
255
|
+
status: (Array.isArray(specification.gherkinScenarios) &&
|
|
256
|
+
specification.gherkinScenarios.every(g => g.feature && Array.isArray(g.scenarios))
|
|
257
|
+
) ? "PASS" : "FAIL",
|
|
258
|
+
blocking: true,
|
|
259
|
+
details: "Each gherkin entry must have {feature: string, scenarios: [{name, tags, given[], when[], then[]}]}"
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
gherkinScenarioCount: {
|
|
245
263
|
minimum: 2,
|
|
246
|
-
actual: specification.gherkinScenarios
|
|
264
|
+
actual: (Array.isArray(specification.gherkinScenarios)
|
|
265
|
+
? specification.gherkinScenarios.reduce((sum, g) => sum + (g.scenarios || []).length, 0)
|
|
266
|
+
: 0),
|
|
247
267
|
status: actual >= minimum ? "PASS" : "FAIL",
|
|
248
|
-
blocking: false, // WARNING only
|
|
268
|
+
blocking: false, // WARNING only — count is advisory
|
|
249
269
|
details: "Gherkin scenarios enable automated testing"
|
|
250
270
|
}
|
|
251
271
|
};
|
|
@@ -297,7 +317,7 @@ ELSE:
|
|
|
297
317
|
| Seed Data | 2 | {n} | {n} | {n} |
|
|
298
318
|
| API Endpoints | 2 | {n} | {n} | {n} |
|
|
299
319
|
| Validations | 1 | {n} | {n} | {n} |
|
|
300
|
-
| Gherkin |
|
|
320
|
+
| Gherkin | 3 | {n} | {n} | {n} |
|
|
301
321
|
|
|
302
322
|
TOTAL: {total_checks} checks | {passed} ✓ | {failed} ✗ | {warnings} ⚠
|
|
303
323
|
|