ai-test-guardrails 0.2.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/.cursor/mcp.json +8 -0
- package/.prettierrc +8 -0
- package/README.md +438 -0
- package/dist/config/defaultRules.d.ts +28 -0
- package/dist/config/defaultRules.d.ts.map +1 -0
- package/dist/config/defaultRules.js +24 -0
- package/dist/config/defaultRules.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +190 -0
- package/dist/server.js.map +1 -0
- package/dist/types/guardrail.types.d.ts +106 -0
- package/dist/types/guardrail.types.d.ts.map +1 -0
- package/dist/types/guardrail.types.js +2 -0
- package/dist/types/guardrail.types.js.map +1 -0
- package/dist/utils/astParser.d.ts +15 -0
- package/dist/utils/astParser.d.ts.map +1 -0
- package/dist/utils/astParser.js +142 -0
- package/dist/utils/astParser.js.map +1 -0
- package/dist/utils/enforcement.d.ts +16 -0
- package/dist/utils/enforcement.d.ts.map +1 -0
- package/dist/utils/enforcement.js +74 -0
- package/dist/utils/enforcement.js.map +1 -0
- package/dist/utils/frameworkDetector.d.ts +8 -0
- package/dist/utils/frameworkDetector.d.ts.map +1 -0
- package/dist/utils/frameworkDetector.js +38 -0
- package/dist/utils/frameworkDetector.js.map +1 -0
- package/dist/utils/projectScanner.d.ts +17 -0
- package/dist/utils/projectScanner.d.ts.map +1 -0
- package/dist/utils/projectScanner.js +162 -0
- package/dist/utils/projectScanner.js.map +1 -0
- package/dist/validators/architecture.d.ts +5 -0
- package/dist/validators/architecture.d.ts.map +1 -0
- package/dist/validators/architecture.js +103 -0
- package/dist/validators/architecture.js.map +1 -0
- package/dist/validators/determinism.d.ts +5 -0
- package/dist/validators/determinism.d.ts.map +1 -0
- package/dist/validators/determinism.js +166 -0
- package/dist/validators/determinism.js.map +1 -0
- package/dist/validators/flakeRisk.d.ts +5 -0
- package/dist/validators/flakeRisk.d.ts.map +1 -0
- package/dist/validators/flakeRisk.js +94 -0
- package/dist/validators/flakeRisk.js.map +1 -0
- package/eslint.config.js +25 -0
- package/examples/playwright/.auth/care-admin-api-rest-dev-0.json +1 -0
- package/examples/playwright/.auth/care-admin-api-rest-stage-0.json +1 -0
- package/examples/playwright/.auth/care-admin-api-soap-dev-0.json +1 -0
- package/examples/playwright/.auth/care-admin-api-soap-stage-0.json +1 -0
- package/examples/playwright/.auth/care-admin-dev-0.json +1 -0
- package/examples/playwright/.auth/care-admin-stage-0-data.json +1 -0
- package/examples/playwright/.auth/care-admin-stage-0.json +1 -0
- package/examples/playwright/.auth/care-admin-stage-1.json +1 -0
- package/examples/playwright/.auth/care-admin-stage-2.json +1 -0
- package/examples/playwright/.auth/care-community-dev-0-data.json +3 -0
- package/examples/playwright/.auth/care-community-dev-0.json +15 -0
- package/examples/playwright/.auth/care-community-stage-0.json +1 -0
- package/examples/playwright/care-admin-base.ts +66 -0
- package/examples/playwright/care-community-base.ts +65 -0
- package/examples/playwright/constants.ts +3 -0
- package/examples/playwright/pages/better-care/better-care-button.page.ts +15 -0
- package/examples/playwright/pages/better-care/menu.page.ts +14 -0
- package/examples/playwright/pages/care-admin/main.frame.ts +17 -0
- package/examples/playwright/pages/care-admin/nav.frame.ts +9 -0
- package/examples/playwright/pages/care-admin/setup.page.ts +131 -0
- package/examples/playwright/pages/care-admin/study-program.types.ts +339 -0
- package/examples/playwright/pages/care-admin/study-programs.page.ts +213 -0
- package/examples/playwright/pages/care-admin/toolbar.frame.ts +42 -0
- package/examples/playwright/pages/care-community/course-registration.page.ts +43 -0
- package/examples/playwright/pages/care-community/documents.page.ts +82 -0
- package/examples/playwright/pages/care-community/examination-results.page.ts +58 -0
- package/examples/playwright/pages/care-community/nav-bar.page.ts +177 -0
- package/examples/playwright/pages/language.page.ts +111 -0
- package/examples/playwright/pages/language.types.ts +13 -0
- package/examples/playwright/snapshots/care-admin/study-program-search-results.yml +5 -0
- package/examples/playwright.config.ts +180 -0
- package/package.json +55 -0
- package/src/config/defaultRules.ts +53 -0
- package/src/server.ts +274 -0
- package/src/types/guardrail.types.ts +121 -0
- package/src/utils/astParser.ts +167 -0
- package/src/utils/enforcement.ts +123 -0
- package/src/utils/frameworkDetector.ts +52 -0
- package/src/utils/projectScanner.ts +211 -0
- package/src/validators/architecture.ts +131 -0
- package/src/validators/determinism.ts +205 -0
- package/src/validators/flakeRisk.ts +118 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +8 -0
package/.cursor/mcp.json
ADDED
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# ai-test-guardrails
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that provides deterministic guardrails for AI-generated and existing test automation. It validates Playwright and Cypress test proposals using AST-based analysis — detecting flake-prone patterns, enforcing architecture rules, scoring risk, and scanning entire projects in a single call.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
- **Validates** AI-generated test code for determinism, flake risk, and architecture compliance
|
|
8
|
+
- **Classifies** violations as `critical`, `major`, or `minor` for prioritised remediation
|
|
9
|
+
- **Scans** entire project directories, validating every test file in one pass with severity breakdown
|
|
10
|
+
- **Detects** flake-prone constructs: hard sleeps, unbounded retries, unmocked network calls, dynamic selectors
|
|
11
|
+
- **Enforces** architectural rules: page object patterns, selector hygiene, nesting depth limits
|
|
12
|
+
- **Scores** flake risk on a 0–1 scale with detailed factor breakdown
|
|
13
|
+
- **Rejects gracefully** unsupported frameworks (e.g. k6) with a clear, actionable message
|
|
14
|
+
- **Returns** structured JSON results with transparent policy output via the MCP tool protocol
|
|
15
|
+
|
|
16
|
+
## What It Does Not Do
|
|
17
|
+
|
|
18
|
+
- Generate tests
|
|
19
|
+
- Run Playwright or Cypress
|
|
20
|
+
- Replace CI pipelines
|
|
21
|
+
- Modify your repository
|
|
22
|
+
|
|
23
|
+
It is a pure validation and scoring engine.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install
|
|
29
|
+
npm run build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires Node.js >= 20.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### As an MCP Server (stdio)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
node dist/server.js
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Configure in your MCP client. For **Cursor**, add a `.cursor/mcp.json` file to your project root:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"ai-test-guardrails": {
|
|
48
|
+
"command": "node",
|
|
49
|
+
"args": ["/path/to/ai-test-guardrails/dist/server.js"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For **Claude Desktop**, add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"mcpServers": {
|
|
60
|
+
"ai-test-guardrails": {
|
|
61
|
+
"command": "node",
|
|
62
|
+
"args": ["/path/to/ai-test-guardrails/dist/server.js"]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Enforcement Modes
|
|
69
|
+
|
|
70
|
+
All tools support a three-tier enforcement model designed for organisational adoption.
|
|
71
|
+
|
|
72
|
+
| Mode | Action | Use case |
|
|
73
|
+
|------|--------|----------|
|
|
74
|
+
| `"advisory"` | Always `PASSED` or `ADVISED`. Never blocks. | Rollout phase — build trust without friction |
|
|
75
|
+
| `"warn"` *(default)* | `WARNED` if under threshold, `REJECTED` if exceeded | Balanced adoption — enforce policy gradually |
|
|
76
|
+
| `"block"` | `REJECTED` on any violation | Full enforcement once confidence is established |
|
|
77
|
+
|
|
78
|
+
All modes produce identical scores and violation lists. Only `valid` and the `policy.action` field change.
|
|
79
|
+
|
|
80
|
+
### Configurable Thresholds (warn mode)
|
|
81
|
+
|
|
82
|
+
In `warn` mode, enforcement only triggers when detected issues exceed your configured thresholds:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mode": "warn",
|
|
87
|
+
"architectureThreshold": 3,
|
|
88
|
+
"flakeRiskThreshold": 0.7,
|
|
89
|
+
"determinismThreshold": 0
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Threshold | Default | Meaning |
|
|
94
|
+
|-----------|---------|---------|
|
|
95
|
+
| `architectureThreshold` | `3` | Max architecture violations before REJECTED |
|
|
96
|
+
| `flakeRiskThreshold` | `0.7` | Max flake risk score (0–1) before REJECTED |
|
|
97
|
+
| `determinismThreshold` | `0` | Max determinism violations before REJECTED |
|
|
98
|
+
|
|
99
|
+
### Policy Output
|
|
100
|
+
|
|
101
|
+
Every result includes a `policy` block that makes enforcement transparent. As of v0.2, `detected` also includes a severity breakdown:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"valid": false,
|
|
106
|
+
"policy": {
|
|
107
|
+
"mode": "warn",
|
|
108
|
+
"thresholds": { "architectureThreshold": 3, "flakeRiskThreshold": 0.7, "determinismThreshold": 0 },
|
|
109
|
+
"detected": {
|
|
110
|
+
"architectureViolations": 10,
|
|
111
|
+
"flakeRiskScore": 0,
|
|
112
|
+
"determinismViolations": 0,
|
|
113
|
+
"criticalCount": 0,
|
|
114
|
+
"majorCount": 8,
|
|
115
|
+
"minorCount": 2
|
|
116
|
+
},
|
|
117
|
+
"action": "REJECTED",
|
|
118
|
+
"reasons": ["10 architecture violations exceeded threshold of 3"]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This makes it clear the test was rejected because of **policy**, not arbitrary tool behaviour.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## MCP Tools
|
|
128
|
+
|
|
129
|
+
### `scan_project`
|
|
130
|
+
|
|
131
|
+
Scans an entire project directory, validates every test file in one pass, and returns an aggregate summary with per-file results, severity breakdown, project-wide scores, and a ranked list of top offenders.
|
|
132
|
+
|
|
133
|
+
**Input:**
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"projectPath": "/path/to/your/tests",
|
|
138
|
+
"framework": "playwright",
|
|
139
|
+
"mode": "warn",
|
|
140
|
+
"architectureThreshold": 3,
|
|
141
|
+
"flakeRiskThreshold": 0.7,
|
|
142
|
+
"determinismThreshold": 0
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Output:**
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"scannedAt": "2026-02-26T22:12:36.742Z",
|
|
151
|
+
"projectPath": "/path/to/your/tests",
|
|
152
|
+
"framework": "playwright",
|
|
153
|
+
"mode": "warn",
|
|
154
|
+
"thresholds": { "architectureThreshold": 3, "flakeRiskThreshold": 0.7, "determinismThreshold": 0 },
|
|
155
|
+
"totals": {
|
|
156
|
+
"files": 19,
|
|
157
|
+
"passed": 6,
|
|
158
|
+
"warned": 8,
|
|
159
|
+
"rejected": 5,
|
|
160
|
+
"totalViolations": 59,
|
|
161
|
+
"criticalViolations": 12,
|
|
162
|
+
"majorViolations": 30,
|
|
163
|
+
"minorViolations": 17
|
|
164
|
+
},
|
|
165
|
+
"scores": {
|
|
166
|
+
"averageDeterminism": 0.99,
|
|
167
|
+
"averageFlakeRisk": 0.17,
|
|
168
|
+
"averageArchitecture": 0.93
|
|
169
|
+
},
|
|
170
|
+
"topOffenders": [
|
|
171
|
+
{
|
|
172
|
+
"file": "admin/email-sender-restrictor.spec.ts",
|
|
173
|
+
"policy": { "action": "REJECTED", "reasons": ["20 architecture violations exceeded threshold of 3"] },
|
|
174
|
+
"violations": ["..."]
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"files": ["...per-file results..."],
|
|
178
|
+
"unsupportedFiles": [
|
|
179
|
+
{ "file": "perf/loadTest.js", "detectedFramework": "k6" },
|
|
180
|
+
{ "file": "perf/soakTest.js", "detectedFramework": "k6" }
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**File discovery:** The scanner picks up files in two passes:
|
|
186
|
+
1. **Conventional names** — `*.spec.ts/js`, `*.test.ts/js`, `*.cy.ts/js` are always included.
|
|
187
|
+
2. **Framework-detected** — any other `.ts`/`.js` file whose imports identify it as Playwright, Cypress, or an unsupported framework (e.g. k6) is also picked up. Files with no recognised test-framework imports (config files, helpers, etc.) are silently skipped.
|
|
188
|
+
|
|
189
|
+
Directories `node_modules`, `.git`, `dist`, and `coverage` are always ignored.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### `validate_test`
|
|
194
|
+
|
|
195
|
+
Validates a single test file for determinism, flake risk, and architecture compliance. Each violation carries a `severity` (`critical`, `major`, or `minor`), a `rule` identifier, and a `message`.
|
|
196
|
+
|
|
197
|
+
**Input (warn mode with custom thresholds):**
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"testCode": "test('login', async ({ page }) => {\n await page.waitForTimeout(1000);\n await page.locator('.btn').click();\n});",
|
|
202
|
+
"framework": "playwright",
|
|
203
|
+
"mode": "warn",
|
|
204
|
+
"architectureThreshold": 3,
|
|
205
|
+
"flakeRiskThreshold": 0.7,
|
|
206
|
+
"determinismThreshold": 0
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Output — REJECTED:**
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"valid": false,
|
|
215
|
+
"policy": {
|
|
216
|
+
"mode": "warn",
|
|
217
|
+
"thresholds": { "architectureThreshold": 3, "flakeRiskThreshold": 0.7, "determinismThreshold": 0 },
|
|
218
|
+
"detected": {
|
|
219
|
+
"architectureViolations": 1,
|
|
220
|
+
"flakeRiskScore": 0,
|
|
221
|
+
"determinismViolations": 1,
|
|
222
|
+
"criticalCount": 1,
|
|
223
|
+
"majorCount": 1,
|
|
224
|
+
"minorCount": 0
|
|
225
|
+
},
|
|
226
|
+
"action": "REJECTED",
|
|
227
|
+
"reasons": ["1 determinism violations exceeded threshold of 0"]
|
|
228
|
+
},
|
|
229
|
+
"determinismScore": 0.833,
|
|
230
|
+
"flakeRiskScore": 0,
|
|
231
|
+
"architectureScore": 0.75,
|
|
232
|
+
"violations": [
|
|
233
|
+
{
|
|
234
|
+
"severity": "critical",
|
|
235
|
+
"rule": "no-wait-for-timeout",
|
|
236
|
+
"message": "[line 2] waitForTimeout introduces non-deterministic timing. Use waitForSelector or expect assertions instead."
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"severity": "major",
|
|
240
|
+
"rule": "no-raw-selector",
|
|
241
|
+
"message": "[line 3] Direct CSS selector \".btn\" in test code. Extract selectors to page objects and use data-testid or role-based selectors."
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Input (advisory mode):**
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"testCode": "test('login', async ({ page }) => {\n await page.waitForTimeout(1000);\n});",
|
|
252
|
+
"framework": "playwright",
|
|
253
|
+
"mode": "advisory"
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Output — ADVISED (violations surfaced, CI not blocked):**
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
{
|
|
261
|
+
"valid": true,
|
|
262
|
+
"policy": { "mode": "advisory", "action": "ADVISED", "reasons": [] },
|
|
263
|
+
"violations": [
|
|
264
|
+
{ "severity": "critical", "rule": "no-wait-for-timeout", "message": "[line 2] waitForTimeout introduces non-deterministic timing..." }
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
### `score_flake_risk`
|
|
272
|
+
|
|
273
|
+
Analyses the flake risk of a single test file and returns a numeric risk score (0–1) with contributing factors.
|
|
274
|
+
|
|
275
|
+
**Input:**
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"testCode": "test('checkout', async ({ page }) => {\n await page.goto('/cart');\n await page.goto('/checkout');\n const res = await fetch('/api/order');\n});",
|
|
280
|
+
"framework": "playwright"
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Output:**
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"score": 0.45,
|
|
289
|
+
"factors": [
|
|
290
|
+
{ "name": "async-heavy", "weight": 0.15, "detected": false, "description": "High number of async operations increases timing sensitivity" },
|
|
291
|
+
{ "name": "network-dependency", "weight": 0.25, "detected": true, "description": "Network calls without mocking create external dependencies" },
|
|
292
|
+
{ "name": "multiple-navigations", "weight": 0.20, "detected": true, "description": "Multiple navigation steps increase page load timing variability" },
|
|
293
|
+
{ "name": "shared-state", "weight": 0.20, "detected": false, "description": "Module-level mutable state can leak between tests" },
|
|
294
|
+
{ "name": "timing-assertions", "weight": 0.20, "detected": false, "description": "Timing-dependent assertions are sensitive to execution speed" }
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
### `enforce_architecture`
|
|
302
|
+
|
|
303
|
+
Checks a single test file for architectural compliance: page object usage, selector patterns, nesting depth, and duplicate titles. Violations are severity-classified. Supports all three enforcement modes.
|
|
304
|
+
|
|
305
|
+
**Input:**
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"testCode": "let count = 0;\ndescribe('a', () => {\n describe('b', () => {\n describe('c', () => {\n it('test', () => { count++; });\n it('test', () => {});\n });\n });\n});",
|
|
310
|
+
"framework": "playwright"
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Output:**
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"valid": false,
|
|
319
|
+
"policy": {
|
|
320
|
+
"mode": "warn",
|
|
321
|
+
"detected": { "architectureViolations": 3, "criticalCount": 1, "majorCount": 0, "minorCount": 2 },
|
|
322
|
+
"action": "REJECTED",
|
|
323
|
+
"reasons": ["3 architecture violations exceeded threshold of 3"]
|
|
324
|
+
},
|
|
325
|
+
"score": 0,
|
|
326
|
+
"violations": [
|
|
327
|
+
{ "severity": "critical", "rule": "no-global-state", "message": "[line 1] Module-level mutable variable \"count\" can leak state between tests..." },
|
|
328
|
+
{ "severity": "minor", "rule": "no-deep-nesting", "message": "Test nesting depth is 3 (max allowed: 2). Flatten describe blocks..." },
|
|
329
|
+
{ "severity": "minor", "rule": "no-duplicate-title", "message": "Duplicate test title \"test\". Each test should have a unique title..." }
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
### Unsupported Framework Detection
|
|
337
|
+
|
|
338
|
+
If test code imports from an unsupported framework (e.g. k6), tools return a graceful rejection instead of attempting validation:
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"supported": false,
|
|
343
|
+
"detectedFramework": "k6",
|
|
344
|
+
"message": "Framework \"k6\" is not supported by ai-test-guardrails v0.2. Supported frameworks: playwright, cypress.",
|
|
345
|
+
"supportedFrameworks": ["playwright", "cypress"]
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Validation Rules (v0.2)
|
|
352
|
+
|
|
353
|
+
### Determinism Rules
|
|
354
|
+
|
|
355
|
+
| Rule | Severity | Detects |
|
|
356
|
+
|------|----------|---------|
|
|
357
|
+
| `no-wait-for-timeout` | **critical** | `page.waitForTimeout()`, `cy.wait(number)` |
|
|
358
|
+
| `no-hard-sleep` | **critical** | `setTimeout`, `sleep()` |
|
|
359
|
+
| `no-unbounded-retry` | **critical** | `while(true)`, `for(;;)` |
|
|
360
|
+
| `no-random-without-seed` | **major** | `Math.random()` |
|
|
361
|
+
| `no-unmocked-network` | **major** | `fetch()`, `axios.*()` without `route()`/`intercept()` |
|
|
362
|
+
| `no-dynamic-selector` | **major** | Template literals in `locator()`, `cy.get()`, etc. |
|
|
363
|
+
|
|
364
|
+
### Architecture Rules
|
|
365
|
+
|
|
366
|
+
| Rule | Severity | Enforces |
|
|
367
|
+
|------|----------|----------|
|
|
368
|
+
| `no-global-state` | **critical** | No module-level `let`/`var` declarations |
|
|
369
|
+
| `no-raw-selector` | **major** | No direct CSS/ID selectors — use `data-testid` or role-based |
|
|
370
|
+
| `no-deep-nesting` | **minor** | Max 2 levels of `describe`/`context` nesting |
|
|
371
|
+
| `no-duplicate-title` | **minor** | No duplicate `it()`/`test()` titles |
|
|
372
|
+
|
|
373
|
+
### Flake Risk Factors
|
|
374
|
+
|
|
375
|
+
| Factor | Weight | Triggers |
|
|
376
|
+
|--------|--------|----------|
|
|
377
|
+
| Async-heavy | 0.15 | > 5 `await` expressions |
|
|
378
|
+
| Network dependency | 0.25 | `fetch()`, `request()` calls |
|
|
379
|
+
| Multiple navigations | 0.20 | > 1 `goto()`/`visit()` call |
|
|
380
|
+
| Shared state | 0.20 | Module-level `let`/`var` |
|
|
381
|
+
| Timing assertions | 0.20 | `waitForTimeout`, assertions with `timeout` option |
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Development
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
npm run dev # Run server in dev mode (tsx)
|
|
389
|
+
npm run build # Compile TypeScript
|
|
390
|
+
npm test # Run tests
|
|
391
|
+
npm run test:watch # Watch mode
|
|
392
|
+
npm run lint # Lint with ESLint
|
|
393
|
+
npm run format # Format with Prettier
|
|
394
|
+
npm run typecheck # Type-check without emitting
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Project Structure
|
|
398
|
+
|
|
399
|
+
```
|
|
400
|
+
src/
|
|
401
|
+
├── server.ts # MCP server entry point with tool registration
|
|
402
|
+
├── validators/
|
|
403
|
+
│ ├── determinism.ts # Determinism rule checks
|
|
404
|
+
│ ├── architecture.ts # Architecture compliance checks
|
|
405
|
+
│ └── flakeRisk.ts # Flake risk scoring engine
|
|
406
|
+
├── types/
|
|
407
|
+
│ └── guardrail.types.ts # Shared TypeScript interfaces
|
|
408
|
+
├── utils/
|
|
409
|
+
│ ├── astParser.ts # TypeScript compiler API utilities
|
|
410
|
+
│ ├── enforcement.ts # Three-mode policy engine
|
|
411
|
+
│ ├── frameworkDetector.ts # Framework detection from imports (k6, Playwright, Cypress)
|
|
412
|
+
│ └── projectScanner.ts # Two-pass file discovery, classification, and aggregator
|
|
413
|
+
└── config/
|
|
414
|
+
└── defaultRules.ts # Default rule configuration
|
|
415
|
+
tests/
|
|
416
|
+
├── astParser.test.ts
|
|
417
|
+
├── determinism.test.ts
|
|
418
|
+
├── architecture.test.ts
|
|
419
|
+
├── flakeRisk.test.ts
|
|
420
|
+
├── mode.test.ts
|
|
421
|
+
├── projectScanner.test.ts
|
|
422
|
+
└── severity.test.ts
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Roadmap
|
|
426
|
+
|
|
427
|
+
- [x] **v0.1** — Core validation engine, three enforcement modes, project scanning
|
|
428
|
+
- [x] **v0.2** — Violation severity tiers (critical / major / minor), k6 detection, severity breakdown in scan summary, two-pass `.js`/`.ts` file discovery
|
|
429
|
+
- [ ] **v0.3** — Auto-fix suggestions in violation messages
|
|
430
|
+
- [ ] **v0.3** — Cypress-specific selector pattern detection
|
|
431
|
+
- [ ] **v0.4** — Support for custom rule plugins
|
|
432
|
+
- [ ] **v0.4** — CI artefact export (scan results to JSON file)
|
|
433
|
+
- [ ] **v0.5** — Historical flake score tracking
|
|
434
|
+
- [ ] **v1.0** — Stable API with semver guarantees
|
|
435
|
+
|
|
436
|
+
## Licence
|
|
437
|
+
|
|
438
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface DeterminismRuleConfig {
|
|
2
|
+
detectWaitForTimeout: boolean;
|
|
3
|
+
detectHardSleeps: boolean;
|
|
4
|
+
detectRandomWithoutSeed: boolean;
|
|
5
|
+
detectUnboundedRetries: boolean;
|
|
6
|
+
detectUnmockedNetworkCalls: boolean;
|
|
7
|
+
detectDynamicSelectors: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ArchitectureRuleConfig {
|
|
10
|
+
enforcePageObjects: boolean;
|
|
11
|
+
forbidGlobalState: boolean;
|
|
12
|
+
maxDescribeDepth: number;
|
|
13
|
+
forbidDuplicateTestTitles: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface FlakeRiskWeightConfig {
|
|
16
|
+
asyncHeavyWeight: number;
|
|
17
|
+
networkDependencyWeight: number;
|
|
18
|
+
multipleNavigationsWeight: number;
|
|
19
|
+
sharedStateWeight: number;
|
|
20
|
+
timingAssertionsWeight: number;
|
|
21
|
+
}
|
|
22
|
+
export interface RuleConfig {
|
|
23
|
+
determinism: DeterminismRuleConfig;
|
|
24
|
+
architecture: ArchitectureRuleConfig;
|
|
25
|
+
flakeRisk: FlakeRiskWeightConfig;
|
|
26
|
+
}
|
|
27
|
+
export declare const DEFAULT_RULES: RuleConfig;
|
|
28
|
+
//# sourceMappingURL=defaultRules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultRules.d.ts","sourceRoot":"","sources":["../../src/config/defaultRules.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,EAAE,OAAO,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,uBAAuB,EAAE,OAAO,CAAC;IACjC,sBAAsB,EAAE,OAAO,CAAC;IAChC,0BAA0B,EAAE,OAAO,CAAC;IACpC,sBAAsB,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,yBAAyB,EAAE,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB,EAAE,MAAM,CAAC;IAChC,yBAAyB,EAAE,MAAM,CAAC;IAClC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,qBAAqB,CAAC;IACnC,YAAY,EAAE,sBAAsB,CAAC;IACrC,SAAS,EAAE,qBAAqB,CAAC;CAClC;AAED,eAAO,MAAM,aAAa,EAAE,UAsB3B,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const DEFAULT_RULES = {
|
|
2
|
+
determinism: {
|
|
3
|
+
detectWaitForTimeout: true,
|
|
4
|
+
detectHardSleeps: true,
|
|
5
|
+
detectRandomWithoutSeed: true,
|
|
6
|
+
detectUnboundedRetries: true,
|
|
7
|
+
detectUnmockedNetworkCalls: true,
|
|
8
|
+
detectDynamicSelectors: true,
|
|
9
|
+
},
|
|
10
|
+
architecture: {
|
|
11
|
+
enforcePageObjects: true,
|
|
12
|
+
forbidGlobalState: true,
|
|
13
|
+
maxDescribeDepth: 2,
|
|
14
|
+
forbidDuplicateTestTitles: true,
|
|
15
|
+
},
|
|
16
|
+
flakeRisk: {
|
|
17
|
+
asyncHeavyWeight: 0.15,
|
|
18
|
+
networkDependencyWeight: 0.25,
|
|
19
|
+
multipleNavigationsWeight: 0.2,
|
|
20
|
+
sharedStateWeight: 0.2,
|
|
21
|
+
timingAssertionsWeight: 0.2,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=defaultRules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultRules.js","sourceRoot":"","sources":["../../src/config/defaultRules.ts"],"names":[],"mappings":"AA8BA,MAAM,CAAC,MAAM,aAAa,GAAe;IACvC,WAAW,EAAE;QACX,oBAAoB,EAAE,IAAI;QAC1B,gBAAgB,EAAE,IAAI;QACtB,uBAAuB,EAAE,IAAI;QAC7B,sBAAsB,EAAE,IAAI;QAC5B,0BAA0B,EAAE,IAAI;QAChC,sBAAsB,EAAE,IAAI;KAC7B;IACD,YAAY,EAAE;QACZ,kBAAkB,EAAE,IAAI;QACxB,iBAAiB,EAAE,IAAI;QACvB,gBAAgB,EAAE,CAAC;QACnB,yBAAyB,EAAE,IAAI;KAChC;IACD,SAAS,EAAE;QACT,gBAAgB,EAAE,IAAI;QACtB,uBAAuB,EAAE,IAAI;QAC7B,yBAAyB,EAAE,GAAG;QAC9B,iBAAiB,EAAE,GAAG;QACtB,sBAAsB,EAAE,GAAG;KAC5B;CACF,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
|