@vibecheckai/cli 3.5.0 → 3.5.2

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.
Files changed (224) hide show
  1. package/bin/registry.js +214 -237
  2. package/bin/runners/cli-utils.js +33 -2
  3. package/bin/runners/context/analyzer.js +52 -1
  4. package/bin/runners/context/generators/cursor.js +2 -49
  5. package/bin/runners/context/git-context.js +3 -1
  6. package/bin/runners/context/team-conventions.js +33 -7
  7. package/bin/runners/lib/analysis-core.js +25 -5
  8. package/bin/runners/lib/analyzers.js +431 -481
  9. package/bin/runners/lib/default-config.js +127 -0
  10. package/bin/runners/lib/doctor/modules/security.js +3 -1
  11. package/bin/runners/lib/engine/ast-cache.js +210 -0
  12. package/bin/runners/lib/engine/auth-extractor.js +211 -0
  13. package/bin/runners/lib/engine/billing-extractor.js +112 -0
  14. package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
  15. package/bin/runners/lib/engine/env-extractor.js +207 -0
  16. package/bin/runners/lib/engine/express-extractor.js +208 -0
  17. package/bin/runners/lib/engine/extractors.js +849 -0
  18. package/bin/runners/lib/engine/index.js +207 -0
  19. package/bin/runners/lib/engine/repo-index.js +514 -0
  20. package/bin/runners/lib/engine/types.js +124 -0
  21. package/bin/runners/lib/engines/accessibility-engine.js +18 -218
  22. package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
  23. package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
  24. package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
  25. package/bin/runners/lib/engines/mock-data-engine.js +10 -53
  26. package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
  27. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
  28. package/bin/runners/lib/engines/type-aware-engine.js +39 -263
  29. package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
  30. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  31. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  32. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  33. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  34. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  35. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  36. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  37. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
  38. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  39. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  40. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  41. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  42. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  43. package/bin/runners/lib/entitlements-v2.js +73 -97
  44. package/bin/runners/lib/error-handler.js +44 -3
  45. package/bin/runners/lib/error-messages.js +289 -0
  46. package/bin/runners/lib/evidence-pack.js +7 -1
  47. package/bin/runners/lib/finding-id.js +69 -0
  48. package/bin/runners/lib/finding-sorter.js +89 -0
  49. package/bin/runners/lib/html-proof-report.js +700 -350
  50. package/bin/runners/lib/missions/plan.js +6 -46
  51. package/bin/runners/lib/missions/templates.js +0 -232
  52. package/bin/runners/lib/next-action.js +560 -0
  53. package/bin/runners/lib/prerequisites.js +149 -0
  54. package/bin/runners/lib/route-detection.js +137 -68
  55. package/bin/runners/lib/scan-output.js +91 -76
  56. package/bin/runners/lib/scan-runner.js +135 -0
  57. package/bin/runners/lib/schemas/ajv-validator.js +464 -0
  58. package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
  59. package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
  60. package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
  61. package/bin/runners/lib/schemas/run-request.schema.json +108 -0
  62. package/bin/runners/lib/schemas/validator.js +27 -0
  63. package/bin/runners/lib/schemas/verdict.schema.json +140 -0
  64. package/bin/runners/lib/ship-output-enterprise.js +23 -23
  65. package/bin/runners/lib/ship-output.js +75 -31
  66. package/bin/runners/lib/terminal-ui.js +6 -113
  67. package/bin/runners/lib/truth.js +351 -10
  68. package/bin/runners/lib/unified-cli-output.js +430 -603
  69. package/bin/runners/lib/unified-output.js +13 -9
  70. package/bin/runners/runAIAgent.js +10 -5
  71. package/bin/runners/runAgent.js +0 -3
  72. package/bin/runners/runAllowlist.js +389 -0
  73. package/bin/runners/runApprove.js +0 -33
  74. package/bin/runners/runAuth.js +73 -45
  75. package/bin/runners/runCheckpoint.js +51 -11
  76. package/bin/runners/runClassify.js +85 -21
  77. package/bin/runners/runContext.js +0 -3
  78. package/bin/runners/runDoctor.js +41 -28
  79. package/bin/runners/runEvidencePack.js +362 -0
  80. package/bin/runners/runFirewall.js +0 -3
  81. package/bin/runners/runFirewallHook.js +0 -3
  82. package/bin/runners/runFix.js +66 -76
  83. package/bin/runners/runGuard.js +18 -411
  84. package/bin/runners/runInit.js +113 -30
  85. package/bin/runners/runLabs.js +424 -0
  86. package/bin/runners/runMcp.js +19 -25
  87. package/bin/runners/runPolish.js +64 -240
  88. package/bin/runners/runPromptFirewall.js +12 -5
  89. package/bin/runners/runProve.js +57 -22
  90. package/bin/runners/runQuickstart.js +531 -0
  91. package/bin/runners/runReality.js +59 -68
  92. package/bin/runners/runReport.js +38 -33
  93. package/bin/runners/runRuntime.js +8 -5
  94. package/bin/runners/runScan.js +1413 -190
  95. package/bin/runners/runShip.js +113 -719
  96. package/bin/runners/runTruth.js +0 -3
  97. package/bin/runners/runValidate.js +13 -9
  98. package/bin/runners/runWatch.js +23 -14
  99. package/bin/scan.js +6 -1
  100. package/bin/vibecheck.js +204 -185
  101. package/mcp-server/deprecation-middleware.js +282 -0
  102. package/mcp-server/handlers/index.ts +15 -0
  103. package/mcp-server/handlers/tool-handler.ts +554 -0
  104. package/mcp-server/index-v1.js +698 -0
  105. package/mcp-server/index.js +210 -238
  106. package/mcp-server/lib/cache-wrapper.cjs +383 -0
  107. package/mcp-server/lib/error-envelope.js +138 -0
  108. package/mcp-server/lib/executor.ts +499 -0
  109. package/mcp-server/lib/index.ts +19 -0
  110. package/mcp-server/lib/rate-limiter.js +166 -0
  111. package/mcp-server/lib/sandbox.test.ts +519 -0
  112. package/mcp-server/lib/sandbox.ts +395 -0
  113. package/mcp-server/lib/types.ts +267 -0
  114. package/mcp-server/package.json +12 -3
  115. package/mcp-server/registry/tool-registry.js +794 -0
  116. package/mcp-server/registry/tools.json +605 -0
  117. package/mcp-server/registry.test.ts +334 -0
  118. package/mcp-server/tests/tier-gating.test.js +297 -0
  119. package/mcp-server/tier-auth.js +378 -45
  120. package/mcp-server/tools-v3.js +353 -442
  121. package/mcp-server/tsconfig.json +37 -0
  122. package/mcp-server/vibecheck-2.0-tools.js +14 -1
  123. package/package.json +1 -1
  124. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
  125. package/bin/runners/lib/audit-logger.js +0 -532
  126. package/bin/runners/lib/authority/authorities/architecture.js +0 -364
  127. package/bin/runners/lib/authority/authorities/compliance.js +0 -341
  128. package/bin/runners/lib/authority/authorities/human.js +0 -343
  129. package/bin/runners/lib/authority/authorities/quality.js +0 -420
  130. package/bin/runners/lib/authority/authorities/security.js +0 -228
  131. package/bin/runners/lib/authority/index.js +0 -293
  132. package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
  133. package/bin/runners/lib/cli-charts.js +0 -368
  134. package/bin/runners/lib/cli-config-display.js +0 -405
  135. package/bin/runners/lib/cli-demo.js +0 -275
  136. package/bin/runners/lib/cli-errors.js +0 -438
  137. package/bin/runners/lib/cli-help-formatter.js +0 -439
  138. package/bin/runners/lib/cli-interactive-menu.js +0 -509
  139. package/bin/runners/lib/cli-prompts.js +0 -441
  140. package/bin/runners/lib/cli-scan-cards.js +0 -362
  141. package/bin/runners/lib/compliance-reporter.js +0 -710
  142. package/bin/runners/lib/conductor/index.js +0 -671
  143. package/bin/runners/lib/easy/README.md +0 -123
  144. package/bin/runners/lib/easy/index.js +0 -140
  145. package/bin/runners/lib/easy/interactive-wizard.js +0 -788
  146. package/bin/runners/lib/easy/one-click-firewall.js +0 -564
  147. package/bin/runners/lib/easy/zero-config-reality.js +0 -714
  148. package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
  149. package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
  150. package/bin/runners/lib/engines/confidence-scoring.js +0 -276
  151. package/bin/runners/lib/engines/context-detection.js +0 -264
  152. package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
  153. package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
  154. package/bin/runners/lib/engines/env-variables-engine.js +0 -458
  155. package/bin/runners/lib/engines/error-handling-engine.js +0 -437
  156. package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
  157. package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
  158. package/bin/runners/lib/engines/framework-detection.js +0 -508
  159. package/bin/runners/lib/engines/import-order-engine.js +0 -429
  160. package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
  161. package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
  162. package/bin/runners/lib/engines/orchestrator.js +0 -334
  163. package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
  166. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
  167. package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
  168. package/bin/runners/lib/enhanced-features/index.js +0 -305
  169. package/bin/runners/lib/enhanced-output.js +0 -631
  170. package/bin/runners/lib/enterprise.js +0 -300
  171. package/bin/runners/lib/firewall/command-validator.js +0 -351
  172. package/bin/runners/lib/firewall/config.js +0 -341
  173. package/bin/runners/lib/firewall/content-validator.js +0 -519
  174. package/bin/runners/lib/firewall/index.js +0 -101
  175. package/bin/runners/lib/firewall/path-validator.js +0 -256
  176. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
  177. package/bin/runners/lib/mcp-utils.js +0 -425
  178. package/bin/runners/lib/output/index.js +0 -1022
  179. package/bin/runners/lib/policy-engine.js +0 -652
  180. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
  181. package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
  182. package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
  183. package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
  184. package/bin/runners/lib/polish/autofix/index.js +0 -200
  185. package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
  186. package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
  187. package/bin/runners/lib/polish/backend-checks.js +0 -148
  188. package/bin/runners/lib/polish/documentation-checks.js +0 -111
  189. package/bin/runners/lib/polish/frontend-checks.js +0 -168
  190. package/bin/runners/lib/polish/index.js +0 -71
  191. package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
  192. package/bin/runners/lib/polish/library-detection.js +0 -175
  193. package/bin/runners/lib/polish/performance-checks.js +0 -100
  194. package/bin/runners/lib/polish/security-checks.js +0 -148
  195. package/bin/runners/lib/polish/utils.js +0 -203
  196. package/bin/runners/lib/prompt-builder.js +0 -540
  197. package/bin/runners/lib/proof-certificate.js +0 -634
  198. package/bin/runners/lib/reality/accessibility-audit.js +0 -946
  199. package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
  200. package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
  201. package/bin/runners/lib/reality/performance-tracker.js +0 -1077
  202. package/bin/runners/lib/reality/scenario-generator.js +0 -1404
  203. package/bin/runners/lib/reality/visual-regression.js +0 -852
  204. package/bin/runners/lib/reality-profiler.js +0 -717
  205. package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
  206. package/bin/runners/lib/review/ai-code-review.js +0 -832
  207. package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
  208. package/bin/runners/lib/sbom-generator.js +0 -641
  209. package/bin/runners/lib/scan-output-enhanced.js +0 -512
  210. package/bin/runners/lib/security/owasp-scanner.js +0 -939
  211. package/bin/runners/lib/validators/contract-validator.js +0 -283
  212. package/bin/runners/lib/validators/dead-export-detector.js +0 -279
  213. package/bin/runners/lib/validators/dep-audit.js +0 -245
  214. package/bin/runners/lib/validators/env-validator.js +0 -319
  215. package/bin/runners/lib/validators/index.js +0 -120
  216. package/bin/runners/lib/validators/license-checker.js +0 -252
  217. package/bin/runners/lib/validators/route-validator.js +0 -290
  218. package/bin/runners/runAuthority.js +0 -528
  219. package/bin/runners/runConductor.js +0 -772
  220. package/bin/runners/runContainer.js +0 -366
  221. package/bin/runners/runEasy.js +0 -410
  222. package/bin/runners/runIaC.js +0 -372
  223. package/bin/runners/runVibe.js +0 -791
  224. package/mcp-server/tools.js +0 -495
