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 +4 -4
- data/.claude/agent-memory/vbw-vbw-dev/MEMORY.md +34 -0
- data/.claude/agent-memory/vbw-vbw-lead/MEMORY.md +49 -0
- data/.claude/commands/release.md +212 -0
- data/.claude/skills/sm-host-setup/SKILL.md +7 -3
- data/.claude/skills/sm-host-setup/reference/setup-checklist.md +3 -0
- data/.claude/skills/sm-job/SKILL.md +10 -9
- data/.gitignore +4 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/VERSION +1 -1
- data/docs/setup.md +4 -4
- data/lib/generators/source_monitor/install/install_generator.rb +100 -0
- data/lib/source_monitor/version.rb +1 -1
- metadata +4 -3
- data/.vbw-planning/.notification-log.jsonl +0 -294
- data/.vbw-planning/.session-log.jsonl +0 -1376
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae2bf8e389d7c36d4f7061a13f7f4bb213c8365408f9d0c1fb818ea25b385ff7
|
|
4
|
+
data.tar.gz: a3b8b537ea6bb06251edbc53a203d9c9e9c20a1d555679a722f00ff62eef9967
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
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
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
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
|
|
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
|
|
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).
|
|
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)
|
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.
|
|
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"
|