e11y 0.2.0 → 1.1.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.
Files changed (288) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +130 -10
  3. data/CHANGELOG.md +80 -1
  4. data/CLAUDE.md +168 -0
  5. data/CONTRIBUTING.md +640 -0
  6. data/README.md +165 -701
  7. data/RELEASE.md +41 -12
  8. data/Rakefile +249 -57
  9. data/config/README.md +1 -1
  10. data/config/loki-local-config.yaml +12 -0
  11. data/config/otel-collector-config.yaml +44 -0
  12. data/cucumber.yml +1 -0
  13. data/docker-compose.yml +18 -2
  14. data/docs/ADAPTERS.md +76 -0
  15. data/docs/ADAPTIVE_SAMPLING.md +59 -0
  16. data/docs/COMPARISON.md +104 -0
  17. data/docs/CONFIGURATION.md +52 -0
  18. data/docs/DISTRIBUTED_TRACING.md +44 -0
  19. data/docs/LIMITATIONS.md +13 -0
  20. data/docs/METRICS_DSL.md +84 -0
  21. data/docs/PERFORMANCE.md +60 -0
  22. data/docs/PII_FILTERING.md +40 -0
  23. data/docs/PRESETS.md +65 -0
  24. data/docs/QUICK-START.md +546 -587
  25. data/docs/RAILS_INTEGRATION.md +79 -0
  26. data/docs/SCHEMA_VALIDATION.md +63 -0
  27. data/docs/SLO-PROMQL-ALERTS.md +161 -0
  28. data/docs/TESTING.md +69 -0
  29. data/docs/{ADR-001-architecture.md → architecture/ADR-001-architecture.md} +36 -65
  30. data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
  31. data/docs/architecture/ADR-003-slo-observability.md +1402 -0
  32. data/docs/{ADR-004-adapter-architecture.md → architecture/ADR-004-adapter-architecture.md} +163 -146
  33. data/docs/{ADR-005-tracing-context.md → architecture/ADR-005-tracing-context.md} +10 -9
  34. data/docs/{ADR-006-security-compliance.md → architecture/ADR-006-security-compliance.md} +184 -191
  35. data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md} +3 -21
  36. data/docs/{ADR-008-rails-integration.md → architecture/ADR-008-rails-integration.md} +182 -743
  37. data/docs/{ADR-009-cost-optimization.md → architecture/ADR-009-cost-optimization.md} +45 -54
  38. data/docs/architecture/ADR-010-developer-experience.md +522 -0
  39. data/docs/{ADR-011-testing-strategy.md → architecture/ADR-011-testing-strategy.md} +44 -86
  40. data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +11 -11
  41. data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
  42. data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
  43. data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +43 -59
  44. data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +58 -355
  45. data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
  46. data/docs/architecture/ADR-018-memory-optimization.md +366 -0
  47. data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
  48. data/docs/plans/2026-03-20-browser-overlay-svelte.md +281 -0
  49. data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
  50. data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
  51. data/docs/prd/01-overview-vision.md +19 -14
  52. data/docs/use_cases/README.md +22 -23
  53. data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
  54. data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
  55. data/docs/use_cases/UC-003-event-metrics.md +66 -0
  56. data/docs/use_cases/UC-004-zero-config-slo-tracking.md +33 -684
  57. data/docs/use_cases/UC-005-sentry-integration.md +13 -15
  58. data/docs/use_cases/UC-006-trace-context-management.md +30 -28
  59. data/docs/use_cases/UC-007-pii-filtering.md +35 -87
  60. data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
  61. data/docs/use_cases/UC-009-multi-service-tracing.md +30 -178
  62. data/docs/use_cases/UC-010-background-job-tracking.md +24 -91
  63. data/docs/use_cases/UC-011-rate-limiting.md +95 -168
  64. data/docs/use_cases/UC-012-audit-trail.md +21 -46
  65. data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
  66. data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
  67. data/docs/use_cases/UC-015-cost-optimization.md +46 -99
  68. data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
  69. data/docs/use_cases/UC-017-local-development.md +203 -777
  70. data/docs/use_cases/UC-018-testing-events.md +3 -3
  71. data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
  72. data/docs/use_cases/UC-020-event-versioning.md +8 -9
  73. data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
  74. data/docs/use_cases/UC-022-event-registry.md +15 -21
  75. data/docs/use_cases/backlog.md +119 -87
  76. data/e11y.gemspec +2 -2
  77. data/gems/e11y-devtools/README.md +158 -0
  78. data/gems/e11y-devtools/config/routes.rb +15 -0
  79. data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
  80. data/gems/e11y-devtools/exe/e11y +34 -0
  81. data/gems/e11y-devtools/frontend/.gitignore +24 -0
  82. data/gems/e11y-devtools/frontend/README.md +51 -0
  83. data/gems/e11y-devtools/frontend/index.html +14 -0
  84. data/gems/e11y-devtools/frontend/package-lock.json +3707 -0
  85. data/gems/e11y-devtools/frontend/package.json +28 -0
  86. data/gems/e11y-devtools/frontend/public/mocks/v1/events/recent.json +4205 -0
  87. data/gems/e11y-devtools/frontend/public/mocks/v1/interactions.json +194 -0
  88. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0a2e04027cfa22d014bc22e8b27cd913/events.json +86 -0
  89. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0e1543af6a630fb3af6b52283154b3e0/events.json +169 -0
  90. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/1838b691faa49564f97db8592ff3978d/events.json +78 -0
  91. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/29f198f6588dacffb687777eb5f8f118/events.json +197 -0
  92. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/34bc3c9c0097de28a7a6f99b90a8e7bc/events.json +194 -0
  93. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/3ba6c20d068ab9cee00e51b180e66444/events.json +184 -0
  94. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/435bfd8f17b9009146a79812d7c3726d/events.json +144 -0
  95. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/4c7676e3fe668e99edb2b94d7d5678a9/events.json +222 -0
  96. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/6daf0d47974bedfc55d5de7004a3ea9f/events.json +194 -0
  97. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8a81ada42834d15f287bb40010043605/events.json +194 -0
  98. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8c0a98900edaae105469df8daedccf02/events.json +198 -0
  99. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8e4f645180f8a7d1dce426b07380466b/events.json +222 -0
  100. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/93db346fa5d44a032605a13b627f4b80/events.json +128 -0
  101. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/98ff6146faf7bd9be8bd03a8275817ba/events.json +223 -0
  102. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/9997ddd0247bc7e25f2ca7a5c415c93d/events.json +197 -0
  103. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/99e35f8ef3baedd798cc4fd085980ad9/events.json +194 -0
  104. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b4f3095c1909924cbc98889a86c83d6d/events.json +131 -0
  105. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b54b7fc32b7575a7110de809d11ccda0/events.json +128 -0
  106. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c0b48033fa06746bcc5886745e053cff/events.json +169 -0
  107. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c44649ac76701b4558927cd2305ab535/events.json +169 -0
  108. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/d601ae3320057580a39dbdac2edfdf4a/events.json +248 -0
  109. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e67e724bab422d2b52eeb49635e512e1/events.json +194 -0
  110. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e6c72765a28f158a8485b35fa63f73da/events.json +194 -0
  111. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/f541b87405c9a54819b18ebe529f6419/events.json +194 -0
  112. data/gems/e11y-devtools/frontend/scripts/generate_mocks.rb +397 -0
  113. data/gems/e11y-devtools/frontend/src/App.svelte +827 -0
  114. data/gems/e11y-devtools/frontend/src/components/Fab.svelte +19 -0
  115. data/gems/e11y-devtools/frontend/src/components/FilterBar.svelte +38 -0
  116. data/gems/e11y-devtools/frontend/src/components/FullscreenPanel.svelte +82 -0
  117. data/gems/e11y-devtools/frontend/src/components/InteractionsTimeline.svelte +264 -0
  118. data/gems/e11y-devtools/frontend/src/components/RecentHistogram.svelte +354 -0
  119. data/gems/e11y-devtools/frontend/src/lib/api.ts +37 -0
  120. data/gems/e11y-devtools/frontend/src/lib/eventIdentity.ts +12 -0
  121. data/gems/e11y-devtools/frontend/src/lib/format.ts +37 -0
  122. data/gems/e11y-devtools/frontend/src/lib/listFilter.ts +43 -0
  123. data/gems/e11y-devtools/frontend/src/lib/recentVolume.ts +80 -0
  124. data/gems/e11y-devtools/frontend/src/lib/router.ts +12 -0
  125. data/gems/e11y-devtools/frontend/src/lib/transitions.ts +34 -0
  126. data/gems/e11y-devtools/frontend/src/lib/viewportOrigin.ts +25 -0
  127. data/gems/e11y-devtools/frontend/src/main.ts +8 -0
  128. data/gems/e11y-devtools/frontend/src/overlay-entry.ts +24 -0
  129. data/gems/e11y-devtools/frontend/src/overlay.css +1080 -0
  130. data/gems/e11y-devtools/frontend/svelte.config.js +2 -0
  131. data/gems/e11y-devtools/frontend/test_puppeteer.js +41 -0
  132. data/gems/e11y-devtools/frontend/test_scale.js +3 -0
  133. data/gems/e11y-devtools/frontend/tsconfig.app.json +21 -0
  134. data/gems/e11y-devtools/frontend/tsconfig.json +7 -0
  135. data/gems/e11y-devtools/frontend/tsconfig.node.json +26 -0
  136. data/gems/e11y-devtools/frontend/vite.config.ts +36 -0
  137. data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
  138. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
  139. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
  140. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
  141. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
  142. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
  143. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
  144. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
  145. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
  146. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
  147. data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +20 -0
  148. data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +94 -0
  149. data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
  150. data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
  151. data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +67 -0
  152. data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
  153. data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
  154. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
  155. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
  156. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
  157. data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
  158. data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
  159. data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
  160. data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +91 -0
  161. data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
  162. data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
  163. data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
  164. data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
  165. data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
  166. data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
  167. data/lib/e11y/adapters/audit_encrypted.rb +53 -11
  168. data/lib/e11y/adapters/base.rb +33 -34
  169. data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
  170. data/lib/e11y/adapters/dev_log/query.rb +219 -0
  171. data/lib/e11y/adapters/dev_log.rb +118 -0
  172. data/lib/e11y/adapters/file.rb +3 -6
  173. data/lib/e11y/adapters/in_memory.rb +52 -5
  174. data/lib/e11y/adapters/in_memory_test.rb +29 -0
  175. data/lib/e11y/adapters/loki.rb +58 -23
  176. data/lib/e11y/adapters/null.rb +82 -0
  177. data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
  178. data/lib/e11y/adapters/otel_logs.rb +136 -23
  179. data/lib/e11y/adapters/sentry.rb +4 -7
  180. data/lib/e11y/adapters/stdout.rb +73 -7
  181. data/lib/e11y/adapters/yabeda.rb +153 -29
  182. data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
  183. data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
  184. data/lib/e11y/buffers/ring_buffer.rb +3 -16
  185. data/lib/e11y/configuration.rb +272 -0
  186. data/lib/e11y/console.rb +10 -17
  187. data/lib/e11y/current.rb +53 -1
  188. data/lib/e11y/debug/pipeline_inspector.rb +96 -0
  189. data/lib/e11y/documentation/generator.rb +48 -0
  190. data/lib/e11y/event/base.rb +176 -82
  191. data/lib/e11y/event/value_sampling_config.rb +1 -5
  192. data/lib/e11y/events/rails/database/query.rb +1 -4
  193. data/lib/e11y/events/rails/job/failed.rb +2 -0
  194. data/lib/e11y/instruments/active_job.rb +44 -12
  195. data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
  196. data/lib/e11y/instruments/sidekiq.rb +135 -31
  197. data/lib/e11y/linters/base.rb +11 -0
  198. data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
  199. data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
  200. data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
  201. data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
  202. data/lib/e11y/logger/bridge.rb +26 -7
  203. data/lib/e11y/metrics/cardinality_protection.rb +10 -15
  204. data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
  205. data/lib/e11y/metrics/registry.rb +3 -5
  206. data/lib/e11y/metrics/test_backend.rb +62 -0
  207. data/lib/e11y/metrics.rb +56 -10
  208. data/lib/e11y/middleware/adapter_resolver.rb +40 -0
  209. data/lib/e11y/middleware/audit_signing.rb +43 -6
  210. data/lib/e11y/middleware/baggage_protection.rb +75 -0
  211. data/lib/e11y/middleware/dev_log_source.rb +24 -0
  212. data/lib/e11y/middleware/event_slo.rb +23 -9
  213. data/lib/e11y/middleware/otel_span.rb +23 -0
  214. data/lib/e11y/middleware/pii_filter.rb +104 -75
  215. data/lib/e11y/middleware/rate_limiting.rb +54 -27
  216. data/lib/e11y/middleware/request.rb +70 -23
  217. data/lib/e11y/middleware/routing.rb +78 -21
  218. data/lib/e11y/middleware/sampling.rb +66 -17
  219. data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
  220. data/lib/e11y/middleware/trace_context.rb +45 -10
  221. data/lib/e11y/middleware/track_latency.rb +34 -0
  222. data/lib/e11y/middleware/validation.rb +7 -16
  223. data/lib/e11y/middleware/versioning.rb +26 -22
  224. data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
  225. data/lib/e11y/opentelemetry/span_creator.rb +142 -0
  226. data/lib/e11y/pii/patterns.rb +12 -1
  227. data/lib/e11y/pipeline/builder.rb +4 -4
  228. data/lib/e11y/presets/audit_event.rb +13 -2
  229. data/lib/e11y/railtie.rb +52 -14
  230. data/lib/e11y/registry.rb +306 -0
  231. data/lib/e11y/reliability/circuit_breaker.rb +19 -21
  232. data/lib/e11y/reliability/dlq/base.rb +71 -0
  233. data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
  234. data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
  235. data/lib/e11y/reliability/dlq/filter.rb +37 -54
  236. data/lib/e11y/reliability/retry_handler.rb +26 -29
  237. data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
  238. data/lib/e11y/sampling/error_spike_detector.rb +0 -2
  239. data/lib/e11y/sampling/load_monitor.rb +5 -9
  240. data/lib/e11y/sampling/stratified_tracker.rb +18 -0
  241. data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
  242. data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
  243. data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
  244. data/lib/e11y/slo/config_loader.rb +40 -0
  245. data/lib/e11y/slo/config_validator.rb +58 -0
  246. data/lib/e11y/slo/dashboard_generator.rb +122 -0
  247. data/lib/e11y/slo/event_driven.rb +8 -0
  248. data/lib/e11y/slo/tracker.rb +31 -4
  249. data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
  250. data/lib/e11y/testing/rspec_matchers.rb +21 -0
  251. data/lib/e11y/testing/snapshot_matcher.rb +86 -0
  252. data/lib/e11y/trace_context/sampler.rb +35 -0
  253. data/lib/e11y/tracing/faraday_middleware.rb +31 -0
  254. data/lib/e11y/tracing/net_http_patch.rb +33 -0
  255. data/lib/e11y/tracing/propagator.rb +144 -0
  256. data/lib/e11y/tracing.rb +47 -0
  257. data/lib/e11y/version.rb +1 -1
  258. data/lib/e11y/versioning/version_extractor.rb +32 -0
  259. data/lib/e11y.rb +123 -266
  260. data/lib/generators/e11y/event/event_generator.rb +22 -0
  261. data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
  262. data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
  263. data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
  264. data/lib/generators/e11y/install/install_generator.rb +34 -0
  265. data/lib/generators/e11y/install/templates/e11y.rb +239 -0
  266. data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
  267. data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
  268. data/lib/tasks/e11y_docs.rake +30 -0
  269. data/lib/tasks/e11y_events.rake +71 -0
  270. data/lib/tasks/e11y_lint.rake +91 -0
  271. data/lib/tasks/e11y_slo.rake +29 -0
  272. metadata +186 -39
  273. data/docs/ADR-003-slo-observability.md +0 -3337
  274. data/docs/ADR-010-developer-experience.md +0 -2166
  275. data/docs/API-REFERENCE-L28.md +0 -914
  276. data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
  277. data/docs/CONTRIBUTING.md +0 -312
  278. data/docs/IMPLEMENTATION_NOTES.md +0 -2804
  279. data/docs/IMPLEMENTATION_PLAN.md +0 -1971
  280. data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
  281. data/docs/PLAN.md +0 -148
  282. data/docs/README.md +0 -296
  283. data/docs/design/00-memory-optimization.md +0 -593
  284. data/docs/guides/MIGRATION-L27-L28.md +0 -692
  285. data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
  286. data/docs/guides/README.md +0 -44
  287. data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
  288. data/lib/e11y/adapters/registry.rb +0 -141
@@ -0,0 +1,2 @@
1
+ /** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */
2
+ export default {}
@@ -0,0 +1,41 @@
1
+ import puppeteer from 'puppeteer';
2
+
3
+ (async () => {
4
+ const browser = await puppeteer.launch({ headless: "new" });
5
+ const page = await browser.newPage();
6
+
7
+ page.on('console', msg => console.log('PAGE LOG:', msg.text()));
8
+
9
+ await page.goto('http://localhost:5173');
10
+ await page.waitForSelector('.e11y-fab', { timeout: 5000 });
11
+ await page.click('.e11y-fab');
12
+ await page.waitForSelector('.e11y-histo-chart-host', { timeout: 5000 });
13
+
14
+ await page.evaluate(() => {
15
+ const host = document.querySelector('.e11y-histo-brush-overlay');
16
+ if (host) {
17
+ const rect = host.getBoundingClientRect();
18
+ const down = new PointerEvent('pointerdown', { clientX: rect.left + 50, clientY: rect.top + 20, button: 0, bubbles: true });
19
+ host.dispatchEvent(down);
20
+
21
+ const move = new PointerEvent('pointermove', { clientX: rect.left + 150, clientY: rect.top + 20, button: 0, bubbles: true });
22
+ host.dispatchEvent(move);
23
+
24
+ const up = new PointerEvent('pointerup', { clientX: rect.left + 150, clientY: rect.top + 20, button: 0, bubbles: true });
25
+ host.dispatchEvent(up);
26
+ }
27
+ });
28
+
29
+ await new Promise(r => setTimeout(r, 500));
30
+
31
+ await page.evaluate(() => {
32
+ const shade = document.querySelector('.e11y-histo-sel-shade');
33
+ if (shade) {
34
+ console.log('SHADE AFTER FAKE EVENT:', shade.style.left, shade.style.width);
35
+ } else {
36
+ console.log('SHADE AFTER FAKE EVENT: null');
37
+ }
38
+ });
39
+
40
+ await browser.close();
41
+ })();
@@ -0,0 +1,3 @@
1
+ import { scaleBand } from "d3-scale";
2
+ const scale = scaleBand().domain([0, 1, 2]).range([0, 100]).paddingInner(0.18).paddingOuter(0.06);
3
+ console.log(scale(0), scale(1), scale(2), scale.bandwidth());
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "@tsconfig/svelte/tsconfig.json",
3
+ "compilerOptions": {
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5
+ "target": "ES2023",
6
+ "useDefineForClassFields": true,
7
+ "module": "ESNext",
8
+ "types": ["svelte", "vite/client"],
9
+ "noEmit": true,
10
+ /**
11
+ * Typecheck JS in `.svelte` and `.js` files by default.
12
+ * Disable checkJs if you'd like to use dynamic types in JS.
13
+ * Note that setting allowJs false does not prevent the use
14
+ * of JS in `.svelte` files.
15
+ */
16
+ "allowJs": true,
17
+ "checkJs": true,
18
+ "moduleDetection": "force"
19
+ },
20
+ "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
21
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["vite.config.ts"]
26
+ }
@@ -0,0 +1,36 @@
1
+ import path from "node:path"
2
+ import { fileURLToPath } from "node:url"
3
+ import { defineConfig } from "vite"
4
+ import { svelte } from "@sveltejs/vite-plugin-svelte"
5
+ import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
8
+ const assetOutDir = path.resolve(__dirname, "../lib/e11y/devtools/overlay/assets")
9
+
10
+ export default defineConfig(({ command }) => {
11
+ if (command === "serve") {
12
+ return {
13
+ plugins: [svelte()],
14
+ }
15
+ }
16
+
17
+ return {
18
+ plugins: [svelte(), cssInjectedByJsPlugin()],
19
+ build: {
20
+ copyPublicDir: false,
21
+ lib: {
22
+ entry: path.resolve(__dirname, "src/overlay-entry.ts"),
23
+ name: "E11yDevtoolsOverlay",
24
+ formats: ["iife"],
25
+ fileName: () => "overlay.js",
26
+ },
27
+ outDir: assetOutDir,
28
+ emptyOutDir: false,
29
+ rollupOptions: {
30
+ output: {
31
+ inlineDynamicImports: true,
32
+ },
33
+ },
34
+ },
35
+ }
36
+ })
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "e11y/adapters/dev_log/query"
5
+ require_relative "tools/recent_events"
6
+ require_relative "tools/events_by_trace"
7
+ require_relative "tools/search"
8
+ require_relative "tools/stats"
9
+ require_relative "tools/interactions"
10
+ require_relative "tools/event_detail"
11
+ require_relative "tools/errors"
12
+ require_relative "tools/clear"
13
+
14
+ module E11y
15
+ module Devtools
16
+ module Mcp
17
+ # MCP Server wrapping the E11y DevLog adapter.
18
+ #
19
+ # Exposes 8 tools to AI tools like Cursor and Claude Code.
20
+ # Supports stdio (default) and StreamableHTTP transports.
21
+ #
22
+ # @example Start stdio server
23
+ # E11y::Devtools::Mcp::Server.new.run
24
+ #
25
+ # @example Start HTTP server on port 3099
26
+ # E11y::Devtools::Mcp::Server.new.run(transport: :http, port: 3099)
27
+ class Server
28
+ TOOLS = [
29
+ Tools::RecentEvents, Tools::EventsByTrace, Tools::Search,
30
+ Tools::Stats, Tools::Interactions, Tools::EventDetail,
31
+ Tools::Errors, Tools::Clear
32
+ ].freeze
33
+
34
+ def initialize(log_path: nil)
35
+ @log_path = log_path || auto_detect_log_path
36
+ @store = E11y::Adapters::DevLog::Query.new(@log_path)
37
+ end
38
+
39
+ # Start the MCP server.
40
+ #
41
+ # @param transport [:stdio, :http] Transport to use
42
+ # @param port [Integer, nil] HTTP port (default 3099)
43
+ def run(transport: :stdio, port: nil)
44
+ require "mcp"
45
+ server = build_mcp_server
46
+ case transport
47
+ when :stdio then run_stdio(server)
48
+ when :http then run_http(server, port || 3099)
49
+ else raise ArgumentError, "Unknown transport: #{transport}"
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def build_mcp_server
56
+ MCP::Server.new(
57
+ name: "e11y",
58
+ version: E11y::Devtools::VERSION,
59
+ tools: TOOLS,
60
+ server_context: { store: @store }
61
+ )
62
+ end
63
+
64
+ def run_stdio(server)
65
+ t = MCP::Server::Transports::StdioTransport.new(server)
66
+ server.transport = t
67
+ t.open
68
+ end
69
+
70
+ def run_http(server, port)
71
+ require "webrick"
72
+ t = MCP::Server::Transports::StreamableHTTPTransport.new(server)
73
+ server.transport = t
74
+ s = WEBrick::HTTPServer.new(Port: port, Logger: WEBrick::Log.new(nil))
75
+ s.mount("/mcp", t)
76
+ trap("INT") { s.shutdown }
77
+ s.start
78
+ end
79
+
80
+ def auto_detect_log_path
81
+ dir = Pathname.new(Dir.pwd)
82
+ loop do
83
+ candidate = dir.join("log", "e11y_dev.jsonl")
84
+ return candidate.to_s if candidate.exist?
85
+
86
+ parent = dir.parent
87
+ break if parent == dir
88
+
89
+ dir = parent
90
+ end
91
+ "log/e11y_dev.jsonl"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module E11y
4
+ module Devtools
5
+ module Mcp
6
+ # Conditional base: use MCP::Tool if available, otherwise plain class.
7
+ # This allows tests to run without the mcp gem installed.
8
+ ToolBase = if defined?(MCP::Tool)
9
+ MCP::Tool
10
+ else
11
+ Class.new do
12
+ def self.description(desc = nil)
13
+ @description = desc if desc
14
+ @description
15
+ end
16
+
17
+ def self.input_schema(schema = nil)
18
+ @input_schema = schema if schema
19
+ @input_schema
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "mcp"
5
+ rescue LoadError
6
+ # mcp gem not available — ToolBase will use plain class
7
+ end
8
+ require_relative "../tool_base"
9
+
10
+ module E11y
11
+ module Devtools
12
+ module Mcp
13
+ module Tools
14
+ # Clears the E11y development log file.
15
+ class Clear < ToolBase
16
+ description "Clear the E11y development log file"
17
+
18
+ input_schema(
19
+ type: :object,
20
+ properties: {}
21
+ )
22
+
23
+ def self.call(server_context:, **_opts)
24
+ server_context[:store].clear!
25
+ "E11y log cleared successfully"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "mcp"
5
+ rescue LoadError
6
+ # mcp gem not available — ToolBase will use plain class
7
+ end
8
+ require_relative "../tool_base"
9
+
10
+ module E11y
11
+ module Devtools
12
+ module Mcp
13
+ module Tools
14
+ # Returns recent error and fatal events only.
15
+ class Errors < ToolBase
16
+ ERROR_SEVERITIES = %w[error fatal].freeze
17
+
18
+ description "Get recent error and fatal events only"
19
+
20
+ input_schema(
21
+ type: :object,
22
+ properties: {
23
+ limit: { type: :integer, description: "Max events", default: 20 }
24
+ }
25
+ )
26
+
27
+ def self.call(server_context:, limit: 20)
28
+ events = server_context[:store].stored_events(limit: limit * 5)
29
+ events.select { |e| ERROR_SEVERITIES.include?(e["severity"]) }.first(limit)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "mcp"
5
+ rescue LoadError
6
+ # mcp gem not available — ToolBase will use plain class
7
+ end
8
+ require_relative "../tool_base"
9
+
10
+ module E11y
11
+ module Devtools
12
+ module Mcp
13
+ module Tools
14
+ # Returns the full payload of a single event by ID.
15
+ class EventDetail < ToolBase
16
+ description "Get full payload of a single event by ID"
17
+
18
+ input_schema(
19
+ type: :object,
20
+ required: ["event_id"],
21
+ properties: {
22
+ event_id: { type: :string, description: "Event UUID" }
23
+ }
24
+ )
25
+
26
+ def self.call(event_id:, server_context:)
27
+ server_context[:store].find_event(event_id) || { error: "Event #{event_id} not found" }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "mcp"
5
+ rescue LoadError
6
+ # mcp gem not available — ToolBase will use plain class
7
+ end
8
+ require_relative "../tool_base"
9
+
10
+ module E11y
11
+ module Devtools
12
+ module Mcp
13
+ module Tools
14
+ # Returns all events for a specific trace ID in chronological order.
15
+ class EventsByTrace < ToolBase
16
+ description "Get all events for a specific trace ID in chronological order"
17
+
18
+ input_schema(
19
+ type: :object,
20
+ required: ["trace_id"],
21
+ properties: {
22
+ trace_id: { type: :string, description: "Trace ID" }
23
+ }
24
+ )
25
+
26
+ def self.call(trace_id:, server_context:)
27
+ server_context[:store].events_by_trace(trace_id)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "mcp"
5
+ rescue LoadError
6
+ # mcp gem not available — ToolBase will use plain class
7
+ end
8
+ require_relative "../tool_base"
9
+
10
+ module E11y
11
+ module Devtools
12
+ module Mcp
13
+ module Tools
14
+ # Returns time-grouped interactions (parallel requests from one user action).
15
+ class Interactions < ToolBase
16
+ description "Get time-grouped interactions (parallel requests from one user action)"
17
+
18
+ input_schema(
19
+ type: :object,
20
+ properties: {
21
+ limit: { type: :integer, description: "Max interactions", default: 20 },
22
+ window_ms: { type: :integer, description: "Grouping window in ms", default: 500 }
23
+ }
24
+ )
25
+
26
+ def self.call(server_context:, limit: 20, window_ms: 500)
27
+ server_context[:store].interactions(limit: limit, window_ms: window_ms).map do |ix|
28
+ {
29
+ started_at: ix.started_at.iso8601(3),
30
+ trace_ids: ix.trace_ids,
31
+ has_error: ix.has_error?,
32
+ traces_count: ix.traces_count
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "mcp"
5
+ rescue LoadError
6
+ # mcp gem not available — ToolBase will use plain class
7
+ end
8
+ require_relative "../tool_base"
9
+
10
+ module E11y
11
+ module Devtools
12
+ module Mcp
13
+ module Tools
14
+ # Returns the most recent events from the dev log.
15
+ class RecentEvents < ToolBase
16
+ description "Get recent E11y events from the development log"
17
+
18
+ input_schema(
19
+ type: :object,
20
+ properties: {
21
+ limit: { type: :integer, description: "Max events to return (default 50)", default: 50 },
22
+ severity: { type: :string, description: "Filter by severity",
23
+ enum: %w[debug info warn error fatal] }
24
+ }
25
+ )
26
+
27
+ def self.call(server_context:, limit: 50, severity: nil)
28
+ server_context[:store].stored_events(limit: limit, severity: severity)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "mcp"
5
+ rescue LoadError
6
+ # mcp gem not available — ToolBase will use plain class
7
+ end
8
+ require_relative "../tool_base"
9
+
10
+ module E11y
11
+ module Devtools
12
+ module Mcp
13
+ module Tools
14
+ # Full-text search across event names and payload content.
15
+ class Search < ToolBase
16
+ description "Full-text search across event names and payload content"
17
+
18
+ input_schema(
19
+ type: :object,
20
+ required: ["query"],
21
+ properties: {
22
+ query: { type: :string, description: "Search term" },
23
+ limit: { type: :integer, description: "Max results", default: 50 }
24
+ }
25
+ )
26
+
27
+ def self.call(query:, server_context:, limit: 50)
28
+ server_context[:store].search(query, limit: limit)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "mcp"
5
+ rescue LoadError
6
+ # mcp gem not available — ToolBase will use plain class
7
+ end
8
+ require_relative "../tool_base"
9
+
10
+ module E11y
11
+ module Devtools
12
+ module Mcp
13
+ module Tools
14
+ # Returns aggregate statistics about the E11y development log.
15
+ class Stats < ToolBase
16
+ description "Get aggregate statistics about the E11y development log"
17
+
18
+ input_schema(
19
+ type: :object,
20
+ properties: {}
21
+ )
22
+
23
+ def self.call(server_context:, **_opts)
24
+ server_context[:store].stats
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end