@@ -1,1084 +0,0 @@
1
- /**
2
- * Chaos Engineering Mode
3
- *
4
- * ═══════════════════════════════════════════════════════════════════════════════
5
- * COMPETITIVE MOAT FEATURE - Resilience Testing Through Controlled Failure
6
- * ═══════════════════════════════════════════════════════════════════════════════
7
- *
8
- * This engine injects controlled failures during Reality Mode to test how the
9
- * application handles adverse conditions. No other tool does this at runtime.
10
- *
11
- * Chaos Types:
12
- * - Network failures (timeout, disconnect, slow connection)
13
- * - API errors (500s, 503s, rate limiting)
14
- * - Latency injection (slow responses)
15
- * - Payload corruption (malformed JSON, truncated responses)
16
- * - Resource exhaustion (memory pressure simulation)
17
- * - Third-party failures (CDN, auth provider, payment gateway)
18
- * - Clock skew (time manipulation)
19
- *
20
- * This catches:
21
- * - Missing error boundaries
22
- * - Poor offline handling
23
- * - Race conditions under load
24
- * - Retry storm vulnerabilities
25
- * - Timeout handling issues
26
- * - Graceful degradation failures
27
- */
28
-
29
- "use strict";
30
-
31
- const fs = require("fs");
32
- const path = require("path");
33
-
34
- // ═══════════════════════════════════════════════════════════════════════════════
35
- // CHAOS SCENARIOS
36
- // ═══════════════════════════════════════════════════════════════════════════════
37
-
38
- const CHAOS_SCENARIOS = {
39
- // ─────────────────────────────────────────────────────────────────────────────
40
- // NETWORK CHAOS
41
- // ─────────────────────────────────────────────────────────────────────────────
42
- network: {
43
- disconnect: {
44
- name: "Network Disconnect",
45
- description: "Simulate complete network failure",
46
- severity: "high",
47
- duration: 5000,
48
- apply: async (context) => {
49
- await context.setOffline(true);
50
- return { type: "disconnect", started: Date.now() };
51
- },
52
- cleanup: async (context) => {
53
- await context.setOffline(false);
54
- }
55
- },
56
-
57
- slowConnection: {
58
- name: "Slow 3G Connection",
59
- description: "Simulate slow mobile network",
60
- severity: "medium",
61
- config: {
62
- downloadThroughput: 50 * 1024, // 50KB/s
63
- uploadThroughput: 20 * 1024, // 20KB/s
64
- latency: 2000 // 2s latency
65
- },
66
- apply: async (context, page) => {
67
- const cdp = await page.context().newCDPSession(page);
68
- await cdp.send("Network.emulateNetworkConditions", {
69
- offline: false,
70
- downloadThroughput: 50 * 1024,
71
- uploadThroughput: 20 * 1024,
72
- latency: 2000
73
- });
74
- return { type: "slowConnection", cdp };
75
- },
76
- cleanup: async (context, state) => {
77
- if (state.cdp) {
78
- await state.cdp.send("Network.emulateNetworkConditions", {
79
- offline: false,
80
- downloadThroughput: -1,
81
- uploadThroughput: -1,
82
- latency: 0
83
- });
84
- }
85
- }
86
- },
87
-
88
- intermittent: {
89
- name: "Intermittent Connectivity",
90
- description: "Connection drops randomly",
91
- severity: "high",
92
- config: {
93
- dropChance: 0.3, // 30% chance to drop
94
- dropDuration: 2000 // 2s drops
95
- },
96
- apply: async (context, page, config) => {
97
- const interval = setInterval(async () => {
98
- if (Math.random() < config.dropChance) {
99
- await context.setOffline(true);
100
- setTimeout(() => context.setOffline(false), config.dropDuration);
101
- }
102
- }, 1000);
103
- return { type: "intermittent", interval };
104
- },
105
- cleanup: async (context, state) => {
106
- clearInterval(state.interval);
107
- await context.setOffline(false);
108
- }
109
- },
110
-
111
- dnsFailure: {
112
- name: "DNS Resolution Failure",
113
- description: "Simulate DNS lookup failures",
114
- severity: "high",
115
- apply: async (context, page) => {
116
- await page.route("**/*", async (route) => {
117
- const url = route.request().url();
118
- // Fail external domains
119
- if (!url.includes("localhost") && !url.includes("127.0.0.1")) {
120
- await route.abort("namenotresolved");
121
- } else {
122
- await route.continue();
123
- }
124
- });
125
- return { type: "dnsFailure" };
126
- },
127
- cleanup: async (context, page) => {
128
- await page.unroute("**/*");
129
- }
130
- }
131
- },
132
-
133
- // ─────────────────────────────────────────────────────────────────────────────
134
- // API CHAOS
135
- // ─────────────────────────────────────────────────────────────────────────────
136
- api: {
137
- serverError: {
138
- name: "API Server Errors",
139
- description: "All API calls return 500",
140
- severity: "high",
141
- config: {
142
- pattern: "**/api/**",
143
- status: 500,
144
- body: { error: "Internal Server Error", message: "Chaos injection" }
145
- },
146
- apply: async (context, page, config) => {
147
- await page.route(config.pattern, async (route) => {
148
- await route.fulfill({
149
- status: config.status,
150
- contentType: "application/json",
151
- body: JSON.stringify(config.body)
152
- });
153
- });
154
- return { type: "serverError", pattern: config.pattern };
155
- },
156
- cleanup: async (context, page, state) => {
157
- await page.unroute(state.pattern);
158
- }
159
- },
160
-
161
- serviceUnavailable: {
162
- name: "Service Unavailable",
163
- description: "API returns 503 with retry-after",
164
- severity: "high",
165
- config: {
166
- pattern: "**/api/**",
167
- retryAfter: 30
168
- },
169
- apply: async (context, page, config) => {
170
- await page.route(config.pattern, async (route) => {
171
- await route.fulfill({
172
- status: 503,
173
- headers: { "Retry-After": String(config.retryAfter) },
174
- contentType: "application/json",
175
- body: JSON.stringify({ error: "Service Unavailable" })
176
- });
177
- });
178
- return { type: "serviceUnavailable", pattern: config.pattern };
179
- },
180
- cleanup: async (context, page, state) => {
181
- await page.unroute(state.pattern);
182
- }
183
- },
184
-
185
- rateLimited: {
186
- name: "Rate Limited",
187
- description: "API returns 429 Too Many Requests",
188
- severity: "medium",
189
- config: {
190
- pattern: "**/api/**",
191
- retryAfter: 60
192
- },
193
- apply: async (context, page, config) => {
194
- await page.route(config.pattern, async (route) => {
195
- await route.fulfill({
196
- status: 429,
197
- headers: {
198
- "Retry-After": String(config.retryAfter),
199
- "X-RateLimit-Remaining": "0",
200
- "X-RateLimit-Reset": String(Date.now() + config.retryAfter * 1000)
201
- },
202
- contentType: "application/json",
203
- body: JSON.stringify({ error: "Too Many Requests" })
204
- });
205
- });
206
- return { type: "rateLimited", pattern: config.pattern };
207
- },
208
- cleanup: async (context, page, state) => {
209
- await page.unroute(state.pattern);
210
- }
211
- },
212
-
213
- randomErrors: {
214
- name: "Random API Errors",
215
- description: "30% of API calls fail randomly",
216
- severity: "high",
217
- config: {
218
- pattern: "**/api/**",
219
- failureRate: 0.3,
220
- errorCodes: [500, 502, 503, 504]
221
- },
222
- apply: async (context, page, config) => {
223
- await page.route(config.pattern, async (route) => {
224
- if (Math.random() < config.failureRate) {
225
- const status = config.errorCodes[
226
- Math.floor(Math.random() * config.errorCodes.length)
227
- ];
228
- await route.fulfill({
229
- status,
230
- contentType: "application/json",
231
- body: JSON.stringify({ error: `Chaos error ${status}` })
232
- });
233
- } else {
234
- await route.continue();
235
- }
236
- });
237
- return { type: "randomErrors", pattern: config.pattern };
238
- },
239
- cleanup: async (context, page, state) => {
240
- await page.unroute(state.pattern);
241
- }
242
- },
243
-
244
- timeout: {
245
- name: "API Timeout",
246
- description: "API calls hang indefinitely",
247
- severity: "high",
248
- config: {
249
- pattern: "**/api/**",
250
- delay: 30000 // 30 seconds
251
- },
252
- apply: async (context, page, config) => {
253
- await page.route(config.pattern, async (route) => {
254
- await new Promise(resolve => setTimeout(resolve, config.delay));
255
- await route.abort("timedout");
256
- });
257
- return { type: "timeout", pattern: config.pattern };
258
- },
259
- cleanup: async (context, page, state) => {
260
- await page.unroute(state.pattern);
261
- }
262
- }
263
- },
264
-
265
- // ─────────────────────────────────────────────────────────────────────────────
266
- // LATENCY CHAOS
267
- // ─────────────────────────────────────────────────────────────────────────────
268
- latency: {
269
- highLatency: {
270
- name: "High Latency",
271
- description: "Add 3-5 second delay to all requests",
272
- severity: "medium",
273
- config: {
274
- pattern: "**/*",
275
- minDelay: 3000,
276
- maxDelay: 5000
277
- },
278
- apply: async (context, page, config) => {
279
- await page.route(config.pattern, async (route) => {
280
- const delay = config.minDelay +
281
- Math.random() * (config.maxDelay - config.minDelay);
282
- await new Promise(resolve => setTimeout(resolve, delay));
283
- await route.continue();
284
- });
285
- return { type: "highLatency", pattern: config.pattern };
286
- },
287
- cleanup: async (context, page, state) => {
288
- await page.unroute(state.pattern);
289
- }
290
- },
291
-
292
- spikyLatency: {
293
- name: "Spiky Latency",
294
- description: "Occasional latency spikes",
295
- severity: "medium",
296
- config: {
297
- pattern: "**/*",
298
- spikeChance: 0.2,
299
- normalDelay: 100,
300
- spikeDelay: 5000
301
- },
302
- apply: async (context, page, config) => {
303
- await page.route(config.pattern, async (route) => {
304
- const delay = Math.random() < config.spikeChance
305
- ? config.spikeDelay
306
- : config.normalDelay;
307
- await new Promise(resolve => setTimeout(resolve, delay));
308
- await route.continue();
309
- });
310
- return { type: "spikyLatency", pattern: config.pattern };
311
- },
312
- cleanup: async (context, page, state) => {
313
- await page.unroute(state.pattern);
314
- }
315
- }
316
- },
317
-
318
- // ─────────────────────────────────────────────────────────────────────────────
319
- // PAYLOAD CHAOS
320
- // ─────────────────────────────────────────────────────────────────────────────
321
- payload: {
322
- malformedJson: {
323
- name: "Malformed JSON",
324
- description: "Return invalid JSON responses",
325
- severity: "high",
326
- config: {
327
- pattern: "**/api/**"
328
- },
329
- apply: async (context, page, config) => {
330
- await page.route(config.pattern, async (route) => {
331
- await route.fulfill({
332
- status: 200,
333
- contentType: "application/json",
334
- body: '{"broken": true, "missing_quote: "value"}' // Invalid JSON
335
- });
336
- });
337
- return { type: "malformedJson", pattern: config.pattern };
338
- },
339
- cleanup: async (context, page, state) => {
340
- await page.unroute(state.pattern);
341
- }
342
- },
343
-
344
- truncatedResponse: {
345
- name: "Truncated Response",
346
- description: "Cut responses in half",
347
- severity: "high",
348
- config: {
349
- pattern: "**/api/**",
350
- truncatePercent: 0.5
351
- },
352
- apply: async (context, page, config) => {
353
- await page.route(config.pattern, async (route) => {
354
- const response = await route.fetch();
355
- const body = await response.text();
356
- const truncated = body.substring(
357
- 0,
358
- Math.floor(body.length * config.truncatePercent)
359
- );
360
- await route.fulfill({
361
- status: response.status(),
362
- headers: response.headers(),
363
- body: truncated
364
- });
365
- });
366
- return { type: "truncatedResponse", pattern: config.pattern };
367
- },
368
- cleanup: async (context, page, state) => {
369
- await page.unroute(state.pattern);
370
- }
371
- },
372
-
373
- emptyResponse: {
374
- name: "Empty Response",
375
- description: "Return empty bodies",
376
- severity: "medium",
377
- config: {
378
- pattern: "**/api/**"
379
- },
380
- apply: async (context, page, config) => {
381
- await page.route(config.pattern, async (route) => {
382
- await route.fulfill({
383
- status: 200,
384
- contentType: "application/json",
385
- body: ""
386
- });
387
- });
388
- return { type: "emptyResponse", pattern: config.pattern };
389
- },
390
- cleanup: async (context, page, state) => {
391
- await page.unroute(state.pattern);
392
- }
393
- },
394
-
395
- wrongContentType: {
396
- name: "Wrong Content-Type",
397
- description: "Return HTML instead of JSON",
398
- severity: "medium",
399
- config: {
400
- pattern: "**/api/**"
401
- },
402
- apply: async (context, page, config) => {
403
- await page.route(config.pattern, async (route) => {
404
- await route.fulfill({
405
- status: 200,
406
- contentType: "text/html",
407
- body: "<html><body>Unexpected HTML</body></html>"
408
- });
409
- });
410
- return { type: "wrongContentType", pattern: config.pattern };
411
- },
412
- cleanup: async (context, page, state) => {
413
- await page.unroute(state.pattern);
414
- }
415
- }
416
- },
417
-
418
- // ─────────────────────────────────────────────────────────────────────────────
419
- // THIRD-PARTY CHAOS
420
- // ─────────────────────────────────────────────────────────────────────────────
421
- thirdParty: {
422
- cdnFailure: {
423
- name: "CDN Failure",
424
- description: "CDN/static assets unavailable",
425
- severity: "high",
426
- config: {
427
- patterns: [
428
- "**/*.cloudflare.com/**",
429
- "**/*.cloudfront.net/**",
430
- "**/*.akamaized.net/**",
431
- "**/cdn.**/**",
432
- "**/static.**/**"
433
- ]
434
- },
435
- apply: async (context, page, config) => {
436
- for (const pattern of config.patterns) {
437
- await page.route(pattern, route => route.abort("failed"));
438
- }
439
- return { type: "cdnFailure", patterns: config.patterns };
440
- },
441
- cleanup: async (context, page, state) => {
442
- for (const pattern of state.patterns) {
443
- await page.unroute(pattern);
444
- }
445
- }
446
- },
447
-
448
- authProviderDown: {
449
- name: "Auth Provider Down",
450
- description: "OAuth/Auth0/Firebase auth fails",
451
- severity: "critical",
452
- config: {
453
- patterns: [
454
- "**/*.auth0.com/**",
455
- "**/accounts.google.com/**",
456
- "**/login.microsoftonline.com/**",
457
- "**/*.firebaseauth.com/**",
458
- "**/clerk.**/**"
459
- ]
460
- },
461
- apply: async (context, page, config) => {
462
- for (const pattern of config.patterns) {
463
- await page.route(pattern, async (route) => {
464
- await route.fulfill({
465
- status: 503,
466
- body: "Auth service unavailable"
467
- });
468
- });
469
- }
470
- return { type: "authProviderDown", patterns: config.patterns };
471
- },
472
- cleanup: async (context, page, state) => {
473
- for (const pattern of state.patterns) {
474
- await page.unroute(pattern);
475
- }
476
- }
477
- },
478
-
479
- paymentGatewayDown: {
480
- name: "Payment Gateway Down",
481
- description: "Stripe/PayPal unavailable",
482
- severity: "critical",
483
- config: {
484
- patterns: [
485
- "**/api.stripe.com/**",
486
- "**/js.stripe.com/**",
487
- "**/api.paypal.com/**",
488
- "**/checkout.stripe.com/**"
489
- ]
490
- },
491
- apply: async (context, page, config) => {
492
- for (const pattern of config.patterns) {
493
- await page.route(pattern, async (route) => {
494
- await route.fulfill({
495
- status: 503,
496
- contentType: "application/json",
497
- body: JSON.stringify({
498
- error: { message: "Payment service unavailable" }
499
- })
500
- });
501
- });
502
- }
503
- return { type: "paymentGatewayDown", patterns: config.patterns };
504
- },
505
- cleanup: async (context, page, state) => {
506
- for (const pattern of state.patterns) {
507
- await page.unroute(pattern);
508
- }
509
- }
510
- },
511
-
512
- analyticsBlocked: {
513
- name: "Analytics Blocked",
514
- description: "Simulate ad blocker blocking analytics",
515
- severity: "low",
516
- config: {
517
- patterns: [
518
- "**/google-analytics.com/**",
519
- "**/googletagmanager.com/**",
520
- "**/segment.io/**",
521
- "**/mixpanel.com/**",
522
- "**/amplitude.com/**",
523
- "**/hotjar.com/**"
524
- ]
525
- },
526
- apply: async (context, page, config) => {
527
- for (const pattern of config.patterns) {
528
- await page.route(pattern, route => route.abort("blockedbyclient"));
529
- }
530
- return { type: "analyticsBlocked", patterns: config.patterns };
531
- },
532
- cleanup: async (context, page, state) => {
533
- for (const pattern of state.patterns) {
534
- await page.unroute(pattern);
535
- }
536
- }
537
- }
538
- },
539
-
540
- // ─────────────────────────────────────────────────────────────────────────────
541
- // RESOURCE CHAOS
542
- // ─────────────────────────────────────────────────────────────────────────────
543
- resource: {
544
- memoryPressure: {
545
- name: "Memory Pressure",
546
- description: "Simulate low memory conditions",
547
- severity: "high",
548
- apply: async (context, page) => {
549
- // Inject script to consume memory
550
- await page.evaluate(() => {
551
- window.__chaosMemory = [];
552
- for (let i = 0; i < 100; i++) {
553
- window.__chaosMemory.push(new Array(1000000).fill("x"));
554
- }
555
- });
556
- return { type: "memoryPressure" };
557
- },
558
- cleanup: async (context, page) => {
559
- await page.evaluate(() => {
560
- if (window.__chaosMemory) {
561
- window.__chaosMemory = null;
562
- }
563
- });
564
- }
565
- },
566
-
567
- cpuThrottle: {
568
- name: "CPU Throttling",
569
- description: "Simulate slow CPU",
570
- severity: "medium",
571
- config: {
572
- rate: 4 // 4x slowdown
573
- },
574
- apply: async (context, page, config) => {
575
- const cdp = await page.context().newCDPSession(page);
576
- await cdp.send("Emulation.setCPUThrottlingRate", { rate: config.rate });
577
- return { type: "cpuThrottle", cdp };
578
- },
579
- cleanup: async (context, page, state) => {
580
- if (state.cdp) {
581
- await state.cdp.send("Emulation.setCPUThrottlingRate", { rate: 1 });
582
- }
583
- }
584
- },
585
-
586
- localStorage: {
587
- name: "LocalStorage Full",
588
- description: "Fill localStorage to quota",
589
- severity: "medium",
590
- apply: async (context, page) => {
591
- await page.evaluate(() => {
592
- try {
593
- const data = "x".repeat(5 * 1024 * 1024); // 5MB
594
- localStorage.setItem("__chaos_fill", data);
595
- } catch {
596
- // Expected - quota exceeded
597
- }
598
- });
599
- return { type: "localStorage" };
600
- },
601
- cleanup: async (context, page) => {
602
- await page.evaluate(() => {
603
- localStorage.removeItem("__chaos_fill");
604
- });
605
- }
606
- }
607
- }
608
- };
609
-
610
- // ═══════════════════════════════════════════════════════════════════════════════
611
- // CHAOS ENGINE CLASS
612
- // ═══════════════════════════════════════════════════════════════════════════════
613
-
614
- class ChaosEngine {
615
- constructor(options = {}) {
616
- this.projectRoot = options.projectRoot || process.cwd();
617
- this.scenarios = { ...CHAOS_SCENARIOS };
618
- this.activeScenarios = new Map();
619
- this.results = [];
620
- this.page = null;
621
- this.context = null;
622
- }
623
-
624
- /**
625
- * Set the Playwright page and context
626
- */
627
- setPage(page) {
628
- this.page = page;
629
- this.context = page.context();
630
- }
631
-
632
- /**
633
- * Apply a chaos scenario
634
- */
635
- async applyScenario(category, scenarioName, customConfig = {}) {
636
- if (!this.page) {
637
- throw new Error("Page not set. Call setPage() first.");
638
- }
639
-
640
- const categoryScenarios = this.scenarios[category];
641
- if (!categoryScenarios) {
642
- throw new Error(`Unknown chaos category: ${category}`);
643
- }
644
-
645
- const scenario = categoryScenarios[scenarioName];
646
- if (!scenario) {
647
- throw new Error(`Unknown chaos scenario: ${category}.${scenarioName}`);
648
- }
649
-
650
- const config = { ...scenario.config, ...customConfig };
651
- const id = `${category}.${scenarioName}`;
652
-
653
- try {
654
- const state = await scenario.apply(this.context, this.page, config);
655
-
656
- this.activeScenarios.set(id, {
657
- scenario,
658
- state,
659
- config,
660
- startTime: Date.now()
661
- });
662
-
663
- return {
664
- success: true,
665
- id,
666
- name: scenario.name,
667
- description: scenario.description
668
- };
669
- } catch (error) {
670
- return {
671
- success: false,
672
- id,
673
- error: error.message
674
- };
675
- }
676
- }
677
-
678
- /**
679
- * Remove a chaos scenario
680
- */
681
- async removeScenario(id) {
682
- const active = this.activeScenarios.get(id);
683
- if (!active) {
684
- return { success: false, error: "Scenario not active" };
685
- }
686
-
687
- try {
688
- await active.scenario.cleanup(this.context, this.page, active.state);
689
- this.activeScenarios.delete(id);
690
-
691
- return {
692
- success: true,
693
- id,
694
- duration: Date.now() - active.startTime
695
- };
696
- } catch (error) {
697
- return {
698
- success: false,
699
- id,
700
- error: error.message
701
- };
702
- }
703
- }
704
-
705
- /**
706
- * Remove all active scenarios
707
- */
708
- async cleanup() {
709
- const results = [];
710
-
711
- for (const id of this.activeScenarios.keys()) {
712
- const result = await this.removeScenario(id);
713
- results.push(result);
714
- }
715
-
716
- return results;
717
- }
718
-
719
- /**
720
- * Run chaos test suite
721
- */
722
- async runChaosSuite(page, url, options = {}) {
723
- this.setPage(page);
724
- this.results = [];
725
-
726
- const {
727
- categories = Object.keys(this.scenarios),
728
- duration = 5000,
729
- captureScreenshots = true
730
- } = options;
731
-
732
- // Navigate to URL first
733
- await page.goto(url, { waitUntil: "networkidle" });
734
- const baselineScreenshot = captureScreenshots
735
- ? await page.screenshot()
736
- : null;
737
-
738
- // Run each chaos scenario
739
- for (const category of categories) {
740
- const categoryScenarios = this.scenarios[category];
741
-
742
- for (const [name, scenario] of Object.entries(categoryScenarios)) {
743
- const result = await this.runSingleChaosTest(
744
- category,
745
- name,
746
- scenario,
747
- url,
748
- duration,
749
- captureScreenshots
750
- );
751
- this.results.push(result);
752
- }
753
- }
754
-
755
- return {
756
- url,
757
- totalTests: this.results.length,
758
- passed: this.results.filter(r => r.passed).length,
759
- failed: this.results.filter(r => !r.passed).length,
760
- results: this.results,
761
- baselineScreenshot
762
- };
763
- }
764
-
765
- /**
766
- * Run a single chaos test
767
- */
768
- async runSingleChaosTest(category, name, scenario, url, duration, captureScreenshots) {
769
- const id = `${category}.${name}`;
770
- const result = {
771
- id,
772
- name: scenario.name,
773
- description: scenario.description,
774
- severity: scenario.severity,
775
- category,
776
- passed: true,
777
- errors: [],
778
- warnings: [],
779
- duration: 0
780
- };
781
-
782
- const startTime = Date.now();
783
-
784
- try {
785
- // Apply chaos
786
- await this.applyScenario(category, name);
787
-
788
- // Wait and collect errors
789
- const consoleErrors = [];
790
- const networkErrors = [];
791
-
792
- this.page.on("console", msg => {
793
- if (msg.type() === "error") {
794
- consoleErrors.push(msg.text());
795
- }
796
- });
797
-
798
- this.page.on("pageerror", err => {
799
- consoleErrors.push(err.message);
800
- });
801
-
802
- this.page.on("requestfailed", req => {
803
- networkErrors.push({
804
- url: req.url(),
805
- failure: req.failure()?.errorText
806
- });
807
- });
808
-
809
- // Reload page under chaos
810
- try {
811
- await this.page.reload({ timeout: duration * 2 });
812
- } catch (reloadError) {
813
- result.warnings.push(`Page reload failed: ${reloadError.message}`);
814
- }
815
-
816
- // Wait for chaos duration
817
- await this.page.waitForTimeout(duration);
818
-
819
- // Take screenshot under chaos
820
- if (captureScreenshots) {
821
- try {
822
- result.chaosScreenshot = await this.page.screenshot();
823
- } catch {
824
- result.warnings.push("Failed to capture chaos screenshot");
825
- }
826
- }
827
-
828
- // Check for JS crashes
829
- const hasJSCrash = await this.page.evaluate(() => {
830
- return document.body.innerHTML.includes("error") ||
831
- document.body.innerHTML.includes("crash") ||
832
- document.querySelector("[class*='error']") !== null;
833
- });
834
-
835
- // Evaluate resilience
836
- if (consoleErrors.length > 0) {
837
- result.warnings.push(`${consoleErrors.length} console errors during chaos`);
838
- result.consoleErrors = consoleErrors.slice(0, 5);
839
- }
840
-
841
- if (hasJSCrash) {
842
- result.errors.push("Possible JS crash detected");
843
- result.passed = false;
844
- }
845
-
846
- // Check for error boundary or graceful degradation
847
- const hasErrorBoundary = await this.page.evaluate(() => {
848
- return document.querySelector("[data-error-boundary]") !== null ||
849
- document.querySelector("[class*='fallback']") !== null ||
850
- document.body.innerText.toLowerCase().includes("something went wrong");
851
- });
852
-
853
- if (!hasErrorBoundary && consoleErrors.length > 3) {
854
- result.warnings.push("No error boundary detected despite errors");
855
- }
856
-
857
- // Check for loading states
858
- const hasLoadingState = await this.page.evaluate(() => {
859
- return document.querySelector("[class*='loading']") !== null ||
860
- document.querySelector("[class*='skeleton']") !== null ||
861
- document.querySelector("[aria-busy='true']") !== null;
862
- });
863
-
864
- if (hasLoadingState) {
865
- result.warnings.push("App stuck in loading state");
866
- }
867
-
868
- // Remove chaos
869
- await this.removeScenario(id);
870
-
871
- // Verify recovery
872
- try {
873
- await this.page.reload({ timeout: 10000 });
874
- const recovered = await this.page.evaluate(() => {
875
- return !document.body.innerHTML.includes("error") &&
876
- document.body.innerHTML.length > 100;
877
- });
878
-
879
- if (!recovered) {
880
- result.errors.push("App did not recover after chaos removed");
881
- result.passed = false;
882
- }
883
- } catch {
884
- result.errors.push("Page failed to reload after chaos");
885
- result.passed = false;
886
- }
887
-
888
- } catch (error) {
889
- result.errors.push(error.message);
890
- result.passed = false;
891
- }
892
-
893
- result.duration = Date.now() - startTime;
894
-
895
- // Final pass/fail based on severity
896
- if (result.errors.length > 0) {
897
- result.passed = false;
898
- } else if (result.warnings.length > 5 && scenario.severity === "high") {
899
- result.passed = false;
900
- }
901
-
902
- return result;
903
- }
904
-
905
- /**
906
- * Get available scenarios
907
- */
908
- getAvailableScenarios() {
909
- const available = [];
910
-
911
- for (const [category, scenarios] of Object.entries(this.scenarios)) {
912
- for (const [name, scenario] of Object.entries(scenarios)) {
913
- available.push({
914
- id: `${category}.${name}`,
915
- category,
916
- name: scenario.name,
917
- description: scenario.description,
918
- severity: scenario.severity
919
- });
920
- }
921
- }
922
-
923
- return available;
924
- }
925
-
926
- /**
927
- * Get active scenarios
928
- */
929
- getActiveScenarios() {
930
- const active = [];
931
-
932
- for (const [id, data] of this.activeScenarios) {
933
- active.push({
934
- id,
935
- name: data.scenario.name,
936
- startTime: data.startTime,
937
- duration: Date.now() - data.startTime
938
- });
939
- }
940
-
941
- return active;
942
- }
943
-
944
- /**
945
- * Generate chaos test report
946
- */
947
- generateReport() {
948
- const passed = this.results.filter(r => r.passed);
949
- const failed = this.results.filter(r => !r.passed);
950
-
951
- const bySeverity = {
952
- critical: this.results.filter(r => r.severity === "critical"),
953
- high: this.results.filter(r => r.severity === "high"),
954
- medium: this.results.filter(r => r.severity === "medium"),
955
- low: this.results.filter(r => r.severity === "low")
956
- };
957
-
958
- const byCategory = {};
959
- for (const result of this.results) {
960
- if (!byCategory[result.category]) {
961
- byCategory[result.category] = { passed: 0, failed: 0 };
962
- }
963
- byCategory[result.category][result.passed ? "passed" : "failed"]++;
964
- }
965
-
966
- return {
967
- timestamp: new Date().toISOString(),
968
- summary: {
969
- total: this.results.length,
970
- passed: passed.length,
971
- failed: failed.length,
972
- passRate: this.results.length > 0
973
- ? ((passed.length / this.results.length) * 100).toFixed(1) + "%"
974
- : "N/A"
975
- },
976
- bySeverity: {
977
- critical: {
978
- total: bySeverity.critical.length,
979
- failed: bySeverity.critical.filter(r => !r.passed).length
980
- },
981
- high: {
982
- total: bySeverity.high.length,
983
- failed: bySeverity.high.filter(r => !r.passed).length
984
- },
985
- medium: {
986
- total: bySeverity.medium.length,
987
- failed: bySeverity.medium.filter(r => !r.passed).length
988
- },
989
- low: {
990
- total: bySeverity.low.length,
991
- failed: bySeverity.low.filter(r => !r.passed).length
992
- }
993
- },
994
- byCategory,
995
- failures: failed.map(r => ({
996
- id: r.id,
997
- name: r.name,
998
- severity: r.severity,
999
- errors: r.errors,
1000
- warnings: r.warnings
1001
- })),
1002
- recommendations: this.generateRecommendations(failed)
1003
- };
1004
- }
1005
-
1006
- /**
1007
- * Generate recommendations from failures
1008
- */
1009
- generateRecommendations(failures) {
1010
- const recommendations = [];
1011
-
1012
- const hasNetworkFailures = failures.some(f => f.category === "network");
1013
- const hasApiFailures = failures.some(f => f.category === "api");
1014
- const hasPayloadFailures = failures.some(f => f.category === "payload");
1015
- const hasThirdPartyFailures = failures.some(f => f.category === "thirdParty");
1016
-
1017
- if (hasNetworkFailures) {
1018
- recommendations.push({
1019
- category: "network",
1020
- priority: "high",
1021
- message: "Improve offline handling",
1022
- suggestions: [
1023
- "Implement service workers for offline support",
1024
- "Add network status indicators",
1025
- "Queue failed requests for retry",
1026
- "Cache critical data locally"
1027
- ]
1028
- });
1029
- }
1030
-
1031
- if (hasApiFailures) {
1032
- recommendations.push({
1033
- category: "api",
1034
- priority: "high",
1035
- message: "Improve API error handling",
1036
- suggestions: [
1037
- "Add error boundaries around API-dependent components",
1038
- "Implement exponential backoff for retries",
1039
- "Show user-friendly error messages",
1040
- "Add circuit breakers for failing endpoints"
1041
- ]
1042
- });
1043
- }
1044
-
1045
- if (hasPayloadFailures) {
1046
- recommendations.push({
1047
- category: "payload",
1048
- priority: "high",
1049
- message: "Improve response validation",
1050
- suggestions: [
1051
- "Validate API responses with schema libraries",
1052
- "Handle malformed JSON gracefully",
1053
- "Add fallback defaults for missing fields",
1054
- "Log parsing errors for debugging"
1055
- ]
1056
- });
1057
- }
1058
-
1059
- if (hasThirdPartyFailures) {
1060
- recommendations.push({
1061
- category: "thirdParty",
1062
- priority: "medium",
1063
- message: "Improve third-party resilience",
1064
- suggestions: [
1065
- "Implement fallbacks for critical third-party services",
1066
- "Use async loading for non-critical services",
1067
- "Add health checks for dependencies",
1068
- "Consider self-hosted alternatives for critical paths"
1069
- ]
1070
- });
1071
- }
1072
-
1073
- return recommendations;
1074
- }
1075
- }
1076
-
1077
- // ═══════════════════════════════════════════════════════════════════════════════
1078
- // EXPORTS
1079
- // ═══════════════════════════════════════════════════════════════════════════════
1080
-
1081
- module.exports = {
1082
- ChaosEngine,
1083
- CHAOS_SCENARIOS
1084
- };