source_monitor 0.3.3 → 0.4.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +101 -58
  3. data/.claude/skills/sm-configure/SKILL.md +13 -2
  4. data/.claude/skills/sm-configure/reference/configuration-reference.md +33 -0
  5. data/.claude/skills/sm-host-setup/SKILL.md +15 -1
  6. data/.claude/skills/sm-host-setup/reference/setup-checklist.md +33 -0
  7. data/.claude/skills/sm-job/SKILL.md +1 -1
  8. data/.vbw-planning/REQUIREMENTS.md +22 -0
  9. data/.vbw-planning/ROADMAP.md +125 -0
  10. data/.vbw-planning/STATE.md +43 -0
  11. data/.vbw-planning/config.json +3 -1
  12. data/.vbw-planning/discovery.json +3 -1
  13. data/.vbw-planning/phases/01-generator-steps/01-CONTEXT.md +33 -0
  14. data/.vbw-planning/phases/01-generator-steps/01-VERIFICATION.md +86 -0
  15. data/.vbw-planning/phases/01-generator-steps/PLAN-01-SUMMARY.md +61 -0
  16. data/.vbw-planning/phases/01-generator-steps/PLAN-01.md +380 -0
  17. data/.vbw-planning/phases/02-verification/02-VERIFICATION.md +78 -0
  18. data/.vbw-planning/phases/02-verification/PLAN-01-SUMMARY.md +46 -0
  19. data/.vbw-planning/phases/02-verification/PLAN-01.md +500 -0
  20. data/.vbw-planning/phases/03-docs-alignment/03-VERIFICATION.md +89 -0
  21. data/.vbw-planning/phases/03-docs-alignment/PLAN-01-SUMMARY.md +48 -0
  22. data/.vbw-planning/phases/03-docs-alignment/PLAN-01.md +456 -0
  23. data/.vbw-planning/phases/04-dashboard-ux/04-VERIFICATION.md +129 -0
  24. data/.vbw-planning/phases/04-dashboard-ux/PLAN-01-SUMMARY.md +70 -0
  25. data/.vbw-planning/phases/04-dashboard-ux/PLAN-01.md +747 -0
  26. data/.vbw-planning/phases/05-active-storage-images/05-VERIFICATION.md +156 -0
  27. data/.vbw-planning/phases/05-active-storage-images/PLAN-01-SUMMARY.md +69 -0
  28. data/.vbw-planning/phases/05-active-storage-images/PLAN-01.md +455 -0
  29. data/.vbw-planning/phases/05-active-storage-images/PLAN-02-SUMMARY.md +39 -0
  30. data/.vbw-planning/phases/05-active-storage-images/PLAN-02.md +488 -0
  31. data/.vbw-planning/phases/06-netflix-feed-fix/06-VERIFICATION.md +100 -0
  32. data/.vbw-planning/phases/06-netflix-feed-fix/PLAN-01-SUMMARY.md +37 -0
  33. data/.vbw-planning/phases/06-netflix-feed-fix/PLAN-01.md +345 -0
  34. data/CHANGELOG.md +31 -0
  35. data/Gemfile.lock +1 -1
  36. data/VERSION +1 -1
  37. data/app/assets/builds/source_monitor/application.css +9 -0
  38. data/app/helpers/source_monitor/application_helper.rb +38 -0
  39. data/app/jobs/source_monitor/download_content_images_job.rb +72 -0
  40. data/app/models/source_monitor/item_content.rb +2 -0
  41. data/app/views/source_monitor/dashboard/_recent_activity.html.erb +9 -0
  42. data/app/views/source_monitor/items/_details.html.erb +2 -2
  43. data/app/views/source_monitor/logs/index.html.erb +9 -0
  44. data/app/views/source_monitor/sources/_details.html.erb +2 -2
  45. data/app/views/source_monitor/sources/_row.html.erb +1 -1
  46. data/docs/setup.md +10 -1
  47. data/docs/troubleshooting.md +38 -7
  48. data/lib/generators/source_monitor/install/install_generator.rb +101 -0
  49. data/lib/source_monitor/configuration/http_settings.rb +7 -1
  50. data/lib/source_monitor/configuration/images_settings.rb +37 -0
  51. data/lib/source_monitor/configuration.rb +3 -1
  52. data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +16 -7
  53. data/lib/source_monitor/dashboard/recent_activity.rb +1 -0
  54. data/lib/source_monitor/dashboard/recent_activity_presenter.rb +15 -2
  55. data/lib/source_monitor/fetching/feed_fetcher/entry_processor.rb +13 -0
  56. data/lib/source_monitor/http.rb +23 -0
  57. data/lib/source_monitor/images/content_rewriter.rb +81 -0
  58. data/lib/source_monitor/images/downloader.rb +82 -0
  59. data/lib/source_monitor/logs/table_presenter.rb +25 -0
  60. data/lib/source_monitor/setup/procfile_patcher.rb +31 -0
  61. data/lib/source_monitor/setup/queue_config_patcher.rb +84 -0
  62. data/lib/source_monitor/setup/verification/recurring_schedule_verifier.rb +102 -0
  63. data/lib/source_monitor/setup/verification/runner.rb +1 -1
  64. data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +1 -1
  65. data/lib/source_monitor/setup/workflow.rb +10 -0
  66. data/lib/source_monitor/version.rb +1 -1
  67. data/lib/source_monitor.rb +8 -0
  68. metadata +31 -1
@@ -0,0 +1,156 @@
1
+ ---
2
+ phase: 5
3
+ tier: standard
4
+ result: PASS
5
+ passed: 34
6
+ failed: 0
7
+ total: 34
8
+ date: 2026-02-12
9
+ ---
10
+
11
+ # Phase 5 Verification: Active Storage Image Downloads
12
+
13
+ ## Must-Have Checks
14
+
15
+ ### PLAN-01: images-config-model-rewriter
16
+
17
+ | # | Truth/Condition | Status | Evidence |
18
+ |---|-----------------|--------|----------|
19
+ | 1 | `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/configuration/images_settings_test.rb` exits 0 with 0 failures | PASS | 15 runs, 21 assertions, 0 failures |
20
+ | 2 | `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/images/content_rewriter_test.rb` exits 0 with 0 failures | PASS | 27 runs, 41 assertions, 0 failures |
21
+ | 3 | `PARALLEL_WORKERS=1 bin/rails test test/models/source_monitor/item_content_test.rb` exits 0 with 0 failures | PASS | 6 runs, 10 assertions, 0 failures |
22
+ | 4 | `bin/rubocop` on PLAN-01 files exits 0 with no offenses | PASS | 3 files inspected, 0 offenses |
23
+ | 5 | `SourceMonitor.config.images` returns an ImagesSettings instance | PASS | attr_reader :images in configuration.rb, @images = ImagesSettings.new |
24
+ | 6 | `SourceMonitor.config.images.download_to_active_storage` defaults to `false` | PASS | DEFAULT in ImagesSettings.reset! sets to false |
25
+ | 7 | `SourceMonitor.reset_configuration!` resets images settings to defaults | PASS | Tested in images_settings_test.rb (15 tests pass) |
26
+ | 8 | ContentRewriter.new(html).image_urls returns an array of absolute image URLs | PASS | ContentRewriter#image_urls method verified (27 tests pass) |
27
+ | 9 | ContentRewriter.new(html).rewrite { \|url\| new_url } replaces img src attributes | PASS | ContentRewriter#rewrite method verified (27 tests pass) |
28
+ | 10 | ItemContent responds to `images` (has_many_attached) | PASS | has_many_attached :images in item_content.rb (6 tests pass) |
29
+
30
+ ### PLAN-02: download-job-integration-docs
31
+
32
+ | # | Truth/Condition | Status | Evidence |
33
+ |---|-----------------|--------|----------|
34
+ | 11 | `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/images/downloader_test.rb` exits 0 with 0 failures | PASS | 11 runs, 18 assertions, 0 failures |
35
+ | 12 | `PARALLEL_WORKERS=1 bin/rails test test/jobs/source_monitor/download_content_images_job_test.rb` exits 0 with 0 failures | PASS | 10 runs, 29 assertions, 0 failures |
36
+ | 13 | `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/fetching/feed_fetcher/entry_processor_test.rb` exits 0 with 0 failures | PASS | 5 runs, 8 assertions, 0 failures |
37
+ | 14 | `bin/rubocop` on PLAN-02 files exits 0 with no offenses | PASS | 3 files inspected, 0 offenses |
38
+ | 15 | When `config.images.download_to_active_storage` is false (default), no job enqueued | PASS | entry_processor_test.rb verifies default behavior |
39
+ | 16 | When `config.images.download_to_active_storage` is true, job enqueued for new items with HTML | PASS | entry_processor_test.rb verifies enabled behavior |
40
+ | 17 | DownloadContentImagesJob downloads images, attaches to ItemContent, rewrites item.content | PASS | download_content_images_job_test.rb (10 tests verify full pipeline) |
41
+ | 18 | Images that fail to download preserve original URLs (graceful fallback) | PASS | download_content_images_job_test.rb tests individual failure handling |
42
+ | 19 | Images larger than max_download_size are skipped | PASS | downloader_test.rb tests size validation |
43
+ | 20 | Images with disallowed content types are skipped | PASS | downloader_test.rb tests content type validation |
44
+ | 21 | sm-configure skill documents the new config.images section | PASS | config.images in SKILL.md and configuration-reference.md |
45
+
46
+ ## Artifact Checks
47
+
48
+ | Artifact | Exists | Contains | Status |
49
+ |----------|--------|----------|--------|
50
+ | `lib/source_monitor/configuration/images_settings.rb` | YES | class ImagesSettings | PASS |
51
+ | `lib/source_monitor/images/content_rewriter.rb` | YES | class ContentRewriter | PASS |
52
+ | `test/lib/source_monitor/configuration/images_settings_test.rb` | YES | class ImagesSettingsTest (15 tests) | PASS |
53
+ | `test/lib/source_monitor/images/content_rewriter_test.rb` | YES | class ContentRewriterTest (27 tests) | PASS |
54
+ | `test/models/source_monitor/item_content_test.rb` | YES | 6 tests for has_many_attached | PASS |
55
+ | `lib/source_monitor/images/downloader.rb` | YES | class Downloader | PASS |
56
+ | `app/jobs/source_monitor/download_content_images_job.rb` | YES | class DownloadContentImagesJob | PASS |
57
+ | `test/lib/source_monitor/images/downloader_test.rb` | YES | 11 tests for Downloader | PASS |
58
+ | `test/jobs/source_monitor/download_content_images_job_test.rb` | YES | 10 tests for job | PASS |
59
+ | `test/lib/source_monitor/fetching/feed_fetcher/entry_processor_test.rb` | YES | 5 tests for integration hook | PASS |
60
+ | `.claude/skills/sm-configure/SKILL.md` | YES | config.images section | PASS |
61
+ | `.claude/skills/sm-configure/reference/configuration-reference.md` | YES | ImagesSettings documentation | PASS |
62
+
63
+ ## Key Link Checks
64
+
65
+ | From | To | Via | Status |
66
+ |------|----|----|--------|
67
+ | images_settings.rb#download_to_active_storage | REQ-24 | Configurable option defaults to false | PASS |
68
+ | content_rewriter.rb#image_urls | REQ-24 | Detects inline images in item content | PASS |
69
+ | content_rewriter.rb#rewrite | REQ-24 | Replaces original URLs with Active Storage URLs | PASS |
70
+ | item_content.rb#has_many_attached | REQ-24 | Images attached to ItemContent via Active Storage | PASS |
71
+ | download_content_images_job.rb#perform | REQ-24 | Downloads inline images to Active Storage | PASS |
72
+ | entry_processor.rb#enqueue_image_download | REQ-24 | Enqueues download job when config enabled | PASS |
73
+ | downloader.rb | REQ-24 | Validates size and content type before download | PASS |
74
+ | sm-configure/SKILL.md | REQ-24 | Configuration documented in skill | PASS |
75
+
76
+ ## Convention Compliance
77
+
78
+ | Convention | File | Status | Detail |
79
+ |------------|------|--------|--------|
80
+ | frozen_string_literal | images_settings.rb | PASS | Present in line 1 |
81
+ | frozen_string_literal | content_rewriter.rb | PASS | Present in line 1 |
82
+ | frozen_string_literal | downloader.rb | PASS | Present in line 1 |
83
+ | frozen_string_literal | download_content_images_job.rb | PASS | Present in line 1 |
84
+ | Test coverage | ImagesSettings | PASS | 15 tests covering defaults, accessors, reset, download_enabled? |
85
+ | Test coverage | ContentRewriter | PASS | 27 tests covering extraction, rewriting, edge cases |
86
+ | Test coverage | Downloader | PASS | 11 tests covering success/failure modes |
87
+ | Test coverage | DownloadContentImagesJob | PASS | 10 tests covering full pipeline and failures |
88
+ | Test coverage | EntryProcessor integration | PASS | 5 tests covering enabled/disabled/failure modes |
89
+ | Naming | ImagesSettings | PASS | Follows Configuration sub-class pattern |
90
+ | Naming | Images module | PASS | Follows engine module structure |
91
+ | Autoloading | Images::ContentRewriter | PASS | Autoloaded in lib/source_monitor.rb |
92
+ | Autoloading | Images::Downloader | PASS | Autoloaded in lib/source_monitor.rb |
93
+
94
+ ## Anti-Pattern Scan
95
+
96
+ | Pattern | Found | Location | Severity |
97
+ |---------|-------|----------|----------|
98
+ | TODO/HACK/FIXME/XXX | NO | lib/source_monitor/images/* | N/A |
99
+ | TODO/HACK/FIXME/XXX | NO | app/jobs/source_monitor/download_content_images_job.rb | N/A |
100
+ | .html_safe | NO | lib/source_monitor/images/* | N/A |
101
+ | Unsafe HTML rendering | NO | All new files | N/A |
102
+
103
+ ## Requirement Mapping
104
+
105
+ | Requirement | Plan Ref | Artifact Evidence | Status |
106
+ |-------------|----------|-------------------|--------|
107
+ | REQ-24: Configurable image download option | PLAN-01 | ImagesSettings with download_to_active_storage (default false) | PASS |
108
+ | REQ-24: Image detection in content | PLAN-01 | ContentRewriter#image_urls extracts URLs from HTML | PASS |
109
+ | REQ-24: Active Storage attachment | PLAN-01 | ItemContent has_many_attached :images | PASS |
110
+ | REQ-24: URL rewriting in content | PLAN-01 | ContentRewriter#rewrite replaces src attributes | PASS |
111
+ | REQ-24: Image download service | PLAN-02 | Downloader downloads, validates size/content-type | PASS |
112
+ | REQ-24: Background job orchestration | PLAN-02 | DownloadContentImagesJob performs full pipeline | PASS |
113
+ | REQ-24: Pipeline integration | PLAN-02 | EntryProcessor enqueues job for new items | PASS |
114
+ | REQ-24: Graceful fallback on failures | PLAN-02 | Failed downloads preserve original URLs | PASS |
115
+ | REQ-24: Zero behavior change when disabled | PLAN-02 | Default config (false) = no jobs enqueued | PASS |
116
+ | REQ-24: Documentation | PLAN-02 | sm-configure skill updated with config.images | PASS |
117
+
118
+ ## Summary
119
+
120
+ **Tier:** standard
121
+
122
+ **Result:** PASS
123
+
124
+ **Passed:** 34/34
125
+
126
+ **Failed:** None
127
+
128
+ ### Highlights
129
+
130
+ - **Configuration layer:** ImagesSettings with 4 configurable attributes (download_to_active_storage defaults to false, max_download_size 10MB, download_timeout 30s, allowed_content_types 5 image MIME types). Fully integrated into SourceMonitor.config.images.
131
+
132
+ - **Model layer:** ItemContent has_many_attached :images via Active Storage. Active Storage tables installed in dummy app for testing.
133
+
134
+ - **HTML processing:** ContentRewriter uses Nokolexbor to extract image URLs and rewrite img src attributes. Handles relative URLs, data: URIs, malformed URLs gracefully.
135
+
136
+ - **Download service:** Downloader validates content type and size, derives filenames, returns nil on any failure for graceful fallback.
137
+
138
+ - **Background job:** DownloadContentImagesJob orchestrates the full pipeline: extract URLs, download images, attach to ItemContent, rewrite item.content with Active Storage blob paths. Idempotent (skips if images already attached). Runs on fetch queue.
139
+
140
+ - **Integration hook:** EntryProcessor calls enqueue_image_download after item creation. Only fires when config enabled and item has HTML content. Wrapped in rescue to prevent feed processing breakage.
141
+
142
+ - **Documentation:** sm-configure skill and reference updated with full config.images section.
143
+
144
+ - **Test coverage:** 74 new tests (15 + 27 + 6 + 11 + 10 + 5) covering all scenarios. Full suite: 967 runs, 3100 assertions, 0 failures.
145
+
146
+ - **Code quality:** RuboCop 389 files, 0 offenses. All files have frozen_string_literal. No anti-patterns detected.
147
+
148
+ - **Zero behavior change:** Default config (download_to_active_storage = false) means no jobs enqueued, no Active Storage calls, no content rewriting. Existing pipelines unaffected.
149
+
150
+ ### Deviations
151
+
152
+ None. Both plans executed as specified. All must_haves verified. All artifacts present and tested.
153
+
154
+ ### Next Steps
155
+
156
+ Phase 5 complete. REQ-24 fully satisfied. Engine now supports configurable image downloads to Active Storage with graceful fallback on all failure modes.
@@ -0,0 +1,69 @@
1
+ # PLAN-01 Summary: images-config-model-rewriter
2
+
3
+ ## Status: COMPLETE
4
+
5
+ ## What Was Built
6
+
7
+ ### Task 1: Create ImagesSettings config class
8
+ - Added `ImagesSettings` class with `download_to_active_storage` (default false), `max_download_size` (10MB), `download_timeout` (30s), `allowed_content_types` (5 image MIME types)
9
+ - Wired into `Configuration` as `config.images` attr_reader, initialized in constructor
10
+ - Added `require` to configuration.rb and `Images` module autoload to lib/source_monitor.rb
11
+ - 15 tests covering defaults, accessors, reset!, download_enabled?, integration with SourceMonitor.config
12
+
13
+ ### Task 2: Install Active Storage and add attachment
14
+ - Created Active Storage migration in dummy app (`test/dummy/db/migrate/20260212000000_create_active_storage_tables.rb`)
15
+ - Active Storage tables (blobs, attachments, variant_records) added to dummy app schema
16
+ - Added `has_many_attached :images` to `ItemContent` model
17
+ - Created 1x1 PNG test fixture at `test/fixtures/files/test_image.png`
18
+ - 6 tests covering belongs_to, validates presence, images attachment, empty collection, single/multiple attach
19
+
20
+ ### Task 3: Create ContentRewriter class
21
+ - Added `ContentRewriter` class in `lib/source_monitor/images/content_rewriter.rb` using Nokolexbor
22
+ - `image_urls` method extracts absolute image URLs from `<img>` tags, skipping data: URIs and invalid URLs
23
+ - `rewrite` method yields each downloadable URL to a block and replaces src with return value; preserves original on nil return
24
+ - Handles relative URL resolution via `base_url` parameter, whitespace in src, self-closing tags
25
+ - 27 tests covering extraction, rewriting, and edge cases
26
+
27
+ ### Task 4: Update existing configuration tests
28
+ - Added 4 images settings tests to `configuration_test.rb` (accessible, defaults, configure block, reset)
29
+ - Added `ImagesSettingsInSettingsTest` class to `settings_test.rb` with 4 tests (defaults, reset, download_enabled?, type check)
30
+
31
+ ### Task 5: Full verification
32
+ - All 941 tests pass (0 failures, 0 errors)
33
+ - RuboCop: 384 files, 0 offenses (12 array bracket spacing issues auto-fixed)
34
+
35
+ ## Files Modified
36
+ - `lib/source_monitor/configuration/images_settings.rb` (new -- ImagesSettings class)
37
+ - `lib/source_monitor/configuration.rb` (added images_settings require, attr_reader, initialization)
38
+ - `lib/source_monitor.rb` (added Images module autoload)
39
+ - `app/models/source_monitor/item_content.rb` (added has_many_attached :images)
40
+ - `lib/source_monitor/images/content_rewriter.rb` (new -- ContentRewriter class)
41
+ - `test/lib/source_monitor/configuration/images_settings_test.rb` (new -- 15 tests)
42
+ - `test/models/source_monitor/item_content_test.rb` (new -- 6 tests)
43
+ - `test/lib/source_monitor/images/content_rewriter_test.rb` (new -- 27 tests)
44
+ - `test/lib/source_monitor/configuration_test.rb` (added 4 images tests)
45
+ - `test/lib/source_monitor/configuration/settings_test.rb` (added 4 images tests)
46
+ - `test/dummy/db/migrate/20260212000000_create_active_storage_tables.rb` (new -- Active Storage migration)
47
+ - `test/dummy/db/schema.rb` (updated with Active Storage tables)
48
+ - `test/fixtures/files/test_image.png` (new -- 1x1 PNG fixture)
49
+
50
+ ## Commits
51
+ - `884a3f6` feat(05-01): create-images-settings
52
+ - `52f4291` feat(05-01): install-active-storage-and-add-attachment
53
+ - `e199999` feat(05-01): create-content-rewriter
54
+ - `3954cb1` test(05-01): integration-test-and-config-test-update
55
+ - `05705a8` style(05-01): fix array bracket spacing in content_rewriter_test
56
+
57
+ ## Requirements Satisfied
58
+ - REQ-24 config: ImagesSettings with download_to_active_storage toggle (default false), size/timeout/content-type limits
59
+ - REQ-24 attachment: ItemContent has has_many_attached :images via Active Storage
60
+ - REQ-24 detection: ContentRewriter.image_urls extracts downloadable image URLs from HTML content
61
+ - REQ-24 URL replacement: ContentRewriter.rewrite replaces img src attributes via block
62
+ - REQ-24 graceful fallback: ContentRewriter preserves original URLs when rewrite block returns nil
63
+
64
+ ## Verification Results
65
+ - `bin/rails test`: 941 runs, 3045 assertions, 0 failures, 0 errors
66
+ - `bin/rubocop`: 384 files inspected, 0 offenses
67
+
68
+ ## Deviations
69
+ None. All tasks executed as specified in the plan.