source_monitor 0.3.2 → 0.3.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24dc578d91173b255c4d1a4ecc7d85542c5c2a86daabac9ee779b73c4f012ed5
4
- data.tar.gz: 43f2ae2005109c50a5c3298c315a48092926411e02a8a4715459e39304324e14
3
+ metadata.gz: ae2bf8e389d7c36d4f7061a13f7f4bb213c8365408f9d0c1fb818ea25b385ff7
4
+ data.tar.gz: a3b8b537ea6bb06251edbc53a203d9c9e9c20a1d555679a722f00ff62eef9967
5
5
  SHA512:
6
- metadata.gz: 640830590b4e1638b0fd2e90c337f49f41c788948abd7a2552338a15e80b13560da320d449ec55fad12a6a9724916af2e1a0942129ca396e9c11f0ddb3abcc89
7
- data.tar.gz: 5dae4fbfe5864a33fd2895a275f2511d014e5a94ccf49a19bc1f4d0c284bd9045a561c003e945fec3acf9565b116f868616c45566310857675f3b02d3768f0e8
6
+ metadata.gz: 418df72b1757e7a941e645e171d00f012072c5f2974352851015cc3501adf6a546703a0b3cc38c35de7f1d6034b05c4bf13ce162815640a609612d395a40ac3d
7
+ data.tar.gz: bef273caaf974979aa4a9e5dce7c4b0043efa47149a0b3f2bcba80874f7b3ff303e5f4672e90ab176d79306a4085ff6ed692f883709a37490e7df279595803e7
@@ -0,0 +1,34 @@
1
+ # VBW Dev Agent Memory
2
+
3
+ ## Project: source_monitor
4
+ - Rails engine gem for RSS/feed monitoring
5
+ - Ruby 3.4.4, Rails 8.x
6
+ - Test suite: 473 tests via `bin/rails test` (takes ~76 seconds)
7
+ - RuboCop uses `rubocop-rails-omakase` base config
8
+
9
+ ## Key Learnings
10
+
11
+ ### Shell/Bash in zsh
12
+ - `!` in zsh inline scripts causes `command not found` errors. Use `case` statements instead of `if ! ...` patterns.
13
+ - Use `IFS= read -r` when reading filenames from pipes to handle edge cases.
14
+
15
+ ### RuboCop scope vs git scope
16
+ - RuboCop inspects ALL Ruby-like files (Gemfile, Rakefile, .gemspec, .rake, bin/*, config.ru), not just `.rb` files.
17
+ - `git ls-files -- '*.rb'` only matches `.rb` extensions. Plans scoped to `.rb` files may miss RuboCop violations in non-`.rb` Ruby files.
18
+ - `test/tmp/` contains untracked generated Rails app templates. These are NOT git-tracked but RuboCop scans them unless excluded.
19
+
20
+ ### File structure
21
+ - `test/lib/tmp/install_generator/` contains test fixtures (1 tracked file: `config/initializers/source_monitor.rb`)
22
+ - `test/tmp/host_app_template_*` directories are generated test artifacts, not git-tracked
23
+ - `.rubocop.yml` is at project root, inherits from `rubocop-rails-omakase`
24
+
25
+ ### Frozen string literal pragma
26
+ - Completed in commit `5f02db8` -- 113 files modified
27
+ - Added `test/tmp/**/*` to RuboCop exclude list
28
+
29
+ ### RuboCop omakase ruleset details
30
+ - Only 45 cops enabled (out of 775 available)
31
+ - ALL Metrics cops disabled (ClassLength, MethodLength, BlockLength, etc.)
32
+ - After frozen_string_literal fix, codebase had zero remaining violations
33
+ - No `.rubocop.yml` exclusions needed for large files since Metrics cops are off
34
+ - Plan 02 completed with no code changes required
@@ -0,0 +1,49 @@
1
+ # Lead Agent Memory -- SourceMonitor
2
+
3
+ ## Project Quick Facts
4
+ - Rails 8 engine, Ruby 3.4+, PostgreSQL-only
5
+ - 325 git-tracked Ruby files, 130 test files
6
+ - Coverage baseline: 2328 uncovered lines in config/coverage_baseline.json
7
+ - RuboCop: rubocop-rails-omakase, config at .rubocop.yml
8
+ - CI: .github/workflows/ci.yml (lint, security, test, release-verify, nightly profiling)
9
+ - Large files: FeedFetcher (627), Configuration (655), ImportSessionsController (792)
10
+
11
+ ## Phase 1 Findings
12
+ - 98 git-tracked .rb files missing frozen_string_literal (out of 325)
13
+ - Breakdown: app/(5), lib/(24), test/(43 non-dummy), test/dummy/(22), config/(1), db/migrate/(4)
14
+ - test/tmp/ is NOT git-tracked (generated by install_generator tests) -- exclude from changes
15
+ - test/lib/tmp/install_generator/config/initializers/source_monitor.rb IS tracked and already has pragma
16
+ - bin/rubocop wrapper forces --config .rubocop.yml
17
+ - Phase 1 criterion #3 (10% coverage shrink) not addressed by plans 01/02 -- noted for future
18
+
19
+ ## Phase 2 Planning Insights
20
+ - Coverage baseline.json has 2117 uncovered lines across 105 files (not 2328 -- that was line count)
21
+ - Top 7 files account for ~743 uncovered lines (35% of total)
22
+ - FeedFetcher: 245 uncovered, 12 existing tests, uses VCR+WebMock
23
+ - ItemCreator: 228 uncovered, 8 existing tests, needs mock entries for Atom/JSON branches
24
+ - Configuration: 94 uncovered, 5 existing tests, ~12 nested settings classes
25
+ - Dashboard::Queries: 66 uncovered, 7 existing tests, uses raw SQL + Cache
26
+ - BulkSourceScraper: 66 uncovered, 6 existing tests, ActiveJob test adapter
27
+ - Broadcaster: 48 uncovered, NO existing test file -- needs creation
28
+ - SourcesIndexMetrics: 34 uncovered, 3 existing tests
29
+ - All 5 Phase 2 plans are Wave 1 (no inter-plan dependencies, no file conflicts)
30
+ - Testing main files will indirectly cover supporting files (retry_policy, fetch_error, state, enqueuer, etc.)
31
+ - Broadcaster tests need Turbo::StreamsChannel stubs -- no full Action Cable required
32
+ - 50% coverage target requires ~1059 lines covered; direct plans target ~630, rest from indirect
33
+
34
+ ## Phase 4 Planning Insights
35
+ - 3 plans: conventions-audit (wave 1), item-creator-extraction (wave 1), final-verification (wave 2)
36
+ - SourcesController has dead `fetch`/`retry` methods (leftover from Phase 3 CRUD extraction)
37
+ - ImportSessionsController `new` and `create` are byte-for-byte identical
38
+ - 4 RuboCop violations in db/migrate/20260210204022_add_composite_index_to_log_entries.rb
39
+ - Duplicate test: test/controllers/concerns/ vs test/controllers/source_monitor/concerns/
40
+ - ItemCreator at 601 lines is the last file over 300 lines -- extract to entry_parser + content_extractor
41
+ - Coverage baseline still at 2117 uncovered (not regenerated since Phase 1) -- must regenerate
42
+ - 60% reduction target: at most 847 uncovered lines
43
+ - dashboard/queries.rb (356) and application_helper.rb (346) are near 300 but acceptable as view/query code
44
+ - `bin/update-coverage-baseline` requires `COVERAGE=1 bin/rails test` first
45
+
46
+ ## Bash/Shell Notes
47
+ - zsh on macOS -- `!` in shell conditionals causes `command not found` errors
48
+ - Use `case` statements instead of `if ! grep` patterns in zsh
49
+ - git ls-files piped to while loops works reliably for file enumeration
@@ -0,0 +1,212 @@
1
+ # Release: PR, CI, Merge, and Gem Build
2
+
3
+ Orchestrate a full release cycle for the source_monitor gem. This command handles changelog generation, version bumping, PR creation, CI monitoring, auto-merge on success, release tagging, and gem build with push instructions.
4
+
5
+ ## Inputs
6
+
7
+ - `$ARGUMENTS` -- Optional: version bump description or release notes summary. If empty, derive from commits since last tag.
8
+
9
+ ## Step 1: Git Hygiene
10
+
11
+ Run these checks. If ANY fail, STOP and report the issue to the user.
12
+
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.
18
+
19
+ Report a summary:
20
+ ```
21
+ Git Status:
22
+ Branch: main
23
+ Clean: yes
24
+ Synced with origin: yes
25
+ Unpushed commits: N
26
+ ```
27
+
28
+ ## Step 2: Version Check
29
+
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:
33
+ - "Current version is X.Y.Z which already has a tag. What should the new version be?"
34
+ - 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.
37
+
38
+ Store the release version for later steps. Do NOT commit yet -- that happens after the changelog is updated.
39
+
40
+ ## Step 3: Update CHANGELOG.md
41
+
42
+ The changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format. The file is at `CHANGELOG.md` in the project root.
43
+
44
+ 1. **Gather commit history** since the last tag:
45
+ ```
46
+ git log vPREVIOUS..HEAD --oneline --no-merges
47
+ ```
48
+ 2. **Categorize commits** into Keep a Changelog sections based on commit prefixes and content:
49
+ - `feat:`, `add:` --> **Added**
50
+ - `fix:`, `bugfix:` --> **Fixed**
51
+ - `chore:`, `refactor:`, `perf:` --> **Changed**
52
+ - `docs:` --> **Documentation** (only include if substantive)
53
+ - `BREAKING:` or `!:` --> **Breaking Changes** (at the top)
54
+ - `remove:`, `deprecate:` --> **Removed**
55
+ - Skip merge commits, version bumps, and CI-only changes.
56
+ - If `$ARGUMENTS` was provided, use it to inform/supplement the categorization.
57
+
58
+ 3. **Draft the changelog entry** and present it to the user for review:
59
+ ```
60
+ ## [X.Y.Z] - YYYY-MM-DD
61
+
62
+ ### Added
63
+ - <items>
64
+
65
+ ### Fixed
66
+ - <items>
67
+
68
+ ### Changed
69
+ - <items>
70
+ ```
71
+ Each bullet should be a concise, user-facing description (not a raw commit message). Consolidate related commits into single bullets where it makes sense.
72
+
73
+ 4. **Ask the user to approve** the changelog entry. Offer to edit if they want changes.
74
+
75
+ 5. **Write the entry** into `CHANGELOG.md`:
76
+ - Replace the `## [Unreleased]` section contents with `- No unreleased changes yet.`
77
+ - Insert the new versioned entry immediately after the `## [Unreleased]` block and before the previous release entry.
78
+ - Preserve all existing entries below.
79
+
80
+ ## Step 4: Commit Version Bump + Changelog
81
+
82
+ Once both `lib/source_monitor/version.rb` (if changed) and `CHANGELOG.md` are updated:
83
+
84
+ 1. Stage the changed files:
85
+ ```
86
+ git add lib/source_monitor/version.rb CHANGELOG.md
87
+ ```
88
+ 2. Create a single commit:
89
+ ```
90
+ chore: bump version to X.Y.Z and update changelog
91
+ ```
92
+ If only the changelog changed (version was already bumped), use:
93
+ ```
94
+ chore: update changelog for vX.Y.Z release
95
+ ```
96
+
97
+ ## Step 5: Create Release Branch and PR
98
+
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`:
103
+ - Title: `Release vX.Y.Z`
104
+ - Body format:
105
+ ```
106
+ ## Release vX.Y.Z
107
+
108
+ <paste the CHANGELOG.md entry content here>
109
+
110
+ ### Release Checklist
111
+ - [x] Version bumped in `lib/source_monitor/version.rb`
112
+ - [x] CHANGELOG.md updated
113
+ - [ ] CI passes (lint, security, test, release_verification)
114
+
115
+ ---
116
+ Auto-generated release PR by `/release` command.
117
+ ```
118
+ - Base: `main`
119
+ 5. Report the PR URL to the user.
120
+
121
+ ## Step 6: Monitor CI Pipeline
122
+
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`.
124
+
125
+ 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.
128
+
129
+ ### If CI PASSES (all checks green):
130
+
131
+ Continue to Step 7.
132
+
133
+ ### If CI FAILS:
134
+
135
+ 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:
137
+ ```
138
+ gh run view <RUN_ID> --log-failed | tail -50
139
+ ```
140
+ 3. Present to the user:
141
+ ```
142
+ CI Failed on PR #N
143
+
144
+ Failed Jobs:
145
+ - <job_name>: <brief summary of failure>
146
+
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
154
+
155
+ ## Step 7: Auto-Merge PR
156
+
157
+ Once CI is green:
158
+
159
+ 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`
161
+ 3. Report: "PR #N merged successfully."
162
+
163
+ ## Step 8: Tag the Release
164
+
165
+ 1. Create an annotated tag:
166
+ ```
167
+ git tag -a vX.Y.Z -m "Release vX.Y.Z"
168
+ ```
169
+ 2. Push the tag: `git push origin vX.Y.Z`
170
+ 3. Create a GitHub release from the tag:
171
+ ```
172
+ gh release create vX.Y.Z --title "vX.Y.Z" --notes "<changelog entry from Step 3>"
173
+ ```
174
+ 4. Report the release URL.
175
+
176
+ ## Step 9: Build the Gem
177
+
178
+ 1. Clean any old gem files: remove any `source_monitor-*.gem` files in the project root.
179
+ 2. Build the gem: `gem build source_monitor.gemspec`
180
+ 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`
182
+
183
+ ## Step 10: Gem Push Instructions
184
+
185
+ Present the final instructions to the user:
186
+
187
+ ```
188
+ Release vX.Y.Z Complete!
189
+
190
+ Git tag: vX.Y.Z (pushed)
191
+ GitHub: <release URL>
192
+ Gem built: source_monitor-X.Y.Z.gem
193
+
194
+ To publish to RubyGems:
195
+
196
+ gem push source_monitor-X.Y.Z.gem
197
+
198
+ You'll be prompted for your RubyGems OTP code (check your authenticator app).
199
+ The gem has `rubygems_mfa_required` enabled, so the OTP is mandatory.
200
+
201
+ After pushing, verify at:
202
+ https://rubygems.org/gems/source_monitor/versions/X.Y.Z
203
+ ```
204
+
205
+ Do NOT run `gem push` automatically -- always let the user handle the OTP-protected push manually.
206
+
207
+ ## Error Recovery
208
+
209
+ - If any step fails unexpectedly, report what happened and where things stand.
210
+ - If a release branch already exists, ask the user whether to reuse or recreate it.
211
+ - If the tag already exists, skip tagging and inform the user.
212
+ - Always leave the user on the `main` branch in a clean state when possible.
@@ -61,7 +61,7 @@ bin/source_monitor install --yes
61
61
  gem "source_monitor", "~> 0.3.0" # in Gemfile
62
62
  bundle install
63
63
 
64
- # 2. Run the install generator
64
+ # 2. Run the install generator (mounts engine, creates initializer, configures recurring jobs)
65
65
  bin/rails generate source_monitor:install --mount-path=/source_monitor
66
66
 
67
67
  # 3. Copy engine migrations
@@ -70,7 +70,7 @@ bin/rails railties:install:migrations FROM=source_monitor
70
70
  # 4. Apply migrations
71
71
  bin/rails db:migrate
72
72
 
73
- # 5. Start background workers
73
+ # 5. Start background workers (recurring jobs configured automatically in config/recurring.yml)
74
74
  bin/rails solid_queue:start
75
75
 
76
76
  # 6. Verify
@@ -86,7 +86,7 @@ gem "source_monitor", github: "dchuk/source_monitor"
86
86
 
87
87
  ## What the Install Generator Does
88
88
 
89
- The generator (`SourceMonitor::Generators::InstallGenerator`) performs two actions:
89
+ The generator (`SourceMonitor::Generators::InstallGenerator`) performs three actions:
90
90
 
91
91
  1. **Mounts the engine** in `config/routes.rb`:
92
92
  ```ruby
@@ -97,6 +97,9 @@ The generator (`SourceMonitor::Generators::InstallGenerator`) performs two actio
97
97
  2. **Creates the initializer** at `config/initializers/source_monitor.rb`:
98
98
  Uses the template at `lib/generators/source_monitor/install/templates/source_monitor.rb.tt`. Skips if the file already exists.
99
99
 
100
+ 3. **Configures recurring jobs** in `config/recurring.yml`:
101
+ 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
+
100
103
  Re-running the generator is safe and idempotent.
101
104
 
102
105
  ## Post-Install Configuration
@@ -217,6 +220,7 @@ end
217
220
  - [ ] `bundle install` completed
218
221
  - [ ] Install generator ran (`bin/rails generate source_monitor:install`)
219
222
  - [ ] Engine migrations copied and applied
223
+ - [ ] Recurring jobs configured in `config/recurring.yml`
220
224
  - [ ] Solid Queue workers started
221
225
  - [ ] Authentication hooks configured in initializer
222
226
  - [ ] `bin/source_monitor verify` passes
@@ -39,6 +39,7 @@ bin/rails generate source_monitor:install --mount-path=/source_monitor
39
39
  Verify after running:
40
40
  - [ ] `config/routes.rb` contains `mount SourceMonitor::Engine, at: "/source_monitor"`
41
41
  - [ ] `config/initializers/source_monitor.rb` exists
42
+ - [ ] `config/recurring.yml` contains SourceMonitor recurring job entries
42
43
 
43
44
  ## Phase 4: Database Setup
44
45
 
@@ -75,6 +76,8 @@ end
75
76
 
76
77
  ## Phase 6: Configure Workers
77
78
 
79
+ The install generator automatically configures recurring jobs in `config/recurring.yml` (fetch scheduling, scrape scheduling, item cleanup, log cleanup). These run automatically with `bin/dev` or `bin/jobs`.
80
+
78
81
  Ensure `config/solid_queue.yml` (or equivalent) includes the SourceMonitor queues:
79
82
 
80
83
  ```yaml
@@ -159,15 +159,16 @@ SourceMonitor.queue_name(:fetch) # => "source_monitor_fetch"
159
159
 
160
160
  ### Recurring Jobs
161
161
 
162
- ScheduleFetchesJob is typically configured as a recurring job in `config/recurring.yml`:
163
-
164
- ```yaml
165
- production:
166
- schedule_fetches:
167
- class: SourceMonitor::ScheduleFetchesJob
168
- schedule: every 1 minute
169
- queue: source_monitor_fetch
170
- ```
162
+ The install generator (`bin/rails generate source_monitor:install`) automatically configures these recurring jobs in `config/recurring.yml`:
163
+
164
+ | Job | Schedule |
165
+ |-----|----------|
166
+ | `SourceMonitor::ScheduleFetchesJob` | every minute |
167
+ | `SourceMonitor::Scraping::Scheduler.run` | every 2 minutes |
168
+ | `SourceMonitor::ItemCleanupJob` | at 2am every day |
169
+ | `SourceMonitor::LogCleanupJob` | at 3am every day |
170
+
171
+ These run automatically with `bin/dev` or `bin/jobs`. If you need to customize, edit `config/recurring.yml` directly.
171
172
 
172
173
  ## Retry Policies
173
174
 
data/.gitignore CHANGED
@@ -15,3 +15,7 @@
15
15
  /app/assets/builds/*
16
16
  !/app/assets/builds/.keep
17
17
  !/app/assets/builds/source_monitor/
18
+ .vbw-planning/.cost-ledger.json
19
+ .vbw-planning/.notification-log.jsonl
20
+ .vbw-planning/.session-log.jsonl
21
+ .vbw-planning/.hook-errors.log
data/CHANGELOG.md CHANGED
@@ -15,6 +15,18 @@ All notable changes to this project are documented below. The format follows [Ke
15
15
 
16
16
  - No unreleased changes yet.
17
17
 
18
+ ## [0.3.3] - 2026-02-11
19
+
20
+ ### Fixed
21
+
22
+ - Added missing `recurring.yml` configuration to the install generator so host apps get Solid Queue recurring job config on install.
23
+ - Fixed YAML alias parsing in install generator so merging into existing `recurring.yml` files with `<<: *default` anchors works correctly.
24
+
25
+ ### Changed
26
+
27
+ - Updated `sm-host-setup` and `sm-job` skills to reflect latest conventions.
28
+ - Updated setup documentation with current installation steps.
29
+
18
30
  ## [0.3.2] - 2026-02-10
19
31
 
20
32
  ### Fixed
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- source_monitor (0.3.2)
4
+ source_monitor (0.3.3)
5
5
  cssbundling-rails (~> 1.4)
6
6
  faraday (~> 2.9)
7
7
  faraday-follow_redirects (~> 0.4)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.2
1
+ 0.3.3
data/docs/setup.md CHANGED
@@ -48,8 +48,8 @@ This ensures Bundler can load SourceMonitor so the commands below are available.
48
48
  3. **Start background workers:**
49
49
  ```bash
50
50
  bin/rails solid_queue:start
51
- bin/jobs --recurring_schedule_file=config/recurring.yml # optional recurring scheduler
52
51
  ```
52
+ Recurring jobs (fetch scheduling, scraping, cleanup) are automatically configured in `config/recurring.yml` by the install generator. They'll run automatically with `bin/dev` or `bin/jobs`.
53
53
 
54
54
  4. **Visit the dashboard** at the chosen mount path, create a source, and trigger “Fetch Now” to validate realtime updates and Solid Queue processing.
55
55
 
@@ -83,7 +83,7 @@ Prefer to script each step or plug SourceMonitor into an existing deployment che
83
83
  | --- | --- | --- |
84
84
  | 1 | `gem "source_monitor", github: "dchuk/source_monitor"` | Add the engine to your Gemfile (skip if already present) |
85
85
  | 2 | `bundle install` | Install Ruby dependencies |
86
- | 3 | `bin/rails generate source_monitor:install --mount-path=/source_monitor` | Mount the engine and create the initializer |
86
+ | 3 | `bin/rails generate source_monitor:install --mount-path=/source_monitor` | Mount the engine, create the initializer, and configure recurring jobs |
87
87
  | 4 | `bin/rails railties:install:migrations FROM=source_monitor` | Copy engine migrations (idempotent) |
88
88
  | 5 | `bin/rails db:migrate` | Apply schema updates, including Solid Queue tables |
89
89
  | 6 | `bin/rails solid_queue:start` | Ensure jobs process via Solid Queue |
@@ -95,11 +95,11 @@ Prefer to script each step or plug SourceMonitor into an existing deployment che
95
95
  ### Step-by-step Details
96
96
 
97
97
  1. **Add the gem** to the host `Gemfile` (GitHub edge or released version) and run `bundle install`. If your host manages node tooling, run `npm install` also.
98
- 2. **Install the engine** via `bin/rails generate source_monitor:install --mount-path=/source_monitor`. The generator mounts the engine, creates `config/initializers/source_monitor.rb`, and prints follow-up instructions. Re-running the generator is safe; it detects existing mounts/initializers.
98
+ 2. **Install the engine** via `bin/rails generate source_monitor:install --mount-path=/source_monitor`. The generator mounts the engine, creates `config/initializers/source_monitor.rb`, and configures recurring Solid Queue jobs in `config/recurring.yml`. Re-running the generator is safe; it detects existing mounts/initializers and skips entries that are already present.
99
99
  3. **Copy migrations** with `bin/rails railties:install:migrations FROM=source_monitor`. This brings in the SourceMonitor tables plus Solid Cable/Queue schema when needed. The command is idempotent—run it again after upgrading the gem.
100
100
  4. **Apply database changes** using `bin/rails db:migrate`. If your host already installed Solid Queue migrations manually, delete duplicate files before migrating.
101
101
  5. **Wire Action Cable** if necessary. SourceMonitor defaults to Solid Cable; confirm `ApplicationCable::Connection`/`Channel` exist and that `config/initializers/source_monitor.rb` uses the adapter you expect. To switch to Redis, set `config.realtime.adapter = :redis` and `config.realtime.redis_url`.
102
- 6. **Start workers** with `bin/rails solid_queue:start` (or your process manager). Add a recurring process via `bin/jobs --recurring_schedule_file=config/recurring.yml` when you need fetch/scrape schedules.
102
+ 6. **Start workers** with `bin/rails solid_queue:start` (or your process manager). The install generator automatically configures recurring jobs in `config/recurring.yml` for fetch scheduling, scraping, and cleanup. They'll run with `bin/dev` or `bin/jobs`.
103
103
  7. **Review the initializer** and tune queue names, HTTP timeouts, scraping adapters, retention limits, authentication hooks, and Mission Control integration. The [configuration reference](configuration.md) details every option.
104
104
  8. **Verify the install**: run `bin/source_monitor verify` to ensure Solid Queue workers and Action Cable are healthy, then visit the mount path to trigger a fetch manually. Enable telemetry if you want JSON logs recorded for support.
105
105
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "yaml"
3
4
  require "rails/generators"
4
5
  require "rails/generators/base"
5
6
 
@@ -32,7 +33,26 @@ module SourceMonitor
32
33
  template "source_monitor.rb.tt", initializer_path
33
34
  end
34
35
 
36
+ def configure_recurring_jobs
37
+ recurring_path = "config/recurring.yml"
38
+ destination = File.join(destination_root, recurring_path)
39
+
40
+ if recurring_file_has_source_monitor_entries?(destination)
41
+ say_status :skip, "#{recurring_path} (SourceMonitor entries already present)", :yellow
42
+ return
43
+ end
44
+
45
+ if File.exist?(destination)
46
+ merge_into_existing_recurring(destination, recurring_path)
47
+ else
48
+ create_recurring_file(destination, recurring_path)
49
+ end
50
+ end
51
+
35
52
  def print_next_steps
53
+ say_status :info,
54
+ "Recurring jobs configured in config/recurring.yml — they'll run automatically with bin/dev or bin/jobs.",
55
+ :green
36
56
  say_status :info,
37
57
  "Next steps: review docs/setup.md for the guided + manual install walkthrough and docs/troubleshooting.md for common fixes.",
38
58
  :green
@@ -40,6 +60,86 @@ module SourceMonitor
40
60
 
41
61
  private
42
62
 
63
+ RECURRING_ENTRIES = {
64
+ "source_monitor_schedule_fetches" => {
65
+ "class" => "SourceMonitor::ScheduleFetchesJob",
66
+ "args" => [ { "limit" => 100 } ],
67
+ "schedule" => "every minute"
68
+ },
69
+ "source_monitor_schedule_scrapes" => {
70
+ "command" => "SourceMonitor::Scraping::Scheduler.run(limit: 100)",
71
+ "schedule" => "every 2 minutes"
72
+ },
73
+ "source_monitor_item_cleanup" => {
74
+ "class" => "SourceMonitor::ItemCleanupJob",
75
+ "schedule" => "at 2am every day"
76
+ },
77
+ "source_monitor_log_cleanup" => {
78
+ "class" => "SourceMonitor::LogCleanupJob",
79
+ "args" => [ { "fetch_logs_older_than_days" => 90, "scrape_logs_older_than_days" => 60 } ],
80
+ "schedule" => "at 3am every day"
81
+ }
82
+ }.freeze
83
+
84
+ def recurring_file_has_source_monitor_entries?(path)
85
+ return false unless File.exist?(path)
86
+
87
+ content = File.read(path)
88
+ content.include?("source_monitor_schedule_fetches")
89
+ end
90
+
91
+ def merge_into_existing_recurring(destination, recurring_path)
92
+ parsed = YAML.safe_load(File.read(destination), aliases: true) || {}
93
+ default_key = parsed.key?("default") ? "default" : nil
94
+
95
+ if default_key
96
+ parsed["default"] = (parsed["default"] || {}).merge(RECURRING_ENTRIES)
97
+ else
98
+ parsed.merge!(RECURRING_ENTRIES)
99
+ end
100
+
101
+ write_recurring_yaml(destination, parsed, has_environments: parsed.key?("development"))
102
+ say_status :append, recurring_path, :green
103
+ end
104
+
105
+ def create_recurring_file(destination, recurring_path)
106
+ FileUtils.mkdir_p(File.dirname(destination))
107
+ yaml_content = build_fresh_recurring_yaml
108
+ File.write(destination, yaml_content)
109
+ say_status :create, recurring_path, :green
110
+ end
111
+
112
+ def build_fresh_recurring_yaml
113
+ entries_yaml = format_entries_yaml(RECURRING_ENTRIES)
114
+
115
+ "default: &default\n#{entries_yaml}\n" \
116
+ "development:\n <<: *default\n\n" \
117
+ "test:\n <<: *default\n\n" \
118
+ "production:\n <<: *default\n"
119
+ end
120
+
121
+ def write_recurring_yaml(destination, parsed, has_environments: false)
122
+ if has_environments
123
+ default_entries = parsed["default"] || {}
124
+ entries_yaml = format_entries_yaml(default_entries)
125
+ envs = %w[development test production].select { |e| parsed.key?(e) }
126
+ env_sections = envs.map { |e| "#{e}:\n <<: *default" }.join("\n\n")
127
+
128
+ content = "default: &default\n#{entries_yaml}"
129
+ content += "\n#{env_sections}\n" unless envs.empty?
130
+ File.write(destination, content)
131
+ else
132
+ File.write(destination, YAML.dump(parsed))
133
+ end
134
+ end
135
+
136
+ def format_entries_yaml(entries)
137
+ entries.map { |key, value|
138
+ entry = YAML.dump({ key => value }).delete_prefix("---\n")
139
+ entry.gsub(/^/, " ")
140
+ }.join("\n")
141
+ end
142
+
43
143
  def engine_already_mounted?(mount_path)
44
144
  routes_path = File.join(destination_root, "config/routes.rb")
45
145
  return false unless File.exist?(routes_path)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SourceMonitor
4
- VERSION = "0.3.2"
4
+ VERSION = "0.3.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: source_monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - dchuk
@@ -238,6 +238,8 @@ executables: []
238
238
  extensions: []
239
239
  extra_rdoc_files: []
240
240
  files:
241
+ - ".claude/agent-memory/vbw-vbw-dev/MEMORY.md"
242
+ - ".claude/agent-memory/vbw-vbw-lead/MEMORY.md"
241
243
  - ".claude/agents/rails-concern.md"
242
244
  - ".claude/agents/rails-controller.md"
243
245
  - ".claude/agents/rails-hotwire.md"
@@ -256,6 +258,7 @@ files:
256
258
  - ".claude/agents/rails-tdd.md"
257
259
  - ".claude/agents/rails-test.md"
258
260
  - ".claude/agents/rails-view-component.md"
261
+ - ".claude/commands/release.md"
259
262
  - ".claude/hooks/block-secrets.sh"
260
263
  - ".claude/settings.json"
261
264
  - ".claude/skills/action-cable-patterns/SKILL.md"
@@ -336,8 +339,6 @@ files:
336
339
  - ".gitignore"
337
340
  - ".rubocop.yml"
338
341
  - ".ruby-version"
339
- - ".vbw-planning/.notification-log.jsonl"
340
- - ".vbw-planning/.session-log.jsonl"
341
342
  - ".vbw-planning/PROJECT.md"
342
343
  - ".vbw-planning/REQUIREMENTS.md"
343
344
  - ".vbw-planning/SHIPPED.md"