source_monitor 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b65480547bf48a4cabf2d1c98dbd6c965a6b7342c3da362b3987b1bed3e59a5d
4
- data.tar.gz: 775abb18c5c94b5cf11e78e01c296a618c7ef884cb328f4f5f886c2d144c2f75
3
+ metadata.gz: 135499910675f0d424b88ec25e1503f3068fdbd78ffa553942438efd886b72bd
4
+ data.tar.gz: a6b03ef217569a206d7521f2d8b42f3c300ab6ddf41e144cabaf24535f92b8dc
5
5
  SHA512:
6
- metadata.gz: f75e313708962d167d7b362ed4f8af42be433e28d5a9e1aa59f290d82ed12103800abaf2f904098211c351f7c3b9265af05363d2364f477e22af3e7de2bc9755
7
- data.tar.gz: ab0e7911a85c744f632d2fad3dbeb671ddb55b3e1f4cc0ed3a0dbc859e2dad21ccacdec8aff7ee45445cf84f3cfb3ddaf2ba803a7e533cacdc14b8cb3ee61ab6
6
+ metadata.gz: b4e0ebe10a0211760f42d6a131cbf61eb09bf3f268006fbefe51636137c10ba0afc0413e2c39a46a8d3ff39037db07ba680a6f059599d7751b56a79d5deef1a3
7
+ data.tar.gz: 7bdfa9ca0995bb91757be78271ca171a14ec83cc54aef8d13b083446e3c23a96d4568718b40a944c83c802173124823609a36c405a68b8a0c81bb138891aa65b
@@ -15,8 +15,10 @@ These are real issues encountered in previous releases. Each step below accounts
15
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
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
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.
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. **This applies to ALL changes in the PR diff vs main, including unpushed commits made before the release started.** If the release includes source code changes (bug fixes, features), every changed source line must be covered.
19
+ 7. **Local main divergence after merge**: After the PR merges, `gh pr merge --merge --delete-branch` will attempt to fast-forward local main. This usually succeeds automatically. If it doesn't (divergent history), you must `git reset --hard origin/main` to sync -- this requires user approval since the sandbox blocks it.
20
+ 8. **Run local checks BEFORE pushing**: Always run `bin/rubocop` and `PARALLEL_WORKERS=1 bin/rails test` locally before the first push to the release branch. Each CI roundtrip (fail → fix → amend → force-push → re-run) costs ~5 minutes. In v0.7.0, skipping local checks caused two wasted CI cycles: first for uncovered diff lines, then for a RuboCop violation in the fix.
21
+ 9. **Zsh glob nomatch**: Commands like `rm -f *.gem` fail in zsh when no files match. Always use `rm -f *.gem 2>/dev/null || true` or check existence first with `ls`.
20
22
 
21
23
  ## Step 1: Git Hygiene
22
24
 
@@ -112,7 +114,25 @@ The changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) f
112
114
  2. Verify the output shows the new version: `Using source_monitor X.Y.Z (was X.Y.Z-1)`.
113
115
  3. If `bundle install` fails, resolve the issue before proceeding.
114
116
 
115
- ## Step 5: Create Release Branch with Single Squashed Commit
117
+ ## Step 5: Local Pre-flight Checks
118
+
119
+ **CRITICAL**: Run these checks BEFORE creating the release branch and pushing. Each CI failure → fix → amend → force-push cycle wastes ~5 minutes. In v0.7.0, skipping this step caused two wasted CI roundtrips.
120
+
121
+ 1. **RuboCop**: Run `bin/rubocop` and fix any violations. Auto-fix with `bin/rubocop -a` if needed. This catches lint issues (like `SpaceInsideArrayLiteralBrackets`) that would fail the CI lint job.
122
+
123
+ 2. **Tests**: Run `PARALLEL_WORKERS=1 bin/rails test` and ensure all tests pass.
124
+
125
+ 3. **Diff coverage pre-check**: If the release includes source code changes beyond version/changelog/lockfile (check with `git diff --name-only origin/main`), review those files for uncovered branches. The CI diff coverage gate will reject any changed source lines without test coverage. Common blind spots:
126
+ - Fallback/else branches in new methods
127
+ - Error handling paths
128
+ - Guard clauses
129
+ If you find uncovered source lines, write tests for them NOW before creating the release commit — it's far cheaper than a CI roundtrip.
130
+
131
+ 4. **Brakeman**: Run `bin/brakeman --no-pager` and ensure zero warnings.
132
+
133
+ Only proceed to Step 6 when all local checks pass.
134
+
135
+ ## Step 6: Create Release Branch with Single Squashed Commit
116
136
 
117
137
  **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
138
 
@@ -124,13 +144,13 @@ The changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) f
124
144
  Also stage any other files that were changed (updated skills, docs, etc.).
125
145
  3. Create a single commit:
126
146
  ```
127
- chore: release vX.Y.Z
147
+ chore(release): release vX.Y.Z
128
148
  ```
129
149
  4. Push the branch: `git push -u origin release/vX.Y.Z`
130
150
  - 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
151
  5. If the push fails for other reasons, diagnose and fix before proceeding.
132
152
 
133
- ## Step 6: Create PR
153
+ ## Step 7: Create PR
134
154
 
135
155
  1. Create the PR using `gh pr create`:
136
156
  - Title: `Release vX.Y.Z`
@@ -152,7 +172,7 @@ The changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) f
152
172
  - Base: `main`
153
173
  2. Report the PR URL to the user.
154
174
 
155
- ## Step 7: Monitor CI Pipeline
175
+ ## Step 8: Monitor CI Pipeline
156
176
 
157
177
  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).
158
178
 
@@ -163,7 +183,7 @@ Poll the CI status using repeated `gh pr checks <PR_NUMBER>` calls. The CI has 4
163
183
 
164
184
  ### If CI PASSES (all checks green):
165
185
 
166
- Continue to Step 8.
186
+ Continue to Step 9. If Step 5 (local pre-flight) was done properly, CI should pass on the first attempt.
167
187
 
168
188
  ### If CI FAILS:
169
189
 
@@ -173,32 +193,32 @@ Continue to Step 8.
173
193
  gh run view <RUN_ID> --log-failed | tail -80
174
194
  ```
175
195
  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.
196
+ 4. **Common failure: Gemfile.lock frozen** -- If `bundle install` fails in CI with "frozen mode", you forgot to run `bundle install` locally (Step 4). Amend the commit with the updated lockfile.
197
+ 5. **Common failure: RuboCop lint** -- If the `lint` job fails, a RuboCop violation slipped through. This should have been caught in Step 5.
198
+ 6. **IMPORTANT: When fixing CI failures, run ALL local checks again before re-pushing.** Don't just fix the one failure run `bin/rubocop` AND `PARALLEL_WORKERS=1 bin/rails test` to catch cascading issues. In v0.7.0, fixing a diff coverage failure introduced a RuboCop violation, requiring a third CI cycle.
199
+ 7. Present failure details to the user and ask what to do:
200
+ - "Fix the issues and re-push" -- Fix issues, run ALL local checks (rubocop + tests), 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
201
  - "Close the PR and abort" -- Close the PR, delete the branch, switch back to main.
180
202
  - "Investigate manually" -- Stop and let the user handle it.
181
203
 
182
204
  **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.
183
205
 
184
- ## Step 8: Auto-Merge PR
206
+ ## Step 9: Auto-Merge PR
185
207
 
186
208
  Once CI is green:
187
209
 
188
210
  1. Merge the PR: `gh pr merge <PR_NUMBER> --merge --delete-branch`
211
+ - The `--delete-branch` flag also fetches and fast-forwards local main in most cases.
189
212
 
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.
213
+ 2. **Sync local main with remote**:
193
214
  - 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.
215
+ - The `gh pr merge` command usually auto-syncs local main via fast-forward. Verify with `git log --oneline -3` that local matches remote (`git rev-parse HEAD` == `git rev-parse origin/main`).
216
+ - If local is behind or diverged, try `git pull origin main`.
217
+ - If pull fails with conflicts or divergence (rare): ask the user to run `git reset --hard origin/main` (the sandbox blocks this command). Explain this is safe because the PR is merged and all changes are on origin/main.
198
218
 
199
219
  3. Report: "PR #N merged successfully."
200
220
 
201
- ## Step 9: Tag the Release
221
+ ## Step 10: Tag the Release
202
222
 
203
223
  1. Verify you're on main and synced with origin.
204
224
  2. Create an annotated tag:
@@ -212,14 +232,17 @@ Once CI is green:
212
232
  ```
213
233
  5. Report the release URL.
214
234
 
215
- ## Step 10: Build the Gem
235
+ ## Step 11: Build the Gem
216
236
 
217
- 1. Clean any old gem files: `ls source_monitor-*.gem` and remove them if found (don't error if none exist).
237
+ 1. Clean any old gem files. **Note**: zsh fails on `rm -f *.gem` when no files match due to `nomatch`. Use:
238
+ ```
239
+ find . -maxdepth 1 -name 'source_monitor-*.gem' -delete
240
+ ```
218
241
  2. Build the gem: `gem build source_monitor.gemspec`
219
242
  3. Verify the gem was built: check for `source_monitor-X.Y.Z.gem` in the project root.
220
243
  4. Show the file size: `ls -la source_monitor-X.Y.Z.gem`
221
244
 
222
- ## Step 11: Gem Push Instructions
245
+ ## Step 12: Gem Push Instructions
223
246
 
224
247
  Present the final instructions to the user:
225
248
 
@@ -5,6 +5,7 @@
5
5
  ### Phases
6
6
 
7
7
  1. [x] **AIA Certificate Resolution** -- Fix SSL failures for feeds with missing intermediate certificates by implementing AIA (Authority Information Access) resolution
8
+ 2. [x] **Test Performance** -- Reduce test suite runtime from ~133s to ~50s by splitting monolithic test classes, enabling parallelism, reducing log IO, and adopting before_all
8
9
 
9
10
  ### Phase Details
10
11
 
@@ -25,8 +26,28 @@
25
26
  - [ ] All existing tests pass (1003+), new tests cover AIA paths
26
27
  - [ ] RuboCop zero offenses, Brakeman zero warnings
27
28
 
29
+ #### Phase 2: Test Performance
30
+
31
+ **Goal:** Reduce test suite wall-clock time from ~133s to ~50s through structural optimizations. The 3-agent investigation identified that FeedFetcherTest (71 tests, 84.8s, 64% of total) is a monolithic class that cannot be parallelized, integration tests add 31s, and 95MB of debug logging adds 5-15s.
32
+
33
+ **Requirements:**
34
+ - REQ-PERF-01: Split FeedFetcherTest into 5+ smaller classes by concern (success paths, error handling, adaptive interval, dirty-check, content fingerprint, utilities)
35
+ - REQ-PERF-02: Set test log level to :warn in test/dummy/config/environments/test.rb (eliminates 95MB log IO)
36
+ - REQ-PERF-03: Tag integration tests (host_install_flow, release_packaging) so they can be excluded during dev with --exclude-pattern
37
+ - REQ-PERF-04: Switch default parallelism from forks to threads (avoids PG segfault, enables splitting benefit)
38
+ - REQ-PERF-05: Adopt before_all/setup_once in top DB-heavy test files (dashboard/queries_test.rb, etc.)
39
+
40
+ **Success Criteria:**
41
+ - [ ] FeedFetcherTest split into 5+ files, each independently runnable
42
+ - [ ] All 1031+ tests pass with PARALLEL_WORKERS=1 and default workers
43
+ - [ ] Test suite completes in <70s locally (down from 133s)
44
+ - [ ] `bin/rails test --exclude-pattern="**/integration/**"` runs <50s
45
+ - [ ] RuboCop zero offenses, Brakeman zero warnings
46
+ - [ ] No test isolation regressions (parallel runs still green)
47
+
28
48
  ### Progress
29
49
 
30
50
  | Phase | Status | Plans | Completed |
31
51
  |-------|--------|-------|-----------|
32
- | 1. AIA Certificate Resolution | Planned | 3 | 0 |
52
+ | 1. AIA Certificate Resolution | Complete | 3 | 3 |
53
+ | 2. Test Performance | Complete | 4 | 4 |
@@ -3,7 +3,7 @@
3
3
  ## Current Position
4
4
 
5
5
  - **Milestone:** aia-ssl-fix
6
- - **Phase:** 1 -- AIA Certificate Resolution
6
+ - **Phase:** 2 -- Test Performance
7
7
  - **Status:** Complete
8
8
  - **Progress:** 100%
9
9
 
@@ -0,0 +1,75 @@
1
+ ## Phase 02 Context
2
+
3
+ ### Goal
4
+ Not available
5
+
6
+ ### Codebase Map Available
7
+ Codebase mapping exists in `.vbw-planning/codebase/`. Key files:
8
+ - `ARCHITECTURE.md`
9
+ - `CONCERNS.md`
10
+ - `PATTERNS.md`
11
+ - `DEPENDENCIES.md`
12
+ - `STRUCTURE.md`
13
+ - `CONVENTIONS.md`
14
+ - `TESTING.md`
15
+ - `STACK.md`
16
+
17
+ Read CONVENTIONS.md, PATTERNS.md, STRUCTURE.md, and DEPENDENCIES.md first to bootstrap codebase understanding.
18
+
19
+ ### Research Findings
20
+ # Phase 2: Test Performance - Research
21
+
22
+ ## Investigation Method
23
+ 3-agent competing hypothesis team (H1: DB/parallelism, H2: HTTP/VCR, H3: test helpers)
24
+
25
+ ## Findings
26
+
27
+ ### Time Budget (133s total, 1031 tests)
28
+ | Component | Time | % | Root Cause |
29
+ |-----------|------|---|------------|
30
+ | FeedFetcherTest (71 tests) | 84.8s | 64% | Monolithic class, CPU-bound (Feedjira parse, SHA256, content extraction) |
31
+ | Integration tests (9 tests) | 30.8s | 23% | Subprocess spawning (bundle exec rails g) |
32
+ | System tests (22 tests) | ~5s | 4% | Selenium/Chrome |
33
+ | Debug log IO | 5-15s | 8% | 95MB :debug output to disk |
34
+ | Everything else (~930 tests) | ~15s | 11% | Fast |
35
+
36
+ ### Why Parallelism Doesn't Help Today
37
+ - Minitest distributes by CLASS, not by test
38
+ - FeedFetcherTest = 1 class with 71 tests = 1 worker gets all 84.8s
39
+ - With 8 workers: max(84.8, 25.6, ~3) ≈ 85s + overhead
40
+
41
+ ### DB Is NOT the Bottleneck
42
+ - SQL: 3.639s of 69.4s (5.25%) via EventProf
43
+ - schema.rb used (fast load)
44
+ - Transactional tests enabled (proper rollback)
45
+
46
+ ### HTTP Mocking Is NOT the Bottleneck
47
+ - Only 4 VCR cassettes (456KB total), 83 WebMock stubs across 9 files
48
+ - WebMock configured once globally, auto-resets
49
+ - Only finding: default_cert_store recreated per connection (~0.5s total)
50
+
51
+ ### Test Helpers Are Cheap
52
+ - reset_configuration!: microseconds per call (pure Ruby)
53
+ - create_source!: 1 INSERT per call, 158 calls total
54
+ - with_inline_jobs: 4 calls total, negligible
55
+
56
+ ### Profiling Data (tmp/test_prof/)
57
+ - TagProf: lib tests 38.0s (55%), integration 28.5s (41%), controllers 0.97s, jobs 0.53s, models 0.47s
58
+ - EventProf: FeedFetcherTest 0.888s SQL in 34.3s total (2.6% SQL)
59
+
60
+ ## Relevant Patterns
61
+ - TestProf before_all available but used in only 1 of 54 eligible files
62
+ - Thread-based parallelism already proven in coverage mode
63
+ - PG fork segfault only on single-file runs, NOT full suite
64
+
65
+ ## Risks
66
+ - Thread safety: reset_configuration! may need thread-local config or mutex
67
+ - Test isolation: splitting FeedFetcherTest must preserve test independence
68
+ - before_all: only safe for tests that don't mutate shared data
69
+
70
+ ## Recommendations (by impact)
71
+ 1. Split FeedFetcherTest into 5-6 classes → enables parallelism, -50s
72
+ 2. Set config.log_level = :warn → -5-15s
73
+ 3. Tag/skip integration tests in dev → -30s dev experience
74
+ 4. Switch to thread-based parallelism → enables splitting benefit
75
+ 5. Adopt before_all in DB-heavy test files → -3-5s
@@ -0,0 +1,89 @@
1
+ ## Phase 02 Context (Compiled)
2
+
3
+ ### Goal
4
+ Not available
5
+
6
+ ### Success Criteria
7
+ Not available
8
+
9
+ ### Requirements (Not available)
10
+ No matching requirements found
11
+
12
+
13
+ ### Active Decisions
14
+
15
+ | Decision | Date | Context |
16
+ |----------|------|---------|
17
+ | Single-phase milestone for AIA fix | 2026-02-17 | Complete plan already validated; no scoping needed |
18
+ | 3 plans with wave parallelism | 2026-02-17 | Plans 01+02 (wave 1, disjoint files), Plan 03 (wave 2, integration) |
19
+
20
+ ### Codebase Map Available
21
+ Codebase mapping exists in `.vbw-planning/codebase/`. Key files:
22
+ - `ARCHITECTURE.md`
23
+ - `CONCERNS.md`
24
+ - `PATTERNS.md`
25
+ - `DEPENDENCIES.md`
26
+ - `STRUCTURE.md`
27
+ - `CONVENTIONS.md`
28
+ - `TESTING.md`
29
+ - `STACK.md`
30
+
31
+ Read ARCHITECTURE.md, CONCERNS.md, and STRUCTURE.md first to bootstrap codebase understanding.
32
+
33
+ ### Research Findings
34
+ # Phase 2: Test Performance - Research
35
+
36
+ ## Investigation Method
37
+ 3-agent competing hypothesis team (H1: DB/parallelism, H2: HTTP/VCR, H3: test helpers)
38
+
39
+ ## Findings
40
+
41
+ ### Time Budget (133s total, 1031 tests)
42
+ | Component | Time | % | Root Cause |
43
+ |-----------|------|---|------------|
44
+ | FeedFetcherTest (71 tests) | 84.8s | 64% | Monolithic class, CPU-bound (Feedjira parse, SHA256, content extraction) |
45
+ | Integration tests (9 tests) | 30.8s | 23% | Subprocess spawning (bundle exec rails g) |
46
+ | System tests (22 tests) | ~5s | 4% | Selenium/Chrome |
47
+ | Debug log IO | 5-15s | 8% | 95MB :debug output to disk |
48
+ | Everything else (~930 tests) | ~15s | 11% | Fast |
49
+
50
+ ### Why Parallelism Doesn't Help Today
51
+ - Minitest distributes by CLASS, not by test
52
+ - FeedFetcherTest = 1 class with 71 tests = 1 worker gets all 84.8s
53
+ - With 8 workers: max(84.8, 25.6, ~3) ≈ 85s + overhead
54
+
55
+ ### DB Is NOT the Bottleneck
56
+ - SQL: 3.639s of 69.4s (5.25%) via EventProf
57
+ - schema.rb used (fast load)
58
+ - Transactional tests enabled (proper rollback)
59
+
60
+ ### HTTP Mocking Is NOT the Bottleneck
61
+ - Only 4 VCR cassettes (456KB total), 83 WebMock stubs across 9 files
62
+ - WebMock configured once globally, auto-resets
63
+ - Only finding: default_cert_store recreated per connection (~0.5s total)
64
+
65
+ ### Test Helpers Are Cheap
66
+ - reset_configuration!: microseconds per call (pure Ruby)
67
+ - create_source!: 1 INSERT per call, 158 calls total
68
+ - with_inline_jobs: 4 calls total, negligible
69
+
70
+ ### Profiling Data (tmp/test_prof/)
71
+ - TagProf: lib tests 38.0s (55%), integration 28.5s (41%), controllers 0.97s, jobs 0.53s, models 0.47s
72
+ - EventProf: FeedFetcherTest 0.888s SQL in 34.3s total (2.6% SQL)
73
+
74
+ ## Relevant Patterns
75
+ - TestProf before_all available but used in only 1 of 54 eligible files
76
+ - Thread-based parallelism already proven in coverage mode
77
+ - PG fork segfault only on single-file runs, NOT full suite
78
+
79
+ ## Risks
80
+ - Thread safety: reset_configuration! may need thread-local config or mutex
81
+ - Test isolation: splitting FeedFetcherTest must preserve test independence
82
+ - before_all: only safe for tests that don't mutate shared data
83
+
84
+ ## Recommendations (by impact)
85
+ 1. Split FeedFetcherTest into 5-6 classes → enables parallelism, -50s
86
+ 2. Set config.log_level = :warn → -5-15s
87
+ 3. Tag/skip integration tests in dev → -30s dev experience
88
+ 4. Switch to thread-based parallelism → enables splitting benefit
89
+ 5. Adopt before_all in DB-heavy test files → -3-5s
@@ -0,0 +1,23 @@
1
+ ## Phase 02 Verification Context
2
+
3
+ ### Goal
4
+ Not available
5
+
6
+ ### Success Criteria
7
+ Not available
8
+
9
+ ### Requirements to Verify
10
+ No matching requirements found
11
+
12
+ ### Codebase Map Available
13
+ Codebase mapping exists in `.vbw-planning/codebase/`. Key files:
14
+ - `ARCHITECTURE.md`
15
+ - `CONCERNS.md`
16
+ - `PATTERNS.md`
17
+ - `DEPENDENCIES.md`
18
+ - `STRUCTURE.md`
19
+ - `CONVENTIONS.md`
20
+ - `TESTING.md`
21
+ - `STACK.md`
22
+
23
+ Read TESTING.md, CONCERNS.md, and ARCHITECTURE.md first to bootstrap codebase understanding.
@@ -0,0 +1,56 @@
1
+ # Phase 2: Test Performance - Research
2
+
3
+ ## Investigation Method
4
+ 3-agent competing hypothesis team (H1: DB/parallelism, H2: HTTP/VCR, H3: test helpers)
5
+
6
+ ## Findings
7
+
8
+ ### Time Budget (133s total, 1031 tests)
9
+ | Component | Time | % | Root Cause |
10
+ |-----------|------|---|------------|
11
+ | FeedFetcherTest (71 tests) | 84.8s | 64% | Monolithic class, CPU-bound (Feedjira parse, SHA256, content extraction) |
12
+ | Integration tests (9 tests) | 30.8s | 23% | Subprocess spawning (bundle exec rails g) |
13
+ | System tests (22 tests) | ~5s | 4% | Selenium/Chrome |
14
+ | Debug log IO | 5-15s | 8% | 95MB :debug output to disk |
15
+ | Everything else (~930 tests) | ~15s | 11% | Fast |
16
+
17
+ ### Why Parallelism Doesn't Help Today
18
+ - Minitest distributes by CLASS, not by test
19
+ - FeedFetcherTest = 1 class with 71 tests = 1 worker gets all 84.8s
20
+ - With 8 workers: max(84.8, 25.6, ~3) ≈ 85s + overhead
21
+
22
+ ### DB Is NOT the Bottleneck
23
+ - SQL: 3.639s of 69.4s (5.25%) via EventProf
24
+ - schema.rb used (fast load)
25
+ - Transactional tests enabled (proper rollback)
26
+
27
+ ### HTTP Mocking Is NOT the Bottleneck
28
+ - Only 4 VCR cassettes (456KB total), 83 WebMock stubs across 9 files
29
+ - WebMock configured once globally, auto-resets
30
+ - Only finding: default_cert_store recreated per connection (~0.5s total)
31
+
32
+ ### Test Helpers Are Cheap
33
+ - reset_configuration!: microseconds per call (pure Ruby)
34
+ - create_source!: 1 INSERT per call, 158 calls total
35
+ - with_inline_jobs: 4 calls total, negligible
36
+
37
+ ### Profiling Data (tmp/test_prof/)
38
+ - TagProf: lib tests 38.0s (55%), integration 28.5s (41%), controllers 0.97s, jobs 0.53s, models 0.47s
39
+ - EventProf: FeedFetcherTest 0.888s SQL in 34.3s total (2.6% SQL)
40
+
41
+ ## Relevant Patterns
42
+ - TestProf before_all available but used in only 1 of 54 eligible files
43
+ - Thread-based parallelism already proven in coverage mode
44
+ - PG fork segfault only on single-file runs, NOT full suite
45
+
46
+ ## Risks
47
+ - Thread safety: reset_configuration! may need thread-local config or mutex
48
+ - Test isolation: splitting FeedFetcherTest must preserve test independence
49
+ - before_all: only safe for tests that don't mutate shared data
50
+
51
+ ## Recommendations (by impact)
52
+ 1. Split FeedFetcherTest into 5-6 classes → enables parallelism, -50s
53
+ 2. Set config.log_level = :warn → -5-15s
54
+ 3. Tag/skip integration tests in dev → -30s dev experience
55
+ 4. Switch to thread-based parallelism → enables splitting benefit
56
+ 5. Adopt before_all in DB-heavy test files → -3-5s
@@ -0,0 +1,51 @@
1
+ # Phase 02 Verification: Test Performance
2
+
3
+ **Verdict: PASS**
4
+ **Date:** 2026-02-18
5
+ **Tier:** Deep
6
+
7
+ ## Requirements Verification
8
+
9
+ | Requirement | Status | Evidence |
10
+ |-------------|--------|----------|
11
+ | REQ-PERF-01: FeedFetcherTest split into 5+ classes | PASS | 6 files created in test/lib/source_monitor/fetching/ (success, error_handling, adaptive_interval, retry_circuit, entry_processing, utilities) |
12
+ | REQ-PERF-02: Test log level set to :warn | PASS | config.log_level = :warn in test/dummy/config/environments/test.rb |
13
+ | REQ-PERF-03: Integration tests excludable | PASS | lib/tasks/test_fast.rake provides test:fast task (1022 tests vs 1033 full) |
14
+ | REQ-PERF-04: Default parallelism switched to threads | PASS | parallelize(workers: worker_count, with: :threads) in test_helper.rb |
15
+ | REQ-PERF-05: DB-heavy files use setup_once/before_all | PASS | 5 files now use setup_once (up from 1) |
16
+
17
+ ## Checks Performed
18
+
19
+ ### Structural Checks
20
+ - [x] 6 split test files exist in test/lib/source_monitor/fetching/
21
+ - [x] Original feed_fetcher_test.rb deleted
22
+ - [x] Shared helper module exists (feed_fetcher_test_helper.rb)
23
+ - [x] Each split file includes FeedFetcherTestHelper
24
+ - [x] Test count preserved: 71 tests across 6 files (5+13+6+6+8+33)
25
+ - [x] config.log_level = :warn present in test.rb
26
+ - [x] lib/tasks/test_fast.rake exists and is valid Ruby
27
+ - [x] parallelize uses with: :threads for all modes
28
+ - [x] setup_once used in 5 files (sources_index_metrics, source_activity_rates, source_fetch_interval_distribution, upcoming_fetch_schedule, query_test)
29
+
30
+ ### Runtime Checks
31
+ - [x] Full test suite: 1033 tests, 0 failures, 0 errors
32
+ - [x] Single-file runs work without PARALLEL_WORKERS=1 (PG segfault eliminated)
33
+ - [x] Each split file passes individually with PARALLEL_WORKERS=1
34
+ - [x] test:fast runs 1022 tests (excludes 11 integration/system tests)
35
+ - [x] Two consecutive full suite runs: 0 flaky failures
36
+
37
+ ### Quality Checks
38
+ - [x] RuboCop: 0 offenses across all modified files
39
+ - [x] Brakeman: 0 warnings
40
+ - [x] No test isolation regressions
41
+
42
+ ### Commits
43
+ - 912665f: perf(02-03): adopt setup_once/before_all in DB-heavy test files
44
+ - a951c95: feat(02-01): split FeedFetcherTest into 6 concern-based test classes
45
+ - edbfe23: perf(02-02): reduce test log IO and add test:fast rake task
46
+ - eceb06d: perf(test): switch default parallelism from forks to threads
47
+
48
+ ## Notes
49
+
50
+ - TestProf emits cosmetic warning "before_all is not implemented for parallalization with threads" — does not affect correctness. before_all/setup_once works correctly because single-file runs stay below parallelization threshold, and full suite distributes by class.
51
+ - PLAN-02 deviated from plan: --exclude-pattern is not a Minitest feature, so Dir glob approach was used instead. Functionally equivalent.
@@ -0,0 +1,37 @@
1
+ ---
2
+ status: complete
3
+ phase: "02"
4
+ plan: "01"
5
+ title: "Split FeedFetcherTest into Concern-Based Classes"
6
+ commits:
7
+ - hash: a951c95
8
+ message: "feat(02-01): split FeedFetcherTest into 6 concern-based test classes"
9
+ tasks_completed: 4
10
+ tasks_total: 4
11
+ deviations: []
12
+ ---
13
+
14
+ ## What Was Built
15
+
16
+ Split the monolithic `FeedFetcherTest` (71 tests, 1350 lines, single class) into 6 independent test classes plus a shared helper module. Each file is independently runnable with `PARALLEL_WORKERS=1`. All 1033 tests pass, RuboCop zero offenses.
17
+
18
+ Test distribution:
19
+ - `FeedFetcherSuccessTest`: 5 tests (RSS/Atom/JSON fetching, ETag/304, Netflix cassette)
20
+ - `FeedFetcherErrorHandlingTest`: 13 tests (Faraday error wrapping, AIA resolution, failure recording)
21
+ - `FeedFetcherAdaptiveIntervalTest`: 6 tests (interval increase/decrease, min/max bounds, disabled mode)
22
+ - `FeedFetcherRetryCircuitTest`: 6 tests (retry state, circuit breaker, policy error handling)
23
+ - `FeedFetcherEntryProcessingTest`: 8 tests (entry creation/update tracking, error normalization, digest fallbacks)
24
+ - `FeedFetcherUtilitiesTest`: 33 tests (headers, jitter, metadata, signature, config helpers, parsing)
25
+
26
+ ## Files Modified
27
+
28
+ | Action | Path |
29
+ |--------|------|
30
+ | CREATE | `test/lib/source_monitor/fetching/feed_fetcher_test_helper.rb` |
31
+ | CREATE | `test/lib/source_monitor/fetching/feed_fetcher_success_test.rb` |
32
+ | CREATE | `test/lib/source_monitor/fetching/feed_fetcher_error_handling_test.rb` |
33
+ | CREATE | `test/lib/source_monitor/fetching/feed_fetcher_adaptive_interval_test.rb` |
34
+ | CREATE | `test/lib/source_monitor/fetching/feed_fetcher_retry_circuit_test.rb` |
35
+ | CREATE | `test/lib/source_monitor/fetching/feed_fetcher_entry_processing_test.rb` |
36
+ | CREATE | `test/lib/source_monitor/fetching/feed_fetcher_utilities_test.rb` |
37
+ | DELETE | `test/lib/source_monitor/fetching/feed_fetcher_test.rb` |