mandrel 2.7.0 → 2.8.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/.agents/README.md +104 -330
- package/.agents/agents/auditor.md +135 -0
- package/.agents/agents/plan-critic.md +80 -0
- package/.agents/audit-checklists/dependencies.md +7 -0
- package/.agents/audit-checklists/documentation.md +1 -0
- package/.agents/docs/SDLC.md +69 -162
- package/.agents/docs/configuration.md +133 -499
- package/.agents/docs/quality-gates.md +59 -180
- package/.agents/instructions.md +170 -295
- package/.agents/rules/changelog-style.md +8 -66
- package/.agents/rules/ci-remediation.md +65 -124
- package/.agents/rules/gherkin-standards.md +10 -31
- package/.agents/rules/git-conventions-reference.md +28 -61
- package/.agents/rules/git-conventions.md +1 -1
- package/.agents/rules/orchestration-error-handling.md +5 -15
- package/.agents/rules/security-baseline.md +7 -13
- package/.agents/rules/shell-conventions.md +4 -13
- package/.agents/rules/test-seams.md +2 -2
- package/.agents/rules/testing-standards.md +7 -17
- package/.agents/skills/core/api-and-interface-design/SKILL.md +23 -297
- package/.agents/skills/core/api-and-interface-design/reference.md +76 -0
- package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +20 -327
- package/.agents/skills/core/browser-testing-with-devtools/reference.md +74 -0
- package/.agents/skills/core/code-review-and-quality/reference.md +16 -398
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +14 -281
- package/.agents/skills/core/documentation-and-adrs/reference.md +42 -338
- package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +28 -326
- package/.agents/skills/core/idea-refinement/SKILL.md +67 -193
- package/.agents/skills/core/security-and-hardening/SKILL.md +15 -31
- package/.agents/skills/core/security-and-hardening/reference.md +15 -273
- package/.agents/skills/skills.index.json +5 -5
- package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +16 -222
- package/.agents/skills/stack/qa/gherkin-authoring/reference.md +157 -0
- package/.agents/skills/stack/qa/playwright/SKILL.md +0 -29
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +19 -23
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +35 -53
- package/.agents/skills/stack/qa/vitest/SKILL.md +0 -29
- package/.agents/workflows/audit-accessibility.md +34 -108
- package/.agents/workflows/audit-architecture.md +35 -123
- package/.agents/workflows/audit-clean-code.md +19 -78
- package/.agents/workflows/audit-data-model.md +32 -100
- package/.agents/workflows/audit-dependencies.md +47 -111
- package/.agents/workflows/audit-devops.md +16 -83
- package/.agents/workflows/audit-documentation.md +46 -93
- package/.agents/workflows/audit-navigability.md +26 -80
- package/.agents/workflows/audit-performance.md +40 -106
- package/.agents/workflows/audit-privacy.md +17 -80
- package/.agents/workflows/audit-quality.md +35 -99
- package/.agents/workflows/audit-security.md +20 -78
- package/.agents/workflows/audit-seo.md +20 -98
- package/.agents/workflows/audit-sre.md +20 -88
- package/.agents/workflows/audit-to-stories.md +1 -8
- package/.agents/workflows/audit-ux-ui.md +17 -80
- package/.agents/workflows/deliver.md +54 -9
- package/.agents/workflows/git-cleanup.md +50 -275
- package/.agents/workflows/helpers/audit-lens-core.md +230 -0
- package/.agents/workflows/helpers/code-review.md +11 -23
- package/.agents/workflows/helpers/deliver-story-reference.md +114 -17
- package/.agents/workflows/helpers/deliver-story.md +26 -186
- package/.agents/workflows/helpers/qa-core.md +174 -0
- package/.agents/workflows/helpers/qa-run-scenario-reference.md +35 -0
- package/.agents/workflows/helpers/qa-run-scenario.md +11 -25
- package/.agents/workflows/helpers/worktree-lifecycle.md +6 -67
- package/.agents/workflows/mandrel-update.md +7 -13
- package/.agents/workflows/plan.md +17 -15
- package/.agents/workflows/qa-assist.md +140 -269
- package/.agents/workflows/qa-explore.md +125 -316
- package/.agents/workflows/qa-run.md +180 -380
- package/docs/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/.agents/workflows/helpers/audit-dual-path.md +0 -59
- package/.agents/workflows/helpers/audit-self-check.md +0 -70
- package/.agents/workflows/helpers/audit-severity-scale.md +0 -19
|
@@ -3,17 +3,11 @@
|
|
|
3
3
|
**Read this when** a task engages one of the sections below and the Policy
|
|
4
4
|
Capsule in [`SKILL.md`](SKILL.md) does not settle it on its own. The capsule
|
|
5
5
|
is the contract; this file is the reference material behind it. Nothing here
|
|
6
|
-
relaxes a capsule MUST, and
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- Building anything that accepts user input
|
|
12
|
-
- Implementing authentication or authorization
|
|
13
|
-
- Storing or transmitting sensitive data
|
|
14
|
-
- Integrating with external APIs or services
|
|
15
|
-
- Adding file uploads, webhooks, or callbacks
|
|
16
|
-
- Handling payment or PII data
|
|
6
|
+
relaxes a capsule MUST, and the generic *how* of each MUST (parameterize
|
|
7
|
+
queries, hash passwords, encode output, verify ownership, set headers,
|
|
8
|
+
restrict CORS, validate at the boundary) is stated once in the SSOT rule,
|
|
9
|
+
[`security-baseline.md`](../../../rules/security-baseline.md) — not duplicated
|
|
10
|
+
here.
|
|
17
11
|
|
|
18
12
|
## Security Surfacing, Not Runtime Pause
|
|
19
13
|
|
|
@@ -40,185 +34,17 @@ unrecoverable blockers (missing prerequisite, ambiguous spec a sub-agent
|
|
|
40
34
|
cannot resolve), not for "this change is sensitive." Sensitive changes
|
|
41
35
|
ship through the documentation path above.
|
|
42
36
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
The patterns below show **how** to satisfy the MUSTs in
|
|
46
|
-
[`security-baseline.md`](../../../rules/security-baseline.md). The MUSTs
|
|
47
|
-
themselves (parameterize queries, hash passwords, encode output, verify
|
|
48
|
-
ownership, set headers, restrict CORS, exclude sensitive fields) are listed
|
|
49
|
-
in the rule.
|
|
50
|
-
|
|
51
|
-
### 1. Injection (SQL, NoSQL, OS Command)
|
|
52
|
-
|
|
53
|
-
See [security-baseline § Output & Rendering](../../../rules/security-baseline.md#output--rendering).
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
// BAD: SQL injection via string concatenation
|
|
57
|
-
const query = `SELECT * FROM users WHERE id = '${userId}'`;
|
|
58
|
-
|
|
59
|
-
// GOOD: Parameterized query
|
|
60
|
-
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
|
|
61
|
-
|
|
62
|
-
// GOOD: ORM with parameterized input
|
|
63
|
-
const user = await prisma.user.findUnique({ where: { id: userId } });
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 2. Broken Authentication
|
|
67
|
-
|
|
68
|
-
See [security-baseline § Authentication](../../../rules/security-baseline.md#authentication).
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
import { hash, compare } from 'bcrypt';
|
|
72
|
-
|
|
73
|
-
const SALT_ROUNDS = 12;
|
|
74
|
-
const hashedPassword = await hash(plaintext, SALT_ROUNDS);
|
|
75
|
-
const isValid = await compare(plaintext, hashedPassword);
|
|
76
|
-
|
|
77
|
-
// Session management
|
|
78
|
-
app.use(
|
|
79
|
-
session({
|
|
80
|
-
secret: process.env.SESSION_SECRET, // From environment, not code
|
|
81
|
-
resave: false,
|
|
82
|
-
saveUninitialized: false,
|
|
83
|
-
cookie: {
|
|
84
|
-
httpOnly: true,
|
|
85
|
-
secure: true,
|
|
86
|
-
sameSite: 'lax',
|
|
87
|
-
maxAge: 24 * 60 * 60 * 1000,
|
|
88
|
-
},
|
|
89
|
-
}),
|
|
90
|
-
);
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### 3. Cross-Site Scripting (XSS)
|
|
94
|
-
|
|
95
|
-
See [security-baseline § Output & Rendering](../../../rules/security-baseline.md#output--rendering).
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
// BAD: Rendering user input as HTML
|
|
99
|
-
element.innerHTML = userInput;
|
|
100
|
-
|
|
101
|
-
// GOOD: Use framework auto-escaping (React does this by default)
|
|
102
|
-
return <div>{userInput}</div>;
|
|
103
|
-
|
|
104
|
-
// If you MUST render HTML, sanitize first
|
|
105
|
-
import DOMPurify from 'dompurify';
|
|
106
|
-
const clean = DOMPurify.sanitize(userInput);
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### 4. Broken Access Control
|
|
110
|
-
|
|
111
|
-
See [security-baseline § Authorization](../../../rules/security-baseline.md#authorization).
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
app.patch('/api/tasks/:id', authenticate, async (req, res) => {
|
|
115
|
-
const task = await taskService.findById(req.params.id);
|
|
116
|
-
|
|
117
|
-
if (task.ownerId !== req.user.id) {
|
|
118
|
-
return res.status(403).json({
|
|
119
|
-
error: {
|
|
120
|
-
code: 'FORBIDDEN',
|
|
121
|
-
message: 'Not authorized to modify this task',
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const updated = await taskService.update(req.params.id, req.body);
|
|
127
|
-
return res.json(updated);
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### 5. Security Misconfiguration
|
|
132
|
-
|
|
133
|
-
See [security-baseline § Transport & Headers](../../../rules/security-baseline.md#transport--headers).
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
import helmet from 'helmet';
|
|
137
|
-
app.use(helmet());
|
|
138
|
-
|
|
139
|
-
app.use(
|
|
140
|
-
helmet.contentSecurityPolicy({
|
|
141
|
-
directives: {
|
|
142
|
-
defaultSrc: ["'self'"],
|
|
143
|
-
scriptSrc: ["'self'"],
|
|
144
|
-
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
145
|
-
imgSrc: ["'self'", 'data:', 'https:'],
|
|
146
|
-
connectSrc: ["'self'"],
|
|
147
|
-
},
|
|
148
|
-
}),
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
app.use(
|
|
152
|
-
cors({
|
|
153
|
-
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
|
|
154
|
-
credentials: true,
|
|
155
|
-
}),
|
|
156
|
-
);
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### 6. Sensitive Data Exposure
|
|
160
|
-
|
|
161
|
-
See [security-baseline § Output & Rendering](../../../rules/security-baseline.md#output--rendering)
|
|
162
|
-
and [§ Secrets Management](../../../rules/security-baseline.md#secrets-management).
|
|
37
|
+
## Validation-Error Responses
|
|
163
38
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
## Input Validation Patterns
|
|
175
|
-
|
|
176
|
-
See [security-baseline § Input Validation](../../../rules/security-baseline.md#input-validation).
|
|
177
|
-
|
|
178
|
-
### Schema Validation at Boundaries
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
import { z } from 'zod';
|
|
182
|
-
|
|
183
|
-
const CreateTaskSchema = z.object({
|
|
184
|
-
title: z.string().min(1).max(200).trim(),
|
|
185
|
-
description: z.string().max(2000).optional(),
|
|
186
|
-
priority: z.enum(['low', 'medium', 'high']).default('medium'),
|
|
187
|
-
dueDate: z.string().datetime().optional(),
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
app.post('/api/tasks', async (req, res) => {
|
|
191
|
-
const result = CreateTaskSchema.safeParse(req.body);
|
|
192
|
-
if (!result.success) {
|
|
193
|
-
return res.status(422).json({
|
|
194
|
-
error: {
|
|
195
|
-
code: 'VALIDATION_ERROR',
|
|
196
|
-
message: 'Invalid input',
|
|
197
|
-
details: result.error.flatten(),
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
const task = await taskService.create(result.data);
|
|
202
|
-
return res.status(201).json(task);
|
|
203
|
-
});
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### File Upload Safety
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
|
|
210
|
-
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
|
|
211
|
-
|
|
212
|
-
function validateUpload(file: UploadedFile) {
|
|
213
|
-
if (!ALLOWED_TYPES.includes(file.mimetype)) {
|
|
214
|
-
throw new ValidationError('File type not allowed');
|
|
215
|
-
}
|
|
216
|
-
if (file.size > MAX_SIZE) {
|
|
217
|
-
throw new ValidationError('File too large (max 5MB)');
|
|
218
|
-
}
|
|
219
|
-
// Don't trust the file extension — check magic bytes if critical
|
|
220
|
-
}
|
|
221
|
-
```
|
|
39
|
+
The status code and response envelope for a failed input validation are owned
|
|
40
|
+
by the wire-format SSOT, not by this skill: validation failures MUST return
|
|
41
|
+
**400 Bad Request** with `error.code = "VALIDATION_ERROR"` in the canonical
|
|
42
|
+
envelope. See
|
|
43
|
+
[`api-conventions.md` § Validation Status](../../../rules/api-conventions.md#validation-status)
|
|
44
|
+
and [§ Response Envelope](../../../rules/api-conventions.md#response-envelope).
|
|
45
|
+
Do not hand-roll a divergent status (e.g. 422) or envelope shape in
|
|
46
|
+
security-relevant handlers — cite the rule and reuse its shape, keeping the
|
|
47
|
+
security skill and the api skill in agreement.
|
|
222
48
|
|
|
223
49
|
## Triaging npm audit Results
|
|
224
50
|
|
|
@@ -250,57 +76,6 @@ npm audit reports a vulnerability
|
|
|
250
76
|
|
|
251
77
|
When you defer a fix, document the reason and set a review date.
|
|
252
78
|
|
|
253
|
-
## Rate Limiting
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
import rateLimit from 'express-rate-limit';
|
|
257
|
-
|
|
258
|
-
// General API rate limit
|
|
259
|
-
app.use(
|
|
260
|
-
'/api/',
|
|
261
|
-
rateLimit({
|
|
262
|
-
windowMs: 15 * 60 * 1000,
|
|
263
|
-
max: 100,
|
|
264
|
-
standardHeaders: true,
|
|
265
|
-
legacyHeaders: false,
|
|
266
|
-
}),
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
// Stricter limit for auth endpoints (the rule MUSTs rate-limiting on auth)
|
|
270
|
-
app.use(
|
|
271
|
-
'/api/auth/',
|
|
272
|
-
rateLimit({
|
|
273
|
-
windowMs: 15 * 60 * 1000,
|
|
274
|
-
max: 10,
|
|
275
|
-
}),
|
|
276
|
-
);
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
## Secrets Management Layout
|
|
280
|
-
|
|
281
|
-
See [security-baseline § Secrets Management](../../../rules/security-baseline.md#secrets-management).
|
|
282
|
-
|
|
283
|
-
```text
|
|
284
|
-
.env files:
|
|
285
|
-
├── .env.example → Committed (template with placeholder values)
|
|
286
|
-
├── .env → NOT committed (contains real secrets)
|
|
287
|
-
└── .env.local → NOT committed (local overrides)
|
|
288
|
-
|
|
289
|
-
.gitignore must include:
|
|
290
|
-
.env
|
|
291
|
-
.env.local
|
|
292
|
-
.env.*.local
|
|
293
|
-
*.pem
|
|
294
|
-
*.key
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
**Always check before committing:**
|
|
298
|
-
|
|
299
|
-
```bash
|
|
300
|
-
# Check for accidentally staged secrets
|
|
301
|
-
git diff --cached | grep -i "password\|secret\|api_key\|token"
|
|
302
|
-
```
|
|
303
|
-
|
|
304
79
|
## Security Review Checklist
|
|
305
80
|
|
|
306
81
|
Use this when reviewing your own change before requesting human review. Each
|
|
@@ -340,36 +115,3 @@ item maps to a section in
|
|
|
340
115
|
- [ ] Dependencies audited for vulnerabilities
|
|
341
116
|
- [ ] Error messages don't expose internals
|
|
342
117
|
```
|
|
343
|
-
|
|
344
|
-
## Common Rationalizations
|
|
345
|
-
|
|
346
|
-
| Rationalization | Reality |
|
|
347
|
-
| --------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
348
|
-
| "This is an internal tool, security doesn't matter" | Internal tools get compromised. Attackers target the weakest link. |
|
|
349
|
-
| "We'll add security later" | Security retrofitting is 10x harder than building it in. Add it now. |
|
|
350
|
-
| "No one would try to exploit this" | Automated scanners will find it. Security by obscurity is not security. |
|
|
351
|
-
| "The framework handles security" | Frameworks provide tools, not guarantees. You still need to use them correctly. |
|
|
352
|
-
| "It's just a prototype" | Prototypes become production. Security habits from day one. |
|
|
353
|
-
|
|
354
|
-
## Red Flags
|
|
355
|
-
|
|
356
|
-
- User input passed directly to database queries, shell commands, or HTML
|
|
357
|
-
rendering
|
|
358
|
-
- Secrets in source code or commit history
|
|
359
|
-
- API endpoints without authentication or authorization checks
|
|
360
|
-
- Missing CORS configuration or wildcard (`*`) origins
|
|
361
|
-
- No rate limiting on authentication endpoints
|
|
362
|
-
- Stack traces or internal errors exposed to users
|
|
363
|
-
- Dependencies with known critical vulnerabilities
|
|
364
|
-
|
|
365
|
-
## Verification
|
|
366
|
-
|
|
367
|
-
After implementing security-relevant code, confirm against the rule:
|
|
368
|
-
|
|
369
|
-
- [ ] `npm audit` shows no critical or high vulnerabilities
|
|
370
|
-
- [ ] No secrets in source code or git history
|
|
371
|
-
- [ ] All user input validated at system boundaries
|
|
372
|
-
- [ ] Authentication and authorization checked on every protected endpoint
|
|
373
|
-
- [ ] Security headers present in response (check with browser DevTools)
|
|
374
|
-
- [ ] Error responses don't expose internal details
|
|
375
|
-
- [ ] Rate limiting active on auth endpoints
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-07-
|
|
2
|
+
"generatedAt": "2026-07-21T13:53:23.232Z",
|
|
3
3
|
"generator": "generate-skills-index.js@1",
|
|
4
4
|
"skills": [
|
|
5
5
|
{
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"category": "core",
|
|
79
79
|
"path": ".agents/skills/core/git-workflow-and-versioning/SKILL.md",
|
|
80
80
|
"description": "Structures git workflow practices. Use when making any code change. Use when committing, branching, resolving conflicts, or when you need to organize work across multiple parallel streams.",
|
|
81
|
-
"policyCapsuleBullets":
|
|
81
|
+
"policyCapsuleBullets": 6,
|
|
82
82
|
"allowedTools": null,
|
|
83
83
|
"vendor": null
|
|
84
84
|
},
|
|
@@ -117,8 +117,8 @@
|
|
|
117
117
|
"tier": "core",
|
|
118
118
|
"category": "core",
|
|
119
119
|
"path": ".agents/skills/core/security-and-hardening/SKILL.md",
|
|
120
|
-
"description": "Hardens code against vulnerabilities. Use when handling user input, authentication, data storage, or external integrations. The non-negotiable security MUSTs live in `.agents/rules/security-baseline.md`; this skill shows how to apply them with
|
|
121
|
-
"policyCapsuleBullets":
|
|
120
|
+
"description": "Hardens code against vulnerabilities. Use when handling user input, authentication, data storage, or external integrations. The non-negotiable security MUSTs live in `.agents/rules/security-baseline.md`; this skill shows how to apply them with process guidance, an audit-triage tree, and a review checklist.",
|
|
121
|
+
"policyCapsuleBullets": 6,
|
|
122
122
|
"allowedTools": null,
|
|
123
123
|
"vendor": null
|
|
124
124
|
},
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
"category": "qa",
|
|
169
169
|
"path": ".agents/skills/stack/qa/qa-harness/SKILL.md",
|
|
170
170
|
"description": "Conventions for the agent-driven QA harness that drives Gherkin scenarios through a real browser. Use when executing `/qa-run` or instrumenting a live surface — covers navigation-first execution, per-surface console and network capture, design-token visual checks, and the framework-generic heuristic cards for turning signal into findings. The harness procedure lives in `.agents/workflows/qa-run.md`; this skill is the conventions reference it leans on.",
|
|
171
|
-
"policyCapsuleBullets":
|
|
171
|
+
"policyCapsuleBullets": 7,
|
|
172
172
|
"allowedTools": null,
|
|
173
173
|
"vendor": null
|
|
174
174
|
},
|
|
@@ -21,234 +21,28 @@ description:
|
|
|
21
21
|
- Author one scenario per PRD acceptance criterion; for bounded matrices, use a Scenario Outline with ≤12 Examples rows.
|
|
22
22
|
- Use third-person present-tense, role-qualified actors (`the billing-admin`), never first person.
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
library. The enforcement rules — tag taxonomy, forbidden patterns, Outline
|
|
27
|
-
conventions, selector discipline, step reuse — live in
|
|
24
|
+
The enforcement rules — tag taxonomy, forbidden patterns, Outline conventions,
|
|
25
|
+
selector discipline, step reuse — live in
|
|
28
26
|
[`.agents/rules/gherkin-standards.md`](../../../../rules/gherkin-standards.md),
|
|
29
27
|
which is the SSOT. This skill shows authors **how** to apply those rules; read
|
|
30
|
-
the rule
|
|
31
|
-
covered by [`playwright-bdd`](../playwright-bdd/SKILL.md); test-layer scope is
|
|
32
|
-
covered by
|
|
33
|
-
[`testing-standards.md`](../../../../rules/testing-standards.md).
|
|
28
|
+
the rule for the **what**.
|
|
34
29
|
|
|
35
|
-
##
|
|
30
|
+
## Long-form reference — read on demand
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
The worked authoring material — canonical Given/When/Then phrasing, the
|
|
33
|
+
one-AC-to-one-scenario translation walkthrough, Background-vs-Given and
|
|
34
|
+
Outline-vs-multi-scenario decisions, the step-definition library layout and
|
|
35
|
+
reuse/deprecation workflow, and the pre-PR authoring checklist — lives in the
|
|
36
|
+
on-demand sibling [`reference.md`](reference.md). Open a section only when the
|
|
37
|
+
task engages it.
|
|
39
38
|
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
event. Splitting a When into multiple clauses is almost always wrong — move
|
|
46
|
-
the extra clauses into Given.
|
|
47
|
-
- Good: `When they issue the invoice`.
|
|
48
|
-
- Bad: `When they log in and issue the invoice and download the PDF`.
|
|
49
|
-
- **Then** — an observable, user-visible outcome. Assert what the *user* sees
|
|
50
|
-
or what the *business* records, not what the system emits internally.
|
|
51
|
-
- Good: `Then the invoice appears in the issued invoices list`.
|
|
52
|
-
- Bad: `Then a 201 is returned` (forbidden; see
|
|
53
|
-
[gherkin-standards § Forbidden Patterns](../../../../rules/gherkin-standards.md#forbidden-patterns)).
|
|
54
|
-
- **And / But** — continuation clauses. They inherit the mood of the most
|
|
55
|
-
recent Given/When/Then. Never start a scenario with And/But.
|
|
39
|
+
- [Canonical Given / When / Then Phrasing](reference.md#canonical-given--when--then-phrasing)
|
|
40
|
+
- [Translating PRD Acceptance Criteria to Scenarios](reference.md#translating-prd-acceptance-criteria-to-scenarios)
|
|
41
|
+
- [Background vs. Given, Outline vs. Multi-Scenario](reference.md#background-vs-given-outline-vs-multi-scenario)
|
|
42
|
+
- [Step-Definition Library Structure](reference.md#step-definition-library-structure)
|
|
43
|
+
- [Authoring Checklist](reference.md#authoring-checklist)
|
|
56
44
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- Third-person, present tense. `the user`, `an admin`, `a billing-admin`
|
|
60
|
-
(role-qualified nouns, not "I" or "we").
|
|
61
|
-
- One clause, one fact. Comma-chained facts hide compound assertions.
|
|
62
|
-
- Numbers and identifiers go in `Examples` tables or fixtures, not inline
|
|
63
|
-
prose. Prose should read the same whether the underlying fixture has 1 or
|
|
64
|
-
10,000 rows.
|
|
65
|
-
|
|
66
|
-
## 2. Translating PRD Acceptance Criteria to Scenarios
|
|
67
|
-
|
|
68
|
-
PRD ACs are the raw material. The authoring move is **one AC → one scenario**
|
|
69
|
-
unless the AC encodes a matrix (in which case it becomes a Scenario Outline —
|
|
70
|
-
see §3).
|
|
71
|
-
|
|
72
|
-
Walkthrough — a PRD AC from a billing feature:
|
|
73
|
-
|
|
74
|
-
> **AC-3:** When a billing-admin issues an invoice for a customer with a
|
|
75
|
-
> negative balance, the system rejects the issue and shows an error naming the
|
|
76
|
-
> outstanding amount.
|
|
77
|
-
|
|
78
|
-
Translation steps:
|
|
79
|
-
|
|
80
|
-
1. **Identify the domain.** Billing → tag the scenario `@domain-billing`.
|
|
81
|
-
2. **Identify the actor and precondition.** "billing-admin", "customer with a
|
|
82
|
-
negative balance" → two Givens.
|
|
83
|
-
3. **Identify the single action.** "issues an invoice" → one When.
|
|
84
|
-
4. **Identify the observable outcome.** "rejects the issue and shows an error
|
|
85
|
-
naming the outstanding amount" → two Thens. The first asserts the business
|
|
86
|
-
outcome (rejection); the second asserts the user-visible detail (error
|
|
87
|
-
names the amount).
|
|
88
|
-
5. **Pick the risk tag.** If the originating ticket was `risk::high`, add
|
|
89
|
-
`@risk-high`. Smoke tag only if this is a critical path on every PR.
|
|
90
|
-
|
|
91
|
-
Resulting scenario:
|
|
92
|
-
|
|
93
|
-
```gherkin
|
|
94
|
-
@domain-billing @risk-high
|
|
95
|
-
Scenario: Issuing an invoice is rejected when the customer has a negative balance
|
|
96
|
-
Given a signed-in billing-admin
|
|
97
|
-
And a customer with a negative account balance
|
|
98
|
-
When the billing-admin issues an invoice for that customer
|
|
99
|
-
Then the issue is rejected
|
|
100
|
-
And the rejection message names the outstanding amount
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Heuristics:
|
|
104
|
-
|
|
105
|
-
- If a single AC needs more than **one When**, it is really two ACs. Split
|
|
106
|
-
before you write.
|
|
107
|
-
- If a Then starts with "and the database has…" or "and the API returned…",
|
|
108
|
-
you have crossed into step-definition or contract-test territory. Rewrite
|
|
109
|
-
it as user-visible language or move the assertion to a contract test (see
|
|
110
|
-
[testing-standards](../../../../rules/testing-standards.md)).
|
|
111
|
-
- If an AC says "the system logs X", that's an engineering non-functional
|
|
112
|
-
requirement — not a BDD scenario. Cover it with a focused unit or
|
|
113
|
-
integration test, not a `.feature` file.
|
|
114
|
-
- When a PRD AC is phrased negatively ("non-admins cannot export"), prefer a
|
|
115
|
-
Scenario Outline if there are multiple negative roles; write a plain
|
|
116
|
-
Scenario if there is exactly one.
|
|
117
|
-
|
|
118
|
-
## 3. Background vs. Given — and Outline vs. Multi-Scenario
|
|
119
|
-
|
|
120
|
-
Two authoring decisions that authors routinely get wrong.
|
|
121
|
-
|
|
122
|
-
### 3.1 Background vs. Given
|
|
123
|
-
|
|
124
|
-
`Background` runs before **every** scenario in the file. Use it only when:
|
|
125
|
-
|
|
126
|
-
- Every scenario in the file genuinely shares the precondition, **and**
|
|
127
|
-
- The precondition has no per-scenario variation (no `<placeholders>`,
|
|
128
|
-
no per-scenario data).
|
|
129
|
-
|
|
130
|
-
Prefer a per-scenario Given when:
|
|
131
|
-
|
|
132
|
-
- Only some scenarios need the precondition. A Background that applies to
|
|
133
|
-
four out of five scenarios is a Background for none of them; move it.
|
|
134
|
-
- The precondition varies by role, plan tier, or fixture shape. Variation
|
|
135
|
-
belongs in a Scenario Outline's Examples, not in Background.
|
|
136
|
-
- Readers cannot understand the scenario without scrolling up to read
|
|
137
|
-
Background. Background is a shortcut; if it hurts readability, inline it.
|
|
138
|
-
|
|
139
|
-
Rule of thumb: if you find yourself writing *"except in the admin scenario,
|
|
140
|
-
where the Background step is actually…"*, delete the Background.
|
|
141
|
-
|
|
142
|
-
### 3.2 Scenario Outline vs. multiple Scenarios
|
|
143
|
-
|
|
144
|
-
Use `Scenario Outline` when the **same behavior** is exercised across a
|
|
145
|
-
**bounded matrix** (roles, plan tiers, locales). The shape of the scenario —
|
|
146
|
-
Givens, When, Thens — is identical; only the data varies.
|
|
147
|
-
|
|
148
|
-
Use multiple `Scenario` blocks when:
|
|
149
|
-
|
|
150
|
-
- The Givens differ structurally (not just in value).
|
|
151
|
-
- The When verb differs.
|
|
152
|
-
- The Then outcomes differ in kind, not just in value. "Delivered vs.
|
|
153
|
-
denied" is one outcome column (see Outline skeleton in
|
|
154
|
-
[gherkin-standards § Scenario Outline Conventions](../../../../rules/gherkin-standards.md#scenario-outline-conventions)).
|
|
155
|
-
"Email is sent vs. invoice is issued vs. account is suspended" are three
|
|
156
|
-
different scenarios.
|
|
157
|
-
|
|
158
|
-
Scale guardrails (also in gherkin-standards):
|
|
159
|
-
|
|
160
|
-
- Keep `Examples` tables under ~12 rows. Larger tables hide distinct
|
|
161
|
-
behaviors behind a shared skeleton.
|
|
162
|
-
- Split `Examples` by tag when rows need different tags (e.g. `@risk-high`
|
|
163
|
-
on admin rows only).
|
|
164
|
-
|
|
165
|
-
## 4. Step-Definition Library Structure
|
|
166
|
-
|
|
167
|
-
Authoring scenarios and maintaining steps are the same job split across two
|
|
168
|
-
files. The library layout below keeps that coupling visible.
|
|
169
|
-
|
|
170
|
-
### 4.1 Layout
|
|
171
|
-
|
|
172
|
-
```text
|
|
173
|
-
tests/
|
|
174
|
-
steps/
|
|
175
|
-
_common/ # actor, auth, navigation — reused across every domain
|
|
176
|
-
auth.steps.ts
|
|
177
|
-
navigation.steps.ts
|
|
178
|
-
billing/ # one directory per @domain-* tag
|
|
179
|
-
invoices.steps.ts
|
|
180
|
-
subscriptions.steps.ts
|
|
181
|
-
auth/
|
|
182
|
-
signin.steps.ts
|
|
183
|
-
_deprecated/ # steps pending migration; see §4.4
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
- One directory per `@domain-*` tag. `_common/` holds cross-cutting steps
|
|
187
|
-
(sign-in, navigation, generic waits handled at the fixture layer).
|
|
188
|
-
- Files are named by the noun the steps act on
|
|
189
|
-
(`invoices.steps.ts`, not `billing-steps-1.ts`). A new noun is a new file.
|
|
190
|
-
- Avoid deep nesting. Two levels (`steps/<domain>/<noun>.steps.ts`) is the
|
|
191
|
-
ceiling for most projects.
|
|
192
|
-
|
|
193
|
-
### 4.2 Naming
|
|
194
|
-
|
|
195
|
-
Step text follows the scenario text verbatim — if the scenario reads `the
|
|
196
|
-
invoice appears in the issued invoices list`, the step regex matches exactly
|
|
197
|
-
that phrase. Divergence between scenario prose and step text is a bug.
|
|
198
|
-
|
|
199
|
-
- Parameterize only over values that vary across scenarios. A step that
|
|
200
|
-
accepts `{string}` for a literal that is always the same value is over-
|
|
201
|
-
parameterized; bake the constant in and rename the step.
|
|
202
|
-
- Role-qualified actors (`{actor}`) read better than generic `{string}`.
|
|
203
|
-
Define a custom parameter type that resolves `account-owner`, `billing-admin`,
|
|
204
|
-
`viewer` to fixtures.
|
|
205
|
-
- Keep step implementations ≤20 lines. Longer implementations indicate a
|
|
206
|
-
missing helper (domain fixture, page object, API client).
|
|
207
|
-
|
|
208
|
-
### 4.3 Reuse Before Authoring
|
|
209
|
-
|
|
210
|
-
The non-negotiable workflow is in
|
|
211
|
-
[gherkin-standards § Step Reuse](../../../../rules/gherkin-standards.md#step-reuse--grep-before-you-write):
|
|
212
|
-
grep the step tree for the verb stem before writing anything new. In practice:
|
|
213
|
-
|
|
214
|
-
1. Search for the verb: `rg -n "issues? an invoice" tests/steps`.
|
|
215
|
-
2. If the phrase exists, **change your scenario** to use that phrase. Do not
|
|
216
|
-
fork a near-duplicate step.
|
|
217
|
-
3. If a near-match exists, extend the existing step (add a parameter, widen
|
|
218
|
-
the regex) and update every call site in the same PR.
|
|
219
|
-
4. Only when no reasonable match exists, add a new step in the correct
|
|
220
|
-
domain directory.
|
|
221
|
-
|
|
222
|
-
### 4.4 Deprecation
|
|
223
|
-
|
|
224
|
-
When a step is superseded:
|
|
225
|
-
|
|
226
|
-
1. Move the old definition into `steps/_deprecated/` and annotate it with a
|
|
227
|
-
one-line comment naming the replacement.
|
|
228
|
-
2. Migrate every call site to the replacement in the same PR.
|
|
229
|
-
3. Delete the `_deprecated/` entry when the migration lands.
|
|
230
|
-
|
|
231
|
-
Never leave two live step definitions that mean the same thing. Parallel
|
|
232
|
-
vocabularies rot the suite faster than any other source of maintenance cost.
|
|
233
|
-
|
|
234
|
-
## 5. Authoring Checklist
|
|
235
|
-
|
|
236
|
-
Before opening a PR that adds or edits a `.feature` file:
|
|
237
|
-
|
|
238
|
-
- [ ] Every Scenario/Outline carries exactly one `@domain-*` tag.
|
|
239
|
-
- [ ] No forbidden patterns (SQL, status codes, selectors, URLs, payloads,
|
|
240
|
-
framework names, explicit waits) appear in prose.
|
|
241
|
-
- [ ] Each scenario has exactly one `When`.
|
|
242
|
-
- [ ] `Then` clauses assert user-visible outcomes, not implementation.
|
|
243
|
-
- [ ] `Background` is justified (applies to every scenario in the file).
|
|
244
|
-
- [ ] If a Scenario Outline is used, the matrix is bounded (≤12 rows) and the
|
|
245
|
-
shape is truly identical across rows.
|
|
246
|
-
- [ ] Every step phrase grep-matches an existing step definition **or** is
|
|
247
|
-
accompanied by a new step definition in the right domain directory.
|
|
248
|
-
- [ ] Reads standalone — a product reader who has never seen the codebase can
|
|
249
|
-
understand the intent without opening a step file.
|
|
250
|
-
|
|
251
|
-
## 6. Cross-References
|
|
45
|
+
## Cross-References
|
|
252
46
|
|
|
253
47
|
- SSOT rules: [`.agents/rules/gherkin-standards.md`](../../../../rules/gherkin-standards.md).
|
|
254
48
|
- Runtime wiring: [`playwright-bdd`](../playwright-bdd/SKILL.md).
|