source_monitor 0.1.1

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 (202) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rubocop.yml +12 -0
  4. data/.ruby-version +1 -0
  5. data/AGENTS.md +132 -0
  6. data/CHANGELOG.md +66 -0
  7. data/CONTRIBUTING.md +31 -0
  8. data/Gemfile +30 -0
  9. data/Gemfile.lock +411 -0
  10. data/MIT-LICENSE +20 -0
  11. data/README.md +108 -0
  12. data/Rakefile +8 -0
  13. data/app/assets/builds/.keep +0 -0
  14. data/app/assets/config/source_monitor_manifest.js +4 -0
  15. data/app/assets/images/source_monitor/.keep +0 -0
  16. data/app/assets/javascripts/source_monitor/application.js +20 -0
  17. data/app/assets/javascripts/source_monitor/controllers/async_submit_controller.js +36 -0
  18. data/app/assets/javascripts/source_monitor/controllers/dropdown_controller.js +109 -0
  19. data/app/assets/javascripts/source_monitor/controllers/modal_controller.js +56 -0
  20. data/app/assets/javascripts/source_monitor/controllers/notification_controller.js +53 -0
  21. data/app/assets/javascripts/source_monitor/turbo_actions.js +13 -0
  22. data/app/assets/stylesheets/source_monitor/application.tailwind.css +13 -0
  23. data/app/assets/svgs/source_monitor/.keep +0 -0
  24. data/app/controllers/concerns/.keep +0 -0
  25. data/app/controllers/concerns/source_monitor/sanitizes_search_params.rb +81 -0
  26. data/app/controllers/source_monitor/application_controller.rb +62 -0
  27. data/app/controllers/source_monitor/dashboard_controller.rb +27 -0
  28. data/app/controllers/source_monitor/fetch_logs_controller.rb +9 -0
  29. data/app/controllers/source_monitor/health_controller.rb +10 -0
  30. data/app/controllers/source_monitor/items_controller.rb +116 -0
  31. data/app/controllers/source_monitor/logs_controller.rb +15 -0
  32. data/app/controllers/source_monitor/scrape_logs_controller.rb +9 -0
  33. data/app/controllers/source_monitor/source_bulk_scrapes_controller.rb +35 -0
  34. data/app/controllers/source_monitor/source_fetches_controller.rb +22 -0
  35. data/app/controllers/source_monitor/source_health_checks_controller.rb +34 -0
  36. data/app/controllers/source_monitor/source_health_resets_controller.rb +27 -0
  37. data/app/controllers/source_monitor/source_retries_controller.rb +22 -0
  38. data/app/controllers/source_monitor/source_turbo_responses.rb +115 -0
  39. data/app/controllers/source_monitor/sources_controller.rb +179 -0
  40. data/app/helpers/source_monitor/application_helper.rb +327 -0
  41. data/app/jobs/source_monitor/application_job.rb +13 -0
  42. data/app/jobs/source_monitor/fetch_feed_job.rb +117 -0
  43. data/app/jobs/source_monitor/item_cleanup_job.rb +48 -0
  44. data/app/jobs/source_monitor/log_cleanup_job.rb +47 -0
  45. data/app/jobs/source_monitor/schedule_fetches_job.rb +29 -0
  46. data/app/jobs/source_monitor/scrape_item_job.rb +47 -0
  47. data/app/jobs/source_monitor/source_health_check_job.rb +77 -0
  48. data/app/mailers/source_monitor/application_mailer.rb +17 -0
  49. data/app/models/concerns/.keep +0 -0
  50. data/app/models/concerns/source_monitor/loggable.rb +18 -0
  51. data/app/models/source_monitor/application_record.rb +5 -0
  52. data/app/models/source_monitor/fetch_log.rb +31 -0
  53. data/app/models/source_monitor/health_check_log.rb +28 -0
  54. data/app/models/source_monitor/item.rb +102 -0
  55. data/app/models/source_monitor/item_content.rb +11 -0
  56. data/app/models/source_monitor/log_entry.rb +56 -0
  57. data/app/models/source_monitor/scrape_log.rb +31 -0
  58. data/app/models/source_monitor/source.rb +115 -0
  59. data/app/views/layouts/source_monitor/application.html.erb +54 -0
  60. data/app/views/source_monitor/dashboard/_fetch_schedule.html.erb +90 -0
  61. data/app/views/source_monitor/dashboard/_job_metrics.html.erb +82 -0
  62. data/app/views/source_monitor/dashboard/_recent_activity.html.erb +39 -0
  63. data/app/views/source_monitor/dashboard/_stat_card.html.erb +6 -0
  64. data/app/views/source_monitor/dashboard/_stats.html.erb +9 -0
  65. data/app/views/source_monitor/dashboard/index.html.erb +48 -0
  66. data/app/views/source_monitor/fetch_logs/show.html.erb +90 -0
  67. data/app/views/source_monitor/items/_details.html.erb +234 -0
  68. data/app/views/source_monitor/items/_details_wrapper.html.erb +3 -0
  69. data/app/views/source_monitor/items/index.html.erb +147 -0
  70. data/app/views/source_monitor/items/show.html.erb +3 -0
  71. data/app/views/source_monitor/logs/index.html.erb +208 -0
  72. data/app/views/source_monitor/scrape_logs/show.html.erb +73 -0
  73. data/app/views/source_monitor/shared/_toast.html.erb +34 -0
  74. data/app/views/source_monitor/sources/_bulk_scrape_form.html.erb +64 -0
  75. data/app/views/source_monitor/sources/_bulk_scrape_modal.html.erb +53 -0
  76. data/app/views/source_monitor/sources/_details.html.erb +302 -0
  77. data/app/views/source_monitor/sources/_details_wrapper.html.erb +3 -0
  78. data/app/views/source_monitor/sources/_empty_state_row.html.erb +5 -0
  79. data/app/views/source_monitor/sources/_fetch_interval_heatmap.html.erb +46 -0
  80. data/app/views/source_monitor/sources/_form.html.erb +143 -0
  81. data/app/views/source_monitor/sources/_health_status_badge.html.erb +46 -0
  82. data/app/views/source_monitor/sources/_row.html.erb +102 -0
  83. data/app/views/source_monitor/sources/edit.html.erb +28 -0
  84. data/app/views/source_monitor/sources/index.html.erb +153 -0
  85. data/app/views/source_monitor/sources/new.html.erb +22 -0
  86. data/app/views/source_monitor/sources/show.html.erb +3 -0
  87. data/config/coverage_baseline.json +2010 -0
  88. data/config/initializers/feedjira.rb +19 -0
  89. data/config/routes.rb +18 -0
  90. data/config/tailwind.config.js +17 -0
  91. data/db/migrate/20241008120000_create_source_monitor_sources.rb +40 -0
  92. data/db/migrate/20241008121000_create_source_monitor_items.rb +44 -0
  93. data/db/migrate/20241008122000_create_source_monitor_fetch_logs.rb +32 -0
  94. data/db/migrate/20241008123000_create_source_monitor_scrape_logs.rb +25 -0
  95. data/db/migrate/20251008183000_change_fetch_interval_to_minutes.rb +23 -0
  96. data/db/migrate/20251009090000_create_source_monitor_item_contents.rb +38 -0
  97. data/db/migrate/20251009103000_add_feed_content_readability_to_sources.rb +5 -0
  98. data/db/migrate/20251010090000_add_adaptive_fetching_toggle_to_sources.rb +7 -0
  99. data/db/migrate/20251010123000_add_deleted_at_to_source_monitor_items.rb +8 -0
  100. data/db/migrate/20251010153000_add_type_to_source_monitor_sources.rb +8 -0
  101. data/db/migrate/20251010154500_add_fetch_status_to_source_monitor_sources.rb +9 -0
  102. data/db/migrate/20251010160000_create_solid_cable_messages.rb +16 -0
  103. data/db/migrate/20251011090000_add_fetch_retry_state_to_sources.rb +14 -0
  104. data/db/migrate/20251012090000_add_health_fields_to_sources.rb +17 -0
  105. data/db/migrate/20251012100000_optimize_source_monitor_database_performance.rb +13 -0
  106. data/db/migrate/20251014064947_add_not_null_constraints_to_items.rb +30 -0
  107. data/db/migrate/20251014171659_add_performance_indexes.rb +29 -0
  108. data/db/migrate/20251014172525_add_fetch_status_check_constraint.rb +18 -0
  109. data/db/migrate/20251015100000_create_source_monitor_log_entries.rb +89 -0
  110. data/db/migrate/20251022100000_create_source_monitor_health_check_logs.rb +22 -0
  111. data/db/migrate/20251108120116_refresh_fetch_status_constraint.rb +29 -0
  112. data/docs/configuration.md +170 -0
  113. data/docs/deployment.md +63 -0
  114. data/docs/gh-cli-workflow.md +44 -0
  115. data/docs/installation.md +144 -0
  116. data/docs/troubleshooting.md +76 -0
  117. data/eslint.config.mjs +27 -0
  118. data/lib/generators/source_monitor/install/install_generator.rb +59 -0
  119. data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +155 -0
  120. data/lib/source_monitor/analytics/source_activity_rates.rb +53 -0
  121. data/lib/source_monitor/analytics/source_fetch_interval_distribution.rb +57 -0
  122. data/lib/source_monitor/analytics/sources_index_metrics.rb +92 -0
  123. data/lib/source_monitor/assets/bundler.rb +49 -0
  124. data/lib/source_monitor/assets.rb +6 -0
  125. data/lib/source_monitor/configuration.rb +654 -0
  126. data/lib/source_monitor/dashboard/queries.rb +356 -0
  127. data/lib/source_monitor/dashboard/quick_action.rb +7 -0
  128. data/lib/source_monitor/dashboard/quick_actions_presenter.rb +26 -0
  129. data/lib/source_monitor/dashboard/recent_activity.rb +30 -0
  130. data/lib/source_monitor/dashboard/recent_activity_presenter.rb +77 -0
  131. data/lib/source_monitor/dashboard/turbo_broadcaster.rb +87 -0
  132. data/lib/source_monitor/dashboard/upcoming_fetch_schedule.rb +126 -0
  133. data/lib/source_monitor/engine.rb +107 -0
  134. data/lib/source_monitor/events.rb +110 -0
  135. data/lib/source_monitor/feedjira_extensions.rb +103 -0
  136. data/lib/source_monitor/fetching/advisory_lock.rb +54 -0
  137. data/lib/source_monitor/fetching/completion/event_publisher.rb +22 -0
  138. data/lib/source_monitor/fetching/completion/follow_up_handler.rb +37 -0
  139. data/lib/source_monitor/fetching/completion/retention_handler.rb +30 -0
  140. data/lib/source_monitor/fetching/feed_fetcher.rb +627 -0
  141. data/lib/source_monitor/fetching/fetch_error.rb +88 -0
  142. data/lib/source_monitor/fetching/fetch_runner.rb +142 -0
  143. data/lib/source_monitor/fetching/retry_policy.rb +85 -0
  144. data/lib/source_monitor/fetching/stalled_fetch_reconciler.rb +146 -0
  145. data/lib/source_monitor/health/source_health_check.rb +100 -0
  146. data/lib/source_monitor/health/source_health_monitor.rb +210 -0
  147. data/lib/source_monitor/health/source_health_reset.rb +68 -0
  148. data/lib/source_monitor/health.rb +46 -0
  149. data/lib/source_monitor/http.rb +85 -0
  150. data/lib/source_monitor/instrumentation.rb +52 -0
  151. data/lib/source_monitor/items/item_creator.rb +601 -0
  152. data/lib/source_monitor/items/retention_pruner.rb +146 -0
  153. data/lib/source_monitor/items/retention_strategies/destroy.rb +26 -0
  154. data/lib/source_monitor/items/retention_strategies/soft_delete.rb +50 -0
  155. data/lib/source_monitor/items/retention_strategies.rb +9 -0
  156. data/lib/source_monitor/jobs/cleanup_options.rb +85 -0
  157. data/lib/source_monitor/jobs/fetch_failure_subscriber.rb +129 -0
  158. data/lib/source_monitor/jobs/solid_queue_metrics.rb +199 -0
  159. data/lib/source_monitor/jobs/visibility.rb +133 -0
  160. data/lib/source_monitor/logs/entry_sync.rb +69 -0
  161. data/lib/source_monitor/logs/filter_set.rb +163 -0
  162. data/lib/source_monitor/logs/query.rb +81 -0
  163. data/lib/source_monitor/logs/table_presenter.rb +161 -0
  164. data/lib/source_monitor/metrics.rb +77 -0
  165. data/lib/source_monitor/model_extensions.rb +109 -0
  166. data/lib/source_monitor/models/sanitizable.rb +76 -0
  167. data/lib/source_monitor/models/url_normalizable.rb +84 -0
  168. data/lib/source_monitor/pagination/paginator.rb +90 -0
  169. data/lib/source_monitor/realtime/adapter.rb +97 -0
  170. data/lib/source_monitor/realtime/broadcaster.rb +237 -0
  171. data/lib/source_monitor/realtime.rb +17 -0
  172. data/lib/source_monitor/release/changelog.rb +59 -0
  173. data/lib/source_monitor/release/runner.rb +73 -0
  174. data/lib/source_monitor/scheduler.rb +82 -0
  175. data/lib/source_monitor/scrapers/base.rb +105 -0
  176. data/lib/source_monitor/scrapers/fetchers/http_fetcher.rb +97 -0
  177. data/lib/source_monitor/scrapers/parsers/readability_parser.rb +101 -0
  178. data/lib/source_monitor/scrapers/readability.rb +156 -0
  179. data/lib/source_monitor/scraping/bulk_result_presenter.rb +85 -0
  180. data/lib/source_monitor/scraping/bulk_source_scraper.rb +233 -0
  181. data/lib/source_monitor/scraping/enqueuer.rb +125 -0
  182. data/lib/source_monitor/scraping/item_scraper/adapter_resolver.rb +44 -0
  183. data/lib/source_monitor/scraping/item_scraper/persistence.rb +189 -0
  184. data/lib/source_monitor/scraping/item_scraper.rb +84 -0
  185. data/lib/source_monitor/scraping/scheduler.rb +43 -0
  186. data/lib/source_monitor/scraping/state.rb +79 -0
  187. data/lib/source_monitor/security/authentication.rb +85 -0
  188. data/lib/source_monitor/security/parameter_sanitizer.rb +42 -0
  189. data/lib/source_monitor/sources/turbo_stream_presenter.rb +54 -0
  190. data/lib/source_monitor/turbo_streams/stream_responder.rb +95 -0
  191. data/lib/source_monitor/version.rb +3 -0
  192. data/lib/source_monitor.rb +149 -0
  193. data/lib/tasks/recover_stalled_fetches.rake +16 -0
  194. data/lib/tasks/source_monitor_assets.rake +28 -0
  195. data/lib/tasks/source_monitor_tasks.rake +29 -0
  196. data/lib/tasks/test_smoke.rake +12 -0
  197. data/package-lock.json +3997 -0
  198. data/package.json +29 -0
  199. data/postcss.config.js +6 -0
  200. data/source_monitor.gemspec +46 -0
  201. data/stylelint.config.js +12 -0
  202. metadata +469 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 90320cedd371ba574e94a72530899c9c186ee1e90f6b27dd221dca6e5831a626
4
+ data.tar.gz: 67db4a9fb817b386cbdffd675c44ec8e32cfa806ad4be284e95a80756180cf82
5
+ SHA512:
6
+ metadata.gz: 1f63265ddc4b7f5a2989a91c04b61645e573ed71b68aa0348197f0f0afb34d8f36b9d8127613d7c63b25df39d7e20603c31cfac445b9f431188bdaf4724f46ee
7
+ data.tar.gz: 6ceed78ef9db2de1d61904745c326016529a06f5ad7c56c07f0428bebf4d6c237a769532ec0e6ca5be795970833389e67c9a503b47283e48aa7af524a321d969
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /doc/
3
+ /log/*.log
4
+ /pkg/
5
+ /tmp/
6
+ /node_modules/
7
+ /coverage/
8
+ /test/dummy/db/*.sqlite3
9
+ /test/dummy/db/*.sqlite3-*
10
+ /test/dummy/log/*.log*
11
+ /test/dummy/storage/
12
+ /test/dummy/tmp/
13
+ /test/tmp/
14
+ /test/lib/tmp/install_generator/config/routes.rb
15
+ /app/assets/builds/*
16
+ !/app/assets/builds/.keep
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ # Omakase Ruby styling for Rails
2
+ inherit_gem: { rubocop-rails-omakase: rubocop.yml }
3
+
4
+ AllCops:
5
+ Exclude:
6
+ - "test/dummy/db/schema.rb"
7
+
8
+ # Overwrite or add rules to create your own house style
9
+ #
10
+ # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
11
+ # Layout/SpaceInsideArrayLiteralBrackets:
12
+ # Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.4
data/AGENTS.md ADDED
@@ -0,0 +1,132 @@
1
+ # Repository Guidelines
2
+
3
+ refer to ./ai/project_overview.md for full scope of this project
4
+
5
+ use rbenv for all ruby and bundler/gem commands, not the system ruby
6
+
7
+ ## Contribution Workflow
8
+
9
+ - Treat the engine like any external contributor would: no direct commits to `main`.
10
+ - Before writing code, branch off the latest `origin/main` (use a descriptive `feature/` or `bugfix/` prefix) and open a draft PR on `github.com/dchuk/source_monitor` referencing the active `.ai/tasks.md` slice.
11
+ - Push early and often to that branch so history stays visible; keep commits scoped and rebases local to your branch only.
12
+ - Move the PR out of draft once tests pass and the slice is ready for review; request at least one review and wait for all CI jobs (lint, security, Rails tests + diff coverage) to succeed before merging.
13
+ - The `test` job enforces diff coverage via `bin/check-diff-coverage`; if legitimate gaps remain after new code paths, refresh `config/coverage_baseline.json` by running `bin/test-coverage` followed by `bin/update-coverage-baseline` on the updated branch and commit the regenerated baseline.
14
+ - Merge via the PR UI (squash or rebase as agreed) after approval; avoid rewriting shared history post-push.
15
+ - Tag releases only after the release PR merges, then follow the checklist in `CHANGELOG.md`.
16
+
17
+ ## Library Documentation
18
+
19
+ - Use Context7 MCP constantly to look up fresh documentation for any task you're looking to complete, especially tasks that rely on using libraries or gems
20
+
21
+ ### Project Dependencies & context7 links
22
+
23
+ - Feedjirra - https://context7.com/feedjira/feedjira
24
+
25
+ ## Project Structure & Module Organization
26
+
27
+ The engine follows the Rails mountable layout generated by `rails plugin new source_monitor --mountable`. Runtime code lives under `app/` (controllers, jobs, views) and is namespaced as `SourceMonitor`. Long-lived services, adapters, and instrumentation helpers belong in `lib/source_monitor/`. Generator templates and install scripts reside in `lib/generators/source_monitor/`. Tests sit in `test/`, with fixtures under `test/fixtures/feeds/`, and the dummy host app in `test/dummy/` for integration coverage. Keep shared UI assets under `app/assets/` and engine configuration in `config/initializers/`.
28
+
29
+ ## Build, Test, and Development Commands
30
+
31
+ Run `bin/setup` to install gems, prepare the dummy database, and compile Tailwind. Use `bin/rails test` (or `bundle exec rake test`) for the full MiniTest suite, including system tests through the dummy app. During feature work, `bin/dev` starts the dummy app with Solid Queue workers and Tailwind watcher. Trigger a feed ingest locally with `bin/rails runner 'SourceMonitor::FetchFeeds.call'` once the fetcher service lands.
32
+
33
+ ## Background Job Defaults
34
+
35
+ - SourceMonitor ensures Solid Queue is the default adapter when the host app is still using the async adapter, but respects any explicit `ActiveJob` configuration already in place. Override queues/concurrency via `SourceMonitor.configure`.
36
+ - Queue names are namespaced (`source_monitor_fetch`/`source_monitor_scrape` by default) and automatically honor host `queue_name_prefix`. Use `SourceMonitor.queue_name(:fetch)` helpers inside jobs.
37
+ - Dashboard queue metrics read directly from Solid Queue tables via `SourceMonitor::Jobs::SolidQueueMetrics`. Host apps must install the Solid Queue migrations (reuse the engine's `20251009140000_create_solid_queue_tables.rb` or run `rails solid_queue:install`) for the card to surface ready/scheduled/failed counts; otherwise the UI falls back to an availability warning. Mission Control remains optional for deeper drill-downs.
38
+ - The dummy host keeps Solid Queue tables in the primary database via `20251009140000_create_solid_queue_tables.rb`. Real apps can either reuse that migration or run `rails solid_queue:install` to manage a dedicated queue database—Mission Control expects one of those setups before it can surface data.
39
+ - Recurring schedules live in `config/recurring.yml`, scheduling `SourceMonitor::ScheduleFetchesJob` each minute plus the scraping scheduler every two minutes. Override the schedule path with `bin/jobs --recurring_schedule_file=...` (or `SOLID_QUEUE_RECURRING_SCHEDULE_FILE`) and disable recurring runners with `SOLID_QUEUE_SKIP_RECURRING=true` or `bin/jobs --skip-recurring`.
40
+ - Hosts that need to wrap Solid Queue command execution can set `config.recurring_command_job_class` in the generated initializer to point at their custom job class.
41
+
42
+ ## Configuration DSL
43
+
44
+ - `SourceMonitor.configure` now exposes structured namespaces:
45
+ - `config.http` for Faraday timeouts, retry policy, proxy, and default headers. Per the [Faraday retry docs](https://github.com/lostisland/faraday/blob/main/docs/middleware/index.md), middleware options map 1:1 to the settings we surface (max retries, interval, backoff, statuses).
46
+ - `config.scrapers` registers/overrides adapters by name; adapters must inherit from `SourceMonitor::Scrapers::Base` and are discovered before constant lookup.
47
+ - `config.retention` supplies global defaults for `items_retention_days`, `max_items`, and the pruning strategy (`:destroy` or `:soft_delete`). Runtimes treat blank source fields as “inherit from config”.
48
+ - `config.models` lets host apps adjust table name prefixes, mix in concerns, and register custom validations per engine model. Use it to bolt on associations or STI-specific rules without monkey patches.
49
+ - `config.realtime` selects the Action Cable backend (`:solid_cable` by default). Solid Cable keeps Turbo streams in the primary database; set `config.realtime.adapter = :redis` and optionally `config.realtime.redis_url` when hosts prefer Redis.
50
+ - Install generator and dummy initializer list all knobs with comments—update those when slicing future roadmap items.
51
+
52
+ ## Retention Defaults
53
+
54
+ - Per-source retention settings live on `SourceMonitor::Source` (`items_retention_days` and `max_items`). Negative values are rejected; blank means unlimited.
55
+ - `SourceMonitor::Items::RetentionPruner` runs after every fetch via `SourceMonitor::Fetching::FetchRunner`, pruning stale items and their associated content/logs while keeping counter caches in sync.
56
+ - Age-based rules prune items when their published timestamp (or `created_at` fallback) is older than the configured window. Count-based rules keep the newest N items.
57
+ - `SourceMonitor::ItemCleanupJob` batches retention pruning across sources and can soft delete records (`rake source_monitor:cleanup:items` honours `SOFT_DELETE=true`, `SOURCE_IDS=1,2`). `SourceMonitor::LogCleanupJob` prunes old fetch/scrape logs (`rake source_monitor:cleanup:logs`, override `FETCH_LOG_DAYS` / `SCRAPE_LOG_DAYS`).
58
+ - Nightly recurring entries in `config/recurring.yml` enqueue both cleanup jobs by default; adjust schedules or disable via Solid Queue overrides as needed.
59
+
60
+ ## Coding Style & Naming Conventions
61
+
62
+ Use two-space indentation and Ruby 3.3 syntax. Keep engine classes under the `SourceMonitor::` namespace; new modules should mirror their directory, e.g., `lib/source_monitor/fetching/pipeline.rb`. Favor service objects ending in `Service`, jobs ending in `Job`, and background channels ending in `Channel`. Rails defaults handle formatting, but run `bundle exec rubocop` (configured via `.rubocop.yml`) before opening a PR. For views, stick with ERB and Tailwind utility classes.
63
+
64
+ ## Testing Guidelines
65
+
66
+ MiniTest drives coverage (`test/models`, `test/controllers`, `test/system`). Name files with `_test.rb` and wrap suites in `module SourceMonitor`. Add VCR cassettes under `test/vcr_cassettes/` when touching HTTP clients, and record new fixtures with descriptive names like `source_fetch_success.yml`. Target >90% coverage for new services and include regression tests for bug fixes. Use `bin/rails test test/system` before changing UI flows.
67
+
68
+ ## Commit & Pull Request Guidelines
69
+
70
+ Adopt imperative commit messages in the format `scope: action`, e.g., `sources: enforce URL normalization`. Group unrelated work into separate commits. PRs should describe context, summarise the slice delivered, list validation steps (`bin/rails test`, manual fetch run), and reference roadmap items from `.ai/tasks.md`. Include screenshots or console output when altering UI or background jobs. Request at least one review and ensure CI completes before merge.
71
+
72
+ ## Security & Configuration Tips
73
+
74
+ Store secrets (API keys, webhook tokens) in `config/credentials/` and never commit plain-text values. When adding HTTP endpoints or webhooks, default to Solid Queue middleware for retries and respect the allowlist in `config/source_monitor.yml`. Document new environment variables in `config/application.yml.sample` and call out any migrations that impact host apps.
75
+
76
+ # Clean coding guidelines
77
+
78
+ Object Orientated Design: You should write code that embraces change. Here’s how…
79
+
80
+ SRP: A class should do the smallest possible useful thing; it should have a single responsibility. A class that has more than one responsibility is difficult to reuse. SRP requires that a class be cohesive, that everything the class does be highly related to its purpose. A class that is easy to reuse will make the application easier to change.
81
+
82
+ Methods, like classes, should have a single responsibility. All of the same reasons apply, having just one responsibility makes them easy to change and easy to reuse.
83
+
84
+ DRY: DRY code tolerates change because any change in behaviour can be made by changing code in just one place.
85
+
86
+ Depend on behaviour not data:
87
+
88
+ Hide instance variables by wrapping them in a method, an attr_reader. The method changes a variable from data ( which is referenced all over) to behaviour (which is defined once). This means if you need to adjust the data you only have to make a change in one place.
89
+
90
+ Hide data structures using the Struct class.
91
+
92
+ Minimise Dependencies: An object depends on another object if, when one object changes, the other might be forced to change in turn. An object has a dependency when it knows:
93
+
94
+ The name of another class.
95
+
96
+ The name of a message that it intends to send to someone other than self.
97
+
98
+ The arguments that a message requires.
99
+
100
+ The order of those arguments.
101
+
102
+ Dependency Injection: Addresses the first dependency. It decouples two objects by moving the creation of object_A outside of object_B eg:
103
+
104
+ object_A.new(1,2,object_B.new)
105
+
106
+ Encapsulation: Addresses the second dependency. Create wrapper methods so that external messages are isolated in one place. The wrapper method is called throughout the class, rather than sending messages externally.
107
+
108
+ Use hashes for initialization arguments: this addresses the final dependencies.
109
+
110
+ Depend on things that change less often than you do
111
+
112
+ ## Agent Notes (2025-10-08)
113
+
114
+ - Finished roadmap section 05.03 (structured fetch error handling). Added `SourceMonitor::Fetching::FetchError` hierarchy, expanded `FeedFetcher` error handling, and ensured instrumentation payloads include success/error context.
115
+ - Fetch attempts now always create `FetchLog` records and update `Source` failure state; added MiniTest coverage for timeout, HTTP 404, and malformed feed scenarios.
116
+ - Relevant tests: `bundle exec rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb` and full suite via `bundle exec rails test` (both passing).
117
+ - Next roadmap item is 05.04 (not started). Untracked tmp dir `test/lib/tmp` can be safely ignored if it reappears.
118
+
119
+ ## Agent Notes (2025-10-09)
120
+
121
+ - Introduced `SourceMonitor::Scrapers::Base`, establishing the scraper adapter contract and Result object. Subclasses merge default, source, and invocation settings (HashWithIndifferentAccess) and must return a Result with status/html/content/metadata. Use `SourceMonitor::Scrapers::Base.call(item:, source:, settings:, http:)` to execute adapters.
122
+ - Added Readability scraper adapter leveraging SourceMonitor::HTTP, Nokolexbor, and ruby-readability. Supports override selectors via `scrape_settings[:selectors]`, records extraction metadata including strategy and inferred status, and returns structured failure results on HTTP errors.
123
+ - Extracted scraper HTTP fetching and parsing into dedicated collaborators (`SourceMonitor::Scrapers::Fetchers::HttpFetcher` and `SourceMonitor::Scrapers::Parsers::ReadabilityParser`). The adapter now orchestrates these services, simplifying future parser additions while keeping failure metadata consistent.
124
+ - Added Readability scraper adapter leveraging SourceMonitor::HTTP, Nokolexbor, and ruby-readability. Supports override selectors via `scrape_settings[:selectors]`, records extraction metadata including strategy and inferred status, and returns structured failure results on HTTP errors.
125
+ - Phase 07.03 complete: introduced `SourceMonitor::ItemContent` table with one-to-one association, virtual accessors on `Item`, and migration that backfills existing scraped data while dropping legacy columns. Clearing both fields now removes the content row. Run `bundle exec rails db:migrate` in host apps after upgrading.
126
+ - Phase 07.04 complete: added `SourceMonitor::Scraping::ItemScraper` service with log recording, manual scrape controls in the item admin, and a dedicated scraping configuration section on the source form. Item show page now highlights content comparisons, status badges, and recent scrape errors. System coverage updated to exercise the manual scraping flow.
127
+ - Phase 08.02 complete: created `SourceMonitor::FetchFeedJob`, introduced `SourceMonitor::Fetching::FetchRunner` with Postgres advisory locking, and added a temporary `ScrapeItemJob` stub so new items from auto-scrape sources are queued with `scrape_status` set to `pending`. Manual fetch UI now reuses the runner, and tests cover job retry behavior plus follow-up scraping enqueue (`bundle exec rails test test/lib/source_monitor/fetching/fetch_runner_test.rb`, `bundle exec rails test test/jobs/source_monitor/fetch_feed_job_test.rb`, and `bundle exec rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb`).
128
+ - Phase 08.03 complete: implemented `SourceMonitor::Scraping::Enqueuer` with deduplication/auto-scrape guards, promoted `SourceMonitor::ScrapeItemJob` to execute `ItemScraper`, and updated the manual item view to enqueue jobs instead of running scrapes inline. Added unit coverage for the enqueuer/job plus refreshed the manual scrape system test (`bundle exec rails test test/lib/source_monitor/scraping/enqueuer_test.rb test/jobs/source_monitor/scrape_item_job_test.rb test/system/items_test.rb test/lib/source_monitor/fetching/fetch_runner_test.rb`).
129
+ - Phase 08.04 complete: manual "Fetch Now" now queues `FetchFeedJob` via `FetchRunner.enqueue`, dashboard shows live queue metrics from `SourceMonitor::Jobs::Visibility`, and optional Mission Control link appears when configured. System tests updated to assert job enqueues (`test/system/sources_test.rb`, `test/system/items_test.rb`, `test/system/dashboard_test.rb`); run `bundle exec rails test test/system` after UI tweaks.
130
+ - Install generator now also creates `config/initializers/source_monitor.rb` with documented defaults for queue settings, metrics, and Mission Control hooks. Update the initializer if future configuration options are added.
131
+ - `SourceMonitor.mission_control_dashboard_path` now validates that the configured path exists before rendering a dashboard link, preventing 404s when Mission Control isn't mounted yet. Template comments outline how to mount the engine when enabling it.
132
+ - `test/dummy/bin/dev` now runs the Rails server with `BUNDLE_GEMFILE` pointing at the dummy app, so dummy-only gems (like mission_control-jobs) load correctly while the Tailwind watcher still uses the engine's Gemfile.
data/CHANGELOG.md ADDED
@@ -0,0 +1,66 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented below. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to Semantic Versioning.
4
+
5
+ ## Release Checklist
6
+
7
+ 1. `rbenv exec bundle exec rails test`
8
+ 2. `rbenv exec bundle exec rubocop`
9
+ 3. `rbenv exec bundle exec rake app:source_monitor:assets:verify`
10
+ 4. `rbenv exec bundle exec gem build source_monitor.gemspec`
11
+ 5. Update release notes in this file and tag the release (`git tag vX.Y.Z`)
12
+ 6. Push tags and publish the gem (`rbenv exec gem push pkg/source_monitor-X.Y.Z.gem`)
13
+
14
+ ## [Unreleased]
15
+
16
+ - No unreleased changes yet.
17
+
18
+ ## [0.1.1] - 2025-11-09
19
+
20
+ ### Changed
21
+
22
+ - Bumped the gem to 0.1.1 so the republished package on RubyGems matches the revamped 0.1.0 release notes without reusing the yanked version number.
23
+
24
+ ### Fixed
25
+
26
+ - Clarified that the 0.1.0 entry now reflects the authoritative feature overview for the first release, preventing consumers from encountering inconsistent documentation across yanks.
27
+
28
+ ## [0.1.0] - 2025-11-08
29
+
30
+ ### Added
31
+
32
+ - Shipped the initial SourceMonitor mountable Rails engine with Source and Item models, Tailwind-powered admin UI, Turbo-powered dashboards, and a dummy host app for full-stack validation.
33
+ - Implemented the full feed ingestion pipeline: Feedjira-based fetcher, Faraday HTTP stack with retry/timeout controls, adaptive scheduling, structured error types, retention policies, and fetch log instrumentation surfaced in the UI.
34
+ - Introduced comprehensive scraping support with a scraper adapter base class, Readability parser, dedicated `ItemContent` storage, manual/bulk scrape controls, and queue-backed `ScrapeItemJob` orchestration.
35
+ - Established Solid Queue and Solid Cable defaults, including recurring schedule config, Mission Control hooks, `FetchFeedJob`/`ScheduleFetchesJob`, queue metrics dashboards, and helper APIs for namespaced queue names.
36
+ - Added health monitoring, failure recovery controls, analytics widgets (heatmaps, distribution insights), and notification hooks so operators can triage outages and re-run work with confidence.
37
+ - Delivered install tooling—generator, initializer template, cleanup/retention rake tasks, host harness smoke tests, and example host templates—plus Faraday/HTTP, scraper, retention, realtime, and mission control configuration DSLs.
38
+
39
+ ### Changed
40
+
41
+ - Rebranded the engine, routes, and namespaces to `SourceMonitor`, aligning configuration defaults, installer output, and docs with the new identity.
42
+ - Modernized the asset and JavaScript pipeline (esbuild, bundler, Stimulus fixes) and widened admin layouts, sortable tables, and bulk action UX for better operator ergonomics.
43
+ - Restructured source member actions into nested REST resources (fetch, retry, bulk scrape) and consolidated log views/analytics for clearer operator workflows.
44
+
45
+ ### Fixed
46
+
47
+ - Hardened scheduler behavior to avoid duplicate catch-up fetches, ensured stalled fetch recovery paths requeue work, and guaranteed fetch failure callbacks always attach logs/state.
48
+ - Resolved Solid Cable initialization issues, host Action Cable dependencies, and dummy host/environment parity problems so realtime updates function out of the box.
49
+ - Stabilized the host harness across Ruby versions, added Postgres-backed CI services, patched rbenv mismatches, and tightened sqlite shims plus asset/database setup to keep tests green on every platform.
50
+
51
+ ### Documentation
52
+
53
+ - Published install and upgrade guides, roadmap phase notes, PR workflow requirements, health configuration guidance, and mission control instructions; expanded AGENT guidance for future contributors.
54
+
55
+ ### CI/CD
56
+
57
+ - Added layered coverage guardrails (diff coverage enforcement, result-set merging, targeted health coverage suites), automated release verification, and artifact preservation across the packaging workflow.
58
+ - Upgraded GitHub Actions dependencies, introduced reusable workflows for test/lint/build jobs, and ensured release verification prepares databases, locks dependencies, and emits the packaged gem.
59
+
60
+ ### Upgrade Notes
61
+
62
+ 1. Add `gem "source_monitor", "~> 0.1.0"` to your host `Gemfile` and run `rbenv exec bundle install`.
63
+ 2. Execute `rbenv exec bin/rails railties:install:migrations FROM=source_monitor` followed by `rbenv exec bin/rails db:migrate` to copy and run Solid Queue + SourceMonitor migrations.
64
+ 3. Review `config/initializers/source_monitor.rb` for queue, scraping, retention, HTTP, and Mission Control settings; adjust the generated defaults to fit your environment.
65
+ 4. If you surface Mission Control Jobs from the dashboard, ensure `mission_control-jobs` stays mounted and `SourceMonitor.mission_control_dashboard_path` resolves correctly.
66
+ 5. Restart Solid Queue workers, Solid Cable (or Redis Action Cable), and any recurring job runners to pick up the new engine version.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,31 @@
1
+ # Contributing Guidelines
2
+
3
+ This project ships each roadmap slice behind a fast, reliable test loop. The notes below summarize the expectations for day-to-day development and code review.
4
+
5
+ ## Test Suite Expectations
6
+
7
+ - **Fast feedback:** run `bundle exec rake app:test:smoke` for unit, helper, and job coverage before pushing. Use `SOURCE_MONITOR_TEST_WORKERS=1` locally when profiling failures for determinism.
8
+ - **Full validation:** continue to run `bundle exec rails test` (or `bin/test-coverage` in CI) before marking a slice ready for review.
9
+ - **Background jobs:** the default adapter is `:test`. Switch to inline execution only for the precise block that needs it via `with_inline_jobs { ... }`; never flip the global adapter.
10
+ - **Database usage:** prefer transactional fixtures over manual `delete_all`. Reach for `setup_once` (TestProf’s `before_all`) when immutable data can be shared safely across examples.
11
+ - **System tests:** keep them focused on UI workflows. If a test only exercises server logic, convert it to a controller/lib test. Stub external services and avoid long flows.
12
+
13
+ ## Performance Profiling
14
+
15
+ - **Local commands:**
16
+ - `TAG_PROF=type SOURCE_MONITOR_TEST_WORKERS=1 bundle exec rails test`
17
+ - `EVENT_PROF=sql.active_record SOURCE_MONITOR_TEST_WORKERS=1 bundle exec rails test`
18
+ - `TEST_STACK_PROF=1 SOURCE_MONITOR_TEST_WORKERS=1 bundle exec rails test test/integration`
19
+ - **CI automation:** a scheduled `profiling` workflow runs the commands above nightly, archives `tmp/test_prof`, and applies guardrails via `bin/check-test-prof-metrics` (suite ≤ 80s, integration ≤ 35s, DB time ≤ 5s). Failures block the job so regressions surface quickly.
20
+
21
+ ## Code Review Checklist
22
+
23
+ Before approval, verify that the changes:
24
+
25
+ 1. Avoid unnecessary database persistence (`build_stubbed`/test doubles over `.create` when assertions allow).
26
+ 2. Share heavy setup with `setup_once` or helper memoization instead of per-test rebuilds.
27
+ 3. Scope inline job execution to `with_inline_jobs { ... }` blocks—no suite-wide adapter swaps.
28
+ 4. Keep new or modified system specs lean, relying on lower-level coverage when UI is not essential.
29
+ 5. Include updates to smoke/full-test instructions or profiling docs when new flows require them.
30
+
31
+ Thanks for keeping the suite fast and predictable. Profile early and often, and treat the guardrails as hard limits when reviewing new slices.
data/Gemfile ADDED
@@ -0,0 +1,30 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in source_monitor.gemspec.
4
+ gemspec
5
+
6
+ gem "puma"
7
+
8
+ gem "pg"
9
+
10
+ gem "propshaft"
11
+
12
+ # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
13
+ gem "rubocop-rails-omakase", require: false
14
+
15
+ group :development, :test do
16
+ gem "brakeman", require: false
17
+ end
18
+
19
+ # Start debugger with binding.b [https://github.com/ruby/debug]
20
+ # gem "debug", ">= 1.0.0"
21
+
22
+ group :test do
23
+ gem "simplecov", require: false
24
+ gem "test-prof", require: false
25
+ gem "stackprof", require: false
26
+ gem "capybara"
27
+ gem "webmock"
28
+ gem "vcr"
29
+ gem "selenium-webdriver"
30
+ end