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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae2bf8e389d7c36d4f7061a13f7f4bb213c8365408f9d0c1fb818ea25b385ff7
4
- data.tar.gz: a3b8b537ea6bb06251edbc53a203d9c9e9c20a1d555679a722f00ff62eef9967
3
+ metadata.gz: 69576ecf67e1a978b29bbc96f594a01cd33c7e5510600f696bb5aca1cf5c0740
4
+ data.tar.gz: a1d42ffe1fc8339e0657c915cdaf1765fe9518659978a20ad8a18a9861823145
5
5
  SHA512:
6
- metadata.gz: 418df72b1757e7a941e645e171d00f012072c5f2974352851015cc3501adf6a546703a0b3cc38c35de7f1d6034b05c4bf13ce162815640a609612d395a40ac3d
7
- data.tar.gz: bef273caaf974979aa4a9e5dce7c4b0043efa47149a0b3f2bcba80874f7b3ff303e5f4672e90ab176d79306a4085ff6ed692f883709a37490e7df279595803e7
6
+ metadata.gz: 2a3a5c00b0803cf7bef974d5b9507519221a00794a6f17398674e089221d1102fe4fef1b2e2d7cdecd30d3b9e9b0f44f50f62b04ebb46c96df7a9f3a52710fac
7
+ data.tar.gz: 1ec0ec3676faa8055c984be855b0e89096c47a9b59eabc5aeba5d806e00f70a8e098ad61c4daa707114e0570bd2c655cd853896524f3855e01bedd29491eaade
@@ -6,34 +6,61 @@ Orchestrate a full release cycle for the source_monitor gem. This command handle
6
6
 
7
7
  - `$ARGUMENTS` -- Optional: version bump description or release notes summary. If empty, derive from commits since last tag.
8
8
 
9
+ ## Known Gotchas (Read Before Starting)
10
+
11
+ These are real issues encountered in previous releases. Each step below accounts for them, but keep them in mind:
12
+
13
+ 1. **Two version files**: Both `lib/source_monitor/version.rb` AND the top-level `VERSION` file must be bumped. The VBW pre-push hook checks for changes to `VERSION` (the top-level file).
14
+ 2. **Gemfile.lock sync**: After bumping the version in `version.rb`, you MUST run `bundle install` to update `Gemfile.lock`. CI runs `bundle install --frozen` which fails if the lockfile is stale.
15
+ 3. **VBW volatile files**: Files in `.vbw-planning/` (`.cost-ledger.json`, `.notification-log.jsonl`, `.session-log.jsonl`, `.hook-errors.log`) are continuously modified by VBW hooks. They should be in `.gitignore`. If they aren't, add them before proceeding.
16
+ 4. **Pre-push hook**: The VBW pre-push hook at `.git/hooks/pre-push` requires the `VERSION` file to appear in the diff for any push. For new branches, it compares the commit against the working tree -- any dirty files will trigger a false positive. For force-pushes to existing branches where `VERSION` hasn't changed since the last push, use `--no-verify`.
17
+ 5. **Single squashed commit**: Always create ONE commit on the release branch with ALL changes (version bump, changelog, Gemfile.lock, any fixes). Multiple commits cause pre-push hook issues.
18
+ 6. **Diff coverage CI gate**: The `test` CI job enforces diff coverage. Any changed lines in source code (not just test files) must have test coverage. If you change source code during the release (e.g., bug fixes), you must add corresponding tests.
19
+ 7. **Local main divergence after merge**: After the PR merges, local main will have different commits than origin/main (pre-squash vs merged). You must `git reset --hard origin/main` to sync -- this requires user approval since the sandbox blocks it.
20
+
9
21
  ## Step 1: Git Hygiene
10
22
 
11
23
  Run these checks. If ANY fail, STOP and report the issue to the user.
12
24
 
13
- 1. **Working tree clean**: `git status --porcelain` must be empty. If not, list the dirty files and ask the user whether to commit, stash, or abort.
14
- 2. **On main branch**: `git branch --show-current` must be `main`. If not, ask the user if they want to continue from the current branch or switch.
15
- 3. **Fetch latest**: `git fetch origin main`
16
- 4. **Up to date with remote**: Compare `git rev-parse HEAD` with `git rev-parse origin/main`. If behind, ask the user whether to pull.
17
- 5. **No unpushed commits**: Compare local HEAD with `origin/main`. If ahead, note how many commits are unpushed -- these will be included in the PR.
25
+ 1. **VBW volatile files gitignored**: Check that `.gitignore` contains these entries:
26
+ ```
27
+ .vbw-planning/.cost-ledger.json
28
+ .vbw-planning/.notification-log.jsonl
29
+ .vbw-planning/.session-log.jsonl
30
+ .vbw-planning/.hook-errors.log
31
+ ```
32
+ If any are missing, add them. If any of these files are tracked by git, remove them from tracking with `git rm --cached <file>`.
33
+
34
+ 2. **Working tree clean**: `git status --porcelain` must be empty (ignoring the VBW volatile files which should now be gitignored). If not, list the dirty files and ask the user whether to commit, stash, or abort.
35
+
36
+ 3. **On main branch**: `git branch --show-current` must be `main`. If not, ask the user if they want to continue from the current branch or switch.
37
+
38
+ 4. **Fetch latest**: `git fetch origin main`
39
+
40
+ 5. **Up to date with remote**: Compare `git rev-parse HEAD` with `git rev-parse origin/main`. If behind, ask the user whether to pull.
41
+
42
+ 6. **No unpushed commits**: Compare local HEAD with `origin/main`. If ahead, note how many commits are unpushed -- these will be included in the release.
18
43
 
19
44
  Report a summary:
20
45
  ```
21
46
  Git Status:
22
47
  Branch: main
23
48
  Clean: yes
49
+ VBW files gitignored: yes
24
50
  Synced with origin: yes
25
51
  Unpushed commits: N
26
52
  ```
27
53
 
28
54
  ## Step 2: Version Check
29
55
 
30
- 1. Read `lib/source_monitor/version.rb` to get the current VERSION.
31
- 2. Read the latest git tag with `git tag --sort=-v:refname | head -1`.
32
- 3. If the VERSION matches the latest tag (e.g., both are `0.3.2`), the version hasn't been bumped. Ask the user:
56
+ 1. Read `lib/source_monitor/version.rb` to get the current VERSION constant.
57
+ 2. Read the top-level `VERSION` file to verify it matches. If they differ, sync them before proceeding.
58
+ 3. Read the latest git tag with `git tag --sort=-v:refname | head -1`.
59
+ 4. If the VERSION matches the latest tag (e.g., both are `0.3.2`), the version hasn't been bumped. Ask the user:
33
60
  - "Current version is X.Y.Z which already has a tag. What should the new version be?"
34
61
  - Offer options: patch (X.Y.Z+1), minor (X.Y+1.0), major (X+1.0.0), or custom.
35
- - Update `lib/source_monitor/version.rb` with the new version.
36
- 4. If VERSION is ahead of the latest tag, proceed with the current version.
62
+ - Update BOTH `lib/source_monitor/version.rb` AND the top-level `VERSION` file with the new version.
63
+ 5. If VERSION is ahead of the latest tag, proceed with the current version.
37
64
 
38
65
  Store the release version for later steps. Do NOT commit yet -- that happens after the changelog is updated.
39
66
 
@@ -77,29 +104,35 @@ The changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) f
77
104
  - Insert the new versioned entry immediately after the `## [Unreleased]` block and before the previous release entry.
78
105
  - Preserve all existing entries below.
79
106
 
80
- ## Step 4: Commit Version Bump + Changelog
107
+ ## Step 4: Sync Gemfile.lock
81
108
 
82
- Once both `lib/source_monitor/version.rb` (if changed) and `CHANGELOG.md` are updated:
109
+ **CRITICAL**: After updating `version.rb`, the gemspec version changes and `Gemfile.lock` becomes stale.
83
110
 
84
- 1. Stage the changed files:
85
- ```
86
- git add lib/source_monitor/version.rb CHANGELOG.md
87
- ```
88
- 2. Create a single commit:
111
+ 1. Run `bundle install` to update `Gemfile.lock`.
112
+ 2. Verify the output shows the new version: `Using source_monitor X.Y.Z (was X.Y.Z-1)`.
113
+ 3. If `bundle install` fails, resolve the issue before proceeding.
114
+
115
+ ## Step 5: Create Release Branch with Single Squashed Commit
116
+
117
+ **IMPORTANT**: All release changes MUST be in a single commit on the release branch. This avoids pre-push hook issues where individual commits are checked for VERSION changes.
118
+
119
+ 1. Create the release branch from main: `git checkout -b release/vX.Y.Z`
120
+ 2. Stage ALL release files in one commit:
89
121
  ```
90
- chore: bump version to X.Y.Z and update changelog
122
+ git add lib/source_monitor/version.rb VERSION CHANGELOG.md Gemfile.lock
91
123
  ```
92
- If only the changelog changed (version was already bumped), use:
124
+ Also stage any other files that were changed (updated skills, docs, etc.).
125
+ 3. Create a single commit:
93
126
  ```
94
- chore: update changelog for vX.Y.Z release
127
+ chore: release vX.Y.Z
95
128
  ```
129
+ 4. Push the branch: `git push -u origin release/vX.Y.Z`
130
+ - If the pre-push hook blocks with a false positive (e.g., VBW files dirty in working tree despite being gitignored), use `git push -u --no-verify origin release/vX.Y.Z`. This is safe because we've verified VERSION is in the commit.
131
+ 5. If the push fails for other reasons, diagnose and fix before proceeding.
96
132
 
97
- ## Step 5: Create Release Branch and PR
133
+ ## Step 6: Create PR
98
134
 
99
- 1. Create a release branch: `git checkout -b release/vX.Y.Z`
100
- 2. Push the branch: `git push -u origin release/vX.Y.Z`
101
- 3. Generate a PR body from the new CHANGELOG.md entry (use the entry written in Step 3, not raw commits).
102
- 4. Create the PR using `gh pr create`:
135
+ 1. Create the PR using `gh pr create`:
103
136
  - Title: `Release vX.Y.Z`
104
137
  - Body format:
105
138
  ```
@@ -108,79 +141,85 @@ Once both `lib/source_monitor/version.rb` (if changed) and `CHANGELOG.md` are up
108
141
  <paste the CHANGELOG.md entry content here>
109
142
 
110
143
  ### Release Checklist
111
- - [x] Version bumped in `lib/source_monitor/version.rb`
144
+ - [x] Version bumped in `lib/source_monitor/version.rb` and `VERSION`
112
145
  - [x] CHANGELOG.md updated
146
+ - [x] Gemfile.lock synced
113
147
  - [ ] CI passes (lint, security, test, release_verification)
114
148
 
115
149
  ---
116
150
  Auto-generated release PR by `/release` command.
117
151
  ```
118
152
  - Base: `main`
119
- 5. Report the PR URL to the user.
153
+ 2. Report the PR URL to the user.
120
154
 
121
- ## Step 6: Monitor CI Pipeline
155
+ ## Step 7: Monitor CI Pipeline
122
156
 
123
- Poll the CI status using `gh pr checks <PR_NUMBER> --watch` or repeated `gh pr checks <PR_NUMBER>` calls. The CI has 4 required jobs: `lint`, `security`, `test`, `release_verification`.
157
+ Poll the CI status using repeated `gh pr checks <PR_NUMBER>` calls. The CI has 4 required jobs: `lint`, `security`, `test`, `release_verification` (release_verification only runs after test passes).
124
158
 
125
159
  1. Tell the user: "Monitoring CI pipeline for PR #N... This typically takes 3-5 minutes."
126
- 2. Poll with `gh pr checks <PR_NUMBER>` every 30 seconds, up to 15 minutes.
127
- 3. After each poll, report progress if any jobs completed.
160
+ 2. Wait 30 seconds before first poll (give CI time to start).
161
+ 3. Poll with `gh pr checks <PR_NUMBER>` every 60 seconds, up to 15 minutes.
162
+ 4. After each poll, report progress if any jobs completed or failed.
128
163
 
129
164
  ### If CI PASSES (all checks green):
130
165
 
131
- Continue to Step 7.
166
+ Continue to Step 8.
132
167
 
133
168
  ### If CI FAILS:
134
169
 
135
170
  1. Get the failure details: `gh pr checks <PR_NUMBER>` to identify which jobs failed.
136
- 2. For each failed job, fetch the log summary:
171
+ 2. For each failed job, fetch the log summary. Note: logs are only available after the **entire run** completes (not just one job). If `gh run view <RUN_ID> --log-failed` says "still in progress", wait and retry:
137
172
  ```
138
- gh run view <RUN_ID> --log-failed | tail -50
173
+ gh run view <RUN_ID> --log-failed | tail -80
139
174
  ```
140
- 3. Present to the user:
141
- ```
142
- CI Failed on PR #N
143
-
144
- Failed Jobs:
145
- - <job_name>: <brief summary of failure>
175
+ 3. **Common failure: diff coverage** -- If the `test` job fails on "Enforce diff coverage", it means changed source lines lack test coverage. Read the error to identify uncovered files/lines, write tests, and add them to the release commit.
176
+ 4. **Common failure: Gemfile.lock frozen** -- If `bundle install` fails in CI with "frozen mode", you forgot to run `bundle install` locally (Step 4).
177
+ 5. Present failure details to the user and ask what to do:
178
+ - "Fix the issues and re-push" -- Fix issues, amend the commit (`git commit --amend --no-edit`), force push (`git push --force-with-lease --no-verify origin release/vX.Y.Z`), and restart CI monitoring.
179
+ - "Close the PR and abort" -- Close the PR, delete the branch, switch back to main.
180
+ - "Investigate manually" -- Stop and let the user handle it.
146
181
 
147
- Log excerpt:
148
- <relevant log lines>
149
- ```
150
- 4. Ask the user what to do:
151
- - "Fix the issues and re-push" -- Help fix the issues, commit, push, and restart CI monitoring
152
- - "Close the PR and abort" -- Close the PR, delete the branch, switch back to main
153
- - "Investigate manually" -- Stop and let the user handle it
182
+ **Note on force pushes**: When force-pushing the release branch after amending, always use `--no-verify` because the pre-push hook will see the diff between old and new branch tips, and `VERSION` won't appear as changed (it's the same in both). This is expected and safe.
154
183
 
155
- ## Step 7: Auto-Merge PR
184
+ ## Step 8: Auto-Merge PR
156
185
 
157
186
  Once CI is green:
158
187
 
159
188
  1. Merge the PR: `gh pr merge <PR_NUMBER> --merge --delete-branch`
160
- 2. Switch back to main and pull: `git checkout main && git pull origin main`
189
+
190
+ 2. **Sync local main with remote** (this is the tricky part):
191
+ - The merge creates a merge commit on origin/main that doesn't exist locally.
192
+ - Local main may have different commits (pre-squash) than what was merged.
193
+ - Switch to main: `git checkout main`
194
+ - Try `git pull origin main`. If it fails with conflicts or divergence:
195
+ - Ask the user to run `git reset --hard origin/main` (the sandbox blocks this command).
196
+ - Explain this is safe because the PR is merged and all changes are on origin/main.
197
+ - Verify with `git log --oneline -3` that local matches remote.
198
+
161
199
  3. Report: "PR #N merged successfully."
162
200
 
163
- ## Step 8: Tag the Release
201
+ ## Step 9: Tag the Release
164
202
 
165
- 1. Create an annotated tag:
203
+ 1. Verify you're on main and synced with origin.
204
+ 2. Create an annotated tag:
166
205
  ```
167
206
  git tag -a vX.Y.Z -m "Release vX.Y.Z"
168
207
  ```
169
- 2. Push the tag: `git push origin vX.Y.Z`
170
- 3. Create a GitHub release from the tag:
208
+ 3. Push the tag: `git push origin vX.Y.Z`
209
+ 4. Create a GitHub release from the tag:
171
210
  ```
172
211
  gh release create vX.Y.Z --title "vX.Y.Z" --notes "<changelog entry from Step 3>"
173
212
  ```
174
- 4. Report the release URL.
213
+ 5. Report the release URL.
175
214
 
176
- ## Step 9: Build the Gem
215
+ ## Step 10: Build the Gem
177
216
 
178
- 1. Clean any old gem files: remove any `source_monitor-*.gem` files in the project root.
217
+ 1. Clean any old gem files: `ls source_monitor-*.gem` and remove them if found (don't error if none exist).
179
218
  2. Build the gem: `gem build source_monitor.gemspec`
180
219
  3. Verify the gem was built: check for `source_monitor-X.Y.Z.gem` in the project root.
181
- 4. Show the gem contents summary: `gem spec source_monitor-X.Y.Z.gem | head -30`
220
+ 4. Show the file size: `ls -la source_monitor-X.Y.Z.gem`
182
221
 
183
- ## Step 10: Gem Push Instructions
222
+ ## Step 11: Gem Push Instructions
184
223
 
185
224
  Present the final instructions to the user:
186
225
 
@@ -189,6 +228,7 @@ Release vX.Y.Z Complete!
189
228
 
190
229
  Git tag: vX.Y.Z (pushed)
191
230
  GitHub: <release URL>
231
+ PR: <PR URL> (merged)
192
232
  Gem built: source_monitor-X.Y.Z.gem
193
233
 
194
234
  To publish to RubyGems:
@@ -209,4 +249,7 @@ Do NOT run `gem push` automatically -- always let the user handle the OTP-protec
209
249
  - If any step fails unexpectedly, report what happened and where things stand.
210
250
  - If a release branch already exists, ask the user whether to reuse or recreate it.
211
251
  - If the tag already exists, skip tagging and inform the user.
252
+ - If the pre-push hook blocks, check whether `VERSION` (top-level) is in the commit diff. If it is and the hook still blocks (working tree drift from VBW), use `--no-verify`.
253
+ - If local main diverges from origin after merge, ask the user to run `git reset --hard origin/main`.
212
254
  - Always leave the user on the `main` branch in a clean state when possible.
255
+ - If `gem build` warns about duplicate URIs (homepage_uri/source_code_uri), this is cosmetic and safe to ignore.
@@ -29,7 +29,7 @@ After the block executes, `ModelExtensions.reload!` runs automatically to apply
29
29
 
30
30
  ## Configuration Sections
31
31
 
32
- The `config` object (`SourceMonitor::Configuration`) has 10 sub-sections plus top-level queue/job settings:
32
+ The `config` object (`SourceMonitor::Configuration`) has 11 sub-sections plus top-level queue/job settings:
33
33
 
34
34
  | Section | Accessor | Class |
35
35
  |---|---|---|
@@ -44,6 +44,7 @@ The `config` object (`SourceMonitor::Configuration`) has 10 sub-sections plus to
44
44
  | Models | `config.models` | `Models` |
45
45
  | Realtime | `config.realtime` | `RealtimeSettings` |
46
46
  | Authentication | `config.authentication` | `AuthenticationSettings` |
47
+ | Images | `config.images` | `ImagesSettings` |
47
48
 
48
49
  See `reference/configuration-reference.md` for every setting with types, defaults, and examples.
49
50
 
@@ -69,6 +70,14 @@ config.authentication.authenticate_with :authenticate_user!
69
70
  config.authentication.authorize_with ->(c) { c.current_user&.admin? }
70
71
  ```
71
72
 
73
+ ### Image Downloads (Active Storage)
74
+ ```ruby
75
+ config.images.download_to_active_storage = true
76
+ config.images.max_download_size = 5 * 1024 * 1024 # 5 MB
77
+ config.images.download_timeout = 15
78
+ config.images.allowed_content_types = %w[image/jpeg image/png image/webp]
79
+ ```
80
+
72
81
  ### Events
73
82
  ```ruby
74
83
  config.events.after_item_created { |e| Notifier.new_item(e.item) }
@@ -116,6 +125,7 @@ SourceMonitor.mission_control_dashboard_path # Resolved MC path or nil
116
125
  | `lib/source_monitor/configuration/model_definition.rb` | Per-model definition |
117
126
  | `lib/source_monitor/configuration/realtime_settings.rb` | Action Cable settings |
118
127
  | `lib/source_monitor/configuration/authentication_settings.rb` | Auth settings |
128
+ | `lib/source_monitor/configuration/images_settings.rb` | Image download settings |
119
129
  | `lib/source_monitor/configuration/validation_definition.rb` | Validation wrapper |
120
130
 
121
131
  ## References
@@ -146,7 +156,8 @@ end
146
156
  ## Checklist
147
157
 
148
158
  - [ ] Initializer exists at `config/initializers/source_monitor.rb`
149
- - [ ] Queue names match `config/solid_queue.yml` entries
159
+ - [ ] Queue names match `config/queue.yml` (or `config/solid_queue.yml`) entries
160
+ - [x] Dispatcher config includes `recurring_schedule: config/recurring.yml` (handled by install generator)
150
161
  - [ ] Authentication hooks configured for host auth system
151
162
  - [ ] HTTP timeouts appropriate for target feeds
152
163
  - [ ] Retention policy set for production
@@ -309,6 +309,39 @@ end
309
309
 
310
310
  ---
311
311
 
312
+ ## Images Settings (`config.images`)
313
+
314
+ Class: `SourceMonitor::Configuration::ImagesSettings`
315
+
316
+ Controls background downloading of inline images from feed content to Active Storage.
317
+
318
+ **Prerequisite:** The host app must have Active Storage installed (`rails active_storage:install` + migrations).
319
+
320
+ | Setting | Type | Default | Description |
321
+ |---|---|---|---|
322
+ | `download_to_active_storage` | Boolean | `false` | Enable background image downloading for new items |
323
+ | `max_download_size` | Integer | `10485760` (10 MB) | Maximum image file size in bytes; larger images are skipped |
324
+ | `download_timeout` | Integer | `30` | HTTP timeout for image downloads in seconds |
325
+ | `allowed_content_types` | Array | `["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"]` | Permitted MIME types for downloaded images |
326
+
327
+ ### Helper Method
328
+
329
+ | Method | Returns | Description |
330
+ |---|---|---|
331
+ | `download_enabled?` | Boolean | Returns `true` when `download_to_active_storage` is truthy |
332
+
333
+ ```ruby
334
+ # Enable image downloading with custom limits
335
+ config.images.download_to_active_storage = true
336
+ config.images.max_download_size = 5 * 1024 * 1024 # 5 MB
337
+ config.images.download_timeout = 15
338
+ config.images.allowed_content_types = %w[image/jpeg image/png image/webp]
339
+ ```
340
+
341
+ When enabled, `DownloadContentImagesJob` is automatically enqueued after new items are created from feed entries. The job downloads inline `<img>` images from `item.content`, attaches them to `item_content.images` via Active Storage, and rewrites the HTML with Active Storage serving URLs. Failed downloads gracefully preserve the original image URL.
342
+
343
+ ---
344
+
312
345
  ## Environment Variables
313
346
 
314
347
  | Variable | Purpose |
@@ -73,6 +73,10 @@ bin/rails db:migrate
73
73
  # 5. Start background workers (recurring jobs configured automatically in config/recurring.yml)
74
74
  bin/rails solid_queue:start
75
75
 
76
+ # Note: The generator automatically patches Procfile.dev with a jobs: entry
77
+ # and adds recurring_schedule to your queue.yml dispatcher config.
78
+ # Re-run the generator if these were not applied: bin/rails generate source_monitor:install
79
+
76
80
  # 6. Verify
77
81
  bin/source_monitor verify
78
82
  ```
@@ -86,7 +90,7 @@ gem "source_monitor", github: "dchuk/source_monitor"
86
90
 
87
91
  ## What the Install Generator Does
88
92
 
89
- The generator (`SourceMonitor::Generators::InstallGenerator`) performs three actions:
93
+ The generator (`SourceMonitor::Generators::InstallGenerator`) performs five actions:
90
94
 
91
95
  1. **Mounts the engine** in `config/routes.rb`:
92
96
  ```ruby
@@ -100,6 +104,12 @@ The generator (`SourceMonitor::Generators::InstallGenerator`) performs three act
100
104
  3. **Configures recurring jobs** in `config/recurring.yml`:
101
105
  Adds entries for `ScheduleFetchesJob` (every minute), scrape scheduling (every 2 minutes), `ItemCleanupJob` (2am daily), and `LogCleanupJob` (3am daily). If the file doesn't exist, creates it with `default: &default` and environment sections. If it exists, merges entries without overwriting. Skips if SourceMonitor entries are already present.
102
106
 
107
+ 4. **Patches Procfile.dev** with a `jobs:` entry for Solid Queue:
108
+ Creates the file with `web:` and `jobs:` entries if it does not exist. Appends a `jobs:` entry if the file exists but lacks one. Skips if a `jobs:` entry is already present.
109
+
110
+ 5. **Patches queue.yml dispatcher** with `recurring_schedule: config/recurring.yml`:
111
+ Adds the `recurring_schedule` key to each dispatcher entry in `config/queue.yml`. If no dispatchers section exists, creates a default one. Skips if `recurring_schedule` is already configured. Skips if `config/queue.yml` does not exist.
112
+
103
113
  Re-running the generator is safe and idempotent.
104
114
 
105
115
  ## Post-Install Configuration
@@ -180,6 +190,8 @@ config.authentication.user_signed_in_method = :user_signed_in?
180
190
  | `lib/generators/source_monitor/install/install_generator.rb` | Rails generator |
181
191
  | `lib/generators/source_monitor/install/templates/source_monitor.rb.tt` | Initializer template |
182
192
  | `lib/source_monitor/setup/initializer_patcher.rb` | Post-install patching |
193
+ | `lib/source_monitor/setup/procfile_patcher.rb` | Procfile.dev patching for guided workflow |
194
+ | `lib/source_monitor/setup/queue_config_patcher.rb` | Queue config patching for guided workflow |
183
195
  | `lib/source_monitor/setup/verification/runner.rb` | Verification runner |
184
196
  | `lib/source_monitor/engine.rb` | Engine configuration and initializers |
185
197
  | `docs/setup.md` | Full setup documentation |
@@ -221,6 +233,8 @@ end
221
233
  - [ ] Install generator ran (`bin/rails generate source_monitor:install`)
222
234
  - [ ] Engine migrations copied and applied
223
235
  - [ ] Recurring jobs configured in `config/recurring.yml`
236
+ - [x] `Procfile.dev` includes `jobs:` entry for Solid Queue (handled by generator)
237
+ - [x] Dispatcher config includes `recurring_schedule: config/recurring.yml` (handled by generator)
224
238
  - [ ] Solid Queue workers started
225
239
  - [ ] Authentication hooks configured in initializer
226
240
  - [ ] `bin/source_monitor verify` passes
@@ -86,6 +86,7 @@ production:
86
86
  dispatchers:
87
87
  - polling_interval: 1
88
88
  batch_size: 500
89
+ recurring_schedule: config/recurring.yml
89
90
  workers:
90
91
  - queues: "source_monitor_fetch"
91
92
  threads: 2
@@ -99,7 +100,39 @@ production:
99
100
  bin/rails solid_queue:start
100
101
  ```
101
102
 
103
+ ### Phase 6a: Procfile.dev for Development (Automatic)
104
+
105
+ The install generator automatically patches `Procfile.dev` with a `jobs:` entry for Solid Queue. If no `Procfile.dev` exists, it creates one with `web:` and `jobs:` entries. If the file exists but lacks a `jobs:` entry, it appends one. This is idempotent -- re-running the generator is safe.
106
+
107
+ Verify after running the generator:
108
+ ```
109
+ # Expected Procfile.dev content:
110
+ web: bin/rails server -p 3000
111
+ jobs: bundle exec rake solid_queue:start
112
+ ```
113
+
114
+ If the entry is missing, re-run: `bin/rails generate source_monitor:install`
115
+
116
+ ### Phase 6b: Recurring Schedule Dispatcher Wiring (Automatic)
117
+
118
+ The install generator automatically patches `config/queue.yml` dispatchers with `recurring_schedule: config/recurring.yml`. If no dispatchers section exists, it creates a default one. This is idempotent -- re-running the generator is safe.
119
+
120
+ Verify after running the generator:
121
+ ```yaml
122
+ # Expected in config/queue.yml under dispatchers:
123
+ dispatchers:
124
+ - polling_interval: 1
125
+ batch_size: 500
126
+ recurring_schedule: config/recurring.yml
127
+ ```
128
+
129
+ If the key is missing, re-run: `bin/rails generate source_monitor:install`
130
+
131
+ **Diagnostics:** Run `bin/source_monitor verify` to check that recurring tasks are registered. The RecurringScheduleVerifier will warn if no SourceMonitor recurring tasks are found in Solid Queue.
132
+
102
133
  - [ ] Queue configuration includes `source_monitor_fetch` and `source_monitor_scrape`
134
+ - [x] `Procfile.dev` includes a `jobs:` entry for Solid Queue (handled by generator)
135
+ - [x] Dispatcher config includes `recurring_schedule: config/recurring.yml` (handled by generator)
103
136
  - [ ] Workers started and processing
104
137
 
105
138
  ## Phase 7: Verify Installation
@@ -168,7 +168,7 @@ The install generator (`bin/rails generate source_monitor:install`) automaticall
168
168
  | `SourceMonitor::ItemCleanupJob` | at 2am every day |
169
169
  | `SourceMonitor::LogCleanupJob` | at 3am every day |
170
170
 
171
- These run automatically with `bin/dev` or `bin/jobs`. If you need to customize, edit `config/recurring.yml` directly.
171
+ The install generator automatically configures `config/recurring.yml` with these entries AND patches the `config/queue.yml` dispatcher with `recurring_schedule: config/recurring.yml` so recurring jobs load on startup. Both steps are idempotent. If you need to customize schedules, edit `config/recurring.yml` directly.
172
172
 
173
173
  ## Retry Policies
174
174
 
@@ -32,6 +32,28 @@ Core value: Drop-in Rails engine for feed monitoring, content scraping, and oper
32
32
  - [ ] **REQ-14**: Audit and fix any RuboCop violations against omakase ruleset
33
33
  - [ ] **REQ-15**: Ensure all models, controllers, and service objects follow Rails conventions
34
34
 
35
+ ### Generator Enhancements
36
+
37
+ - [ ] **REQ-16**: Install generator patches `Procfile.dev` with a `jobs:` entry for Solid Queue
38
+ - [ ] **REQ-17**: Install generator patches queue config dispatcher with `recurring_schedule: config/recurring.yml`
39
+ - [ ] **REQ-18**: Guided workflow (`Setup::Workflow`) integrates both new generator steps
40
+ - [ ] **REQ-19**: `RecurringScheduleVerifier` checks that recurring tasks are registered with Solid Queue
41
+ - [ ] **REQ-20**: `SolidQueueVerifier` remediation suggests `Procfile.dev` when workers not detected
42
+ - [ ] **REQ-21**: Skills and documentation updated to reflect automated Procfile.dev and recurring_schedule setup
43
+
44
+ ### Dashboard UX
45
+
46
+ - [ ] **REQ-22**: Fetch logs show source URL for both success and failure entries on the dashboard
47
+ - [ ] **REQ-23**: Dashboard links to sources and items are clickable and open in a new tab
48
+
49
+ ### Active Storage Image Downloads
50
+
51
+ - [ ] **REQ-24**: Configurable option to download inline images from items to Active Storage instead of loading from source
52
+
53
+ ### Feed Compatibility
54
+
55
+ - [ ] **REQ-25**: Investigate and fix failing fetch for Netflix Tech Blog feed (https://netflixtechblog.com/feed)
56
+
35
57
  ## v2 Requirements
36
58
 
37
59
  - [ ] **REQ-XX**: Improve optional dependency loading with clear error messages
@@ -0,0 +1,125 @@
1
+ <!-- VBW ROADMAP -- Phase decomposition with requirement mapping -->
2
+ <!-- Created during /vbw scope -->
3
+
4
+ # SourceMonitor Generator Enhancements Roadmap
5
+
6
+ **Milestone:** generator-enhancements
7
+ **Goal:** Make the install generator and verification suite catch the two most common host-app setup failures: missing Procfile.dev jobs entry and missing recurring_schedule dispatcher wiring.
8
+
9
+ ## Phases
10
+
11
+ 1. [x] ~~Phase 0: Documentation Gaps~~ (shipped via quick fix ea788ea)
12
+ 2. [x] Phase 1: Install Generator Steps (Procfile.dev + Queue Config)
13
+ 3. [x] Phase 2: Recurring Schedule Verifier
14
+ 4. [x] Phase 3: Skills & Documentation Alignment
15
+ 5. [x] Phase 4: Dashboard UX Improvements
16
+ 6. [x] Phase 5: Active Storage Image Downloads
17
+ 7. [x] Phase 6: Netflix Feed Investigation
18
+
19
+ ## Phase Details
20
+
21
+ ### Phase 1: Install Generator Steps
22
+
23
+ **Goal:** Add two new idempotent steps to the install generator: (a) patch `Procfile.dev` with a `jobs:` entry for Solid Queue, and (b) patch the queue config dispatcher with `recurring_schedule: config/recurring.yml`.
24
+
25
+ **Requirements:** REQ-16, REQ-17, REQ-18
26
+
27
+ **Success Criteria:**
28
+ - `bin/rails generate source_monitor:install` patches Procfile.dev when present (idempotent, skip if entry exists)
29
+ - `bin/rails generate source_monitor:install` patches queue.yml dispatcher with recurring_schedule (idempotent)
30
+ - Both steps wired into `Setup::Workflow` for the guided installer
31
+ - Generator tests cover: fresh file, existing file with entry, existing file without entry, missing file
32
+ - `bin/rails test` passes, RuboCop clean
33
+
34
+ ### Phase 2: Recurring Schedule Verifier
35
+
36
+ **Goal:** Add a `RecurringScheduleVerifier` to the verification suite that checks whether recurring tasks are actually registered with Solid Queue dispatchers, and enhance the existing `SolidQueueVerifier` to suggest Procfile.dev when workers aren't detected.
37
+
38
+ **Requirements:** REQ-19, REQ-20
39
+
40
+ **Success Criteria:**
41
+ - `bin/source_monitor verify` checks that recurring tasks are registered (not just that workers heartbeat)
42
+ - Warning when no recurring tasks found with actionable remediation message
43
+ - SolidQueueVerifier remediation mentions Procfile.dev for `bin/dev` users
44
+ - Verifier tests with mocked Solid Queue state
45
+ - `bin/rails test` passes, RuboCop clean
46
+
47
+ ### Phase 3: Skills & Documentation Alignment
48
+
49
+ **Goal:** Update all `sm-*` skills and docs to reflect that the generator now automatically handles Procfile.dev and recurring_schedule wiring. Remove manual steps that are now automated.
50
+
51
+ **Requirements:** REQ-21
52
+
53
+ **Success Criteria:**
54
+ - sm-host-setup skill reflects new generator capabilities (auto-patching, not manual steps)
55
+ - sm-configure skill references the automatic recurring_schedule wiring
56
+ - docs/setup.md updated to note the generator handles both automatically
57
+ - docs/troubleshooting.md updated with improved diagnostics
58
+ - setup-checklist.md reflects automation (checked by default, not manual)
59
+
60
+ ### Phase 4: Dashboard UX Improvements
61
+
62
+ **Goal:** Show source URLs in fetch log entries for both successes and failures on the dashboard, and make links to sources and items clickable (opening in a new tab).
63
+
64
+ **Requirements:** REQ-22, REQ-23
65
+
66
+ **Success Criteria:**
67
+ - Fetch log entries on the dashboard display the source URL alongside the existing summary
68
+ - Both success and failure fetch logs show the URL
69
+ - Source names and item titles are clickable links that open in a new tab
70
+ - Existing dashboard layout is preserved
71
+ - `bin/rails test` passes, RuboCop clean
72
+
73
+ ### Phase 5: Active Storage Image Downloads
74
+
75
+ **Goal:** Add a configurable option to download inline images from feed items to Active Storage instead of loading them directly from the source URL. This prevents broken images when sources go offline and improves page load performance.
76
+
77
+ **Requirements:** REQ-24
78
+
79
+ **Success Criteria:**
80
+ - New configuration option (`config.images.download_to_active_storage` or similar) defaults to `false`
81
+ - When enabled, inline images in item content are detected and downloaded to Active Storage
82
+ - Original image URLs are replaced with Active Storage URLs in the stored content
83
+ - Images that fail to download gracefully fall back to original URLs
84
+ - Configuration is documented in sm-configure skill
85
+ - `bin/rails test` passes, RuboCop clean
86
+
87
+ ### Phase 6: Netflix Feed Investigation
88
+
89
+ **Goal:** Investigate and fix the failing fetch for `https://netflixtechblog.com/feed`. Determine whether the issue is in the feed parser, HTTP client configuration, or content format, and apply appropriate fixes.
90
+
91
+ **Requirements:** REQ-25
92
+
93
+ **Success Criteria:**
94
+ - Root cause identified and documented
95
+ - Fix applied (parser, HTTP client, or configuration change)
96
+ - Netflix Tech Blog feed fetches successfully
97
+ - No regressions in other feed types
98
+ - `bin/rails test` passes, RuboCop clean
99
+
100
+ ## Progress
101
+
102
+ | Phase | Status | Plans |
103
+ |-------|--------|-------|
104
+ | 0 | Complete | - |
105
+ | 1 | Complete | PLAN-01 (5 tasks, 4 commits) |
106
+ | 2 | Complete | PLAN-01 (5 tasks, 1 commit) |
107
+ | 3 | Complete | PLAN-01 (5 tasks, 1 commit) |
108
+ | 4 | Complete | PLAN-01 (5 tasks, 5 commits) |
109
+ | 5 | Complete | PLAN-01 (4 tasks, 5 commits) + PLAN-02 (4 tasks, 4 commits) |
110
+ | 6 | Complete | PLAN-01 (5 tasks, 5 commits) |
111
+
112
+ ## Requirement Mapping
113
+
114
+ | REQ | Phase | Description |
115
+ |-----|-------|-------------|
116
+ | REQ-16 | 1 | Generator patches Procfile.dev with jobs: entry |
117
+ | REQ-17 | 1 | Generator patches queue config with recurring_schedule |
118
+ | REQ-18 | 1 | Guided workflow integrates both new steps |
119
+ | REQ-19 | 2 | RecurringScheduleVerifier checks recurring task registration |
120
+ | REQ-20 | 2 | SolidQueueVerifier remediation mentions Procfile.dev |
121
+ | REQ-21 | 3 | Skills and docs reflect automated setup |
122
+ | REQ-22 | 4 | Fetch logs show source URL on dashboard |
123
+ | REQ-23 | 4 | Dashboard links clickable in new tab |
124
+ | REQ-24 | 5 | Download inline images to Active Storage |
125
+ | REQ-25 | 6 | Fix Netflix Tech Blog feed fetch |