source_monitor 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +101 -58
  3. data/.claude/skills/sm-configure/SKILL.md +13 -2
  4. data/.claude/skills/sm-configure/reference/configuration-reference.md +33 -0
  5. data/.claude/skills/sm-host-setup/SKILL.md +15 -1
  6. data/.claude/skills/sm-host-setup/reference/setup-checklist.md +33 -0
  7. data/.claude/skills/sm-job/SKILL.md +1 -1
  8. data/.vbw-planning/REQUIREMENTS.md +22 -0
  9. data/.vbw-planning/ROADMAP.md +125 -0
  10. data/.vbw-planning/STATE.md +43 -0
  11. data/.vbw-planning/config.json +3 -1
  12. data/.vbw-planning/discovery.json +3 -1
  13. data/.vbw-planning/phases/01-generator-steps/01-CONTEXT.md +33 -0
  14. data/.vbw-planning/phases/01-generator-steps/01-VERIFICATION.md +86 -0
  15. data/.vbw-planning/phases/01-generator-steps/PLAN-01-SUMMARY.md +61 -0
  16. data/.vbw-planning/phases/01-generator-steps/PLAN-01.md +380 -0
  17. data/.vbw-planning/phases/02-verification/02-VERIFICATION.md +78 -0
  18. data/.vbw-planning/phases/02-verification/PLAN-01-SUMMARY.md +46 -0
  19. data/.vbw-planning/phases/02-verification/PLAN-01.md +500 -0
  20. data/.vbw-planning/phases/03-docs-alignment/03-VERIFICATION.md +89 -0
  21. data/.vbw-planning/phases/03-docs-alignment/PLAN-01-SUMMARY.md +48 -0
  22. data/.vbw-planning/phases/03-docs-alignment/PLAN-01.md +456 -0
  23. data/.vbw-planning/phases/04-dashboard-ux/04-VERIFICATION.md +129 -0
  24. data/.vbw-planning/phases/04-dashboard-ux/PLAN-01-SUMMARY.md +70 -0
  25. data/.vbw-planning/phases/04-dashboard-ux/PLAN-01.md +747 -0
  26. data/.vbw-planning/phases/05-active-storage-images/05-VERIFICATION.md +156 -0
  27. data/.vbw-planning/phases/05-active-storage-images/PLAN-01-SUMMARY.md +69 -0
  28. data/.vbw-planning/phases/05-active-storage-images/PLAN-01.md +455 -0
  29. data/.vbw-planning/phases/05-active-storage-images/PLAN-02-SUMMARY.md +39 -0
  30. data/.vbw-planning/phases/05-active-storage-images/PLAN-02.md +488 -0
  31. data/.vbw-planning/phases/06-netflix-feed-fix/06-VERIFICATION.md +100 -0
  32. data/.vbw-planning/phases/06-netflix-feed-fix/PLAN-01-SUMMARY.md +37 -0
  33. data/.vbw-planning/phases/06-netflix-feed-fix/PLAN-01.md +345 -0
  34. data/CHANGELOG.md +31 -0
  35. data/Gemfile.lock +1 -1
  36. data/VERSION +1 -1
  37. data/app/assets/builds/source_monitor/application.css +9 -0
  38. data/app/helpers/source_monitor/application_helper.rb +38 -0
  39. data/app/jobs/source_monitor/download_content_images_job.rb +72 -0
  40. data/app/models/source_monitor/item_content.rb +2 -0
  41. data/app/views/source_monitor/dashboard/_recent_activity.html.erb +9 -0
  42. data/app/views/source_monitor/items/_details.html.erb +2 -2
  43. data/app/views/source_monitor/logs/index.html.erb +9 -0
  44. data/app/views/source_monitor/sources/_details.html.erb +2 -2
  45. data/app/views/source_monitor/sources/_row.html.erb +1 -1
  46. data/docs/setup.md +10 -1
  47. data/docs/troubleshooting.md +38 -7
  48. data/lib/generators/source_monitor/install/install_generator.rb +101 -0
  49. data/lib/source_monitor/configuration/http_settings.rb +7 -1
  50. data/lib/source_monitor/configuration/images_settings.rb +37 -0
  51. data/lib/source_monitor/configuration.rb +3 -1
  52. data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +16 -7
  53. data/lib/source_monitor/dashboard/recent_activity.rb +1 -0
  54. data/lib/source_monitor/dashboard/recent_activity_presenter.rb +15 -2
  55. data/lib/source_monitor/fetching/feed_fetcher/entry_processor.rb +13 -0
  56. data/lib/source_monitor/http.rb +23 -0
  57. data/lib/source_monitor/images/content_rewriter.rb +81 -0
  58. data/lib/source_monitor/images/downloader.rb +82 -0
  59. data/lib/source_monitor/logs/table_presenter.rb +25 -0
  60. data/lib/source_monitor/setup/procfile_patcher.rb +31 -0
  61. data/lib/source_monitor/setup/queue_config_patcher.rb +84 -0
  62. data/lib/source_monitor/setup/verification/recurring_schedule_verifier.rb +102 -0
  63. data/lib/source_monitor/setup/verification/runner.rb +1 -1
  64. data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +1 -1
  65. data/lib/source_monitor/setup/workflow.rb +10 -0
  66. data/lib/source_monitor/version.rb +1 -1
  67. data/lib/source_monitor.rb +8 -0
  68. metadata +31 -1
@@ -0,0 +1,43 @@
1
+ <!-- VBW STATE -- Current milestone progress -->
2
+
3
+ # State
4
+
5
+ **Milestone:** generator-enhancements
6
+ **Current Phase:** All phases complete
7
+ **Status:** Ready for archive
8
+ **Date:** 2026-02-12
9
+
10
+ ## Progress
11
+
12
+ - Phase 0: Complete (quick fix ea788ea)
13
+ - Phase 1: Complete (1 plan, 5 tasks, 4 commits)
14
+ - Phase 2: Complete (1 plan, 5 tasks, 1 commit)
15
+ - Phase 3: Complete (1 plan, 5 tasks, 1 commit) -- QA PASS
16
+ - Phase 4: Complete (1 plan, 5 tasks, 5 commits) -- QA PASS 23/23
17
+ - Phase 5: Complete (2 plans, 9 tasks, 9 commits) -- QA PASS 34/34
18
+ - Phase 6: Complete (1 plan, 5 tasks, 5 commits) -- QA PASS 18/18
19
+
20
+ ## Decisions
21
+
22
+ | Decision | Date | Rationale |
23
+ |----------|------|-----------|
24
+ | Docs-first approach | 2026-02-11 | Shipped doc fixes before code changes to unblock users immediately |
25
+ | 3 phases for code changes | 2026-02-11 | Generator steps, verification, then docs alignment -- each independently testable |
26
+ | Always create/patch Procfile.dev | 2026-02-11 | Maximum hand-holding for host app setup |
27
+ | Target queue.yml only | 2026-02-11 | Rails 8 default, no legacy naming support |
28
+ | External links open new tab with icon | 2026-02-12 | Consistent UX for all external URLs across dashboard, logs, sources, items |
29
+ | Fetch log URL display: domain for RSS, item URL for scrapes | 2026-02-12 | User requested contextual URL display based on event type |
30
+ | Image downloads via background job to ItemContent | 2026-02-12 | Content images only, opt-in via config.images.download_to_active_storage |
31
+ | SSL fix: general cert store config, not Netflix-specific | 2026-02-12 | OpenSSL::X509::Store#set_default_paths on every Faraday connection |
32
+
33
+ ## Metrics
34
+
35
+ | Metric | Value |
36
+ |--------|-------|
37
+ | Phases | 7 (Phase 0-6) |
38
+ | Plans completed | 7 (across all phases) |
39
+ | Tasks completed | 34 |
40
+ | Commits | ~26 |
41
+ | Tests | 973 (up from 841) |
42
+ | RuboCop | 389 files, 0 offenses |
43
+ | Brakeman | 0 warnings |
@@ -8,5 +8,7 @@
8
8
  "visual_format": "unicode",
9
9
  "compaction_trigger": 130000,
10
10
  "max_tasks_per_plan": 5,
11
- "agent_teams": true
11
+ "agent_teams": true,
12
+ "model_profile": "quality",
13
+ "model_overrides": {}
12
14
  }
@@ -19,6 +19,8 @@
19
19
  "User wants comprehensive cleanup, not surface-level",
20
20
  "Public API changes are acceptable for convention alignment",
21
21
  "Tests should be updated to match any changes, not removed",
22
- "All layers: models, controllers, services, dead code"
22
+ "All layers: models, controllers, services, dead code",
23
+ "Generator should be maximally helpful -- create files rather than just warn",
24
+ "Target Rails 8 defaults (queue.yml) rather than supporting legacy naming"
23
25
  ]
24
26
  }
@@ -0,0 +1,33 @@
1
+ # Phase 1 Context: Install Generator Steps
2
+
3
+ ## User Vision
4
+ Enhance the install generator to automatically handle the two most common Solid Queue setup failures in host apps: missing Procfile.dev jobs entry and missing recurring_schedule dispatcher wiring.
5
+
6
+ ## Essential Features
7
+ - Generator creates Procfile.dev if missing (with web: and jobs: entries)
8
+ - Generator patches existing Procfile.dev to add jobs: entry (idempotent)
9
+ - Generator patches config/queue.yml dispatcher with recurring_schedule: config/recurring.yml
10
+ - Both steps integrated into the guided Setup::Workflow
11
+
12
+ ## Technical Preferences
13
+ - **Procfile.dev:** Always create/patch -- maximum hand-holding. Create if missing, patch if exists.
14
+ - **Queue config:** Target `config/queue.yml` (Rails 8 default). Don't worry about legacy `solid_queue.yml` naming.
15
+ - Follow existing generator patterns (idempotent, skip-if-present, say_status output)
16
+
17
+ ## Boundaries
18
+ - Don't modify any other config files (cable.yml, database.yml, etc.)
19
+ - Don't modify the initializer template
20
+ - Don't change existing generator steps (routes, initializer, recurring.yml)
21
+ - Don't add verification logic here (that's Phase 2)
22
+
23
+ ## Acceptance Criteria
24
+ - `bin/rails generate source_monitor:install` creates Procfile.dev with web: + jobs: when none exists
25
+ - `bin/rails generate source_monitor:install` adds jobs: line to existing Procfile.dev without duplicating
26
+ - `bin/rails generate source_monitor:install` adds recurring_schedule to queue.yml dispatcher
27
+ - `bin/source_monitor install` (guided) runs both new steps
28
+ - All existing generator tests still pass
29
+ - New tests cover: fresh Procfile.dev, existing with entry, existing without entry, missing queue.yml
30
+
31
+ ## Decisions Made
32
+ - Create Procfile.dev if missing (not just warn)
33
+ - Target config/queue.yml only (Rails 8 default), not solid_queue.yml
@@ -0,0 +1,86 @@
1
+ # PLAN-01 Verification Report
2
+
3
+ ## Verdict: PASS
4
+
5
+ **Verified by:** QA agent (deep tier, 30 checks)
6
+ **Date:** 2026-02-11
7
+
8
+ ---
9
+
10
+ ## Functional Verification
11
+
12
+ | # | Check | Result | Notes |
13
+ |---|-------|--------|-------|
14
+ | 1 | Generator tests pass | PASS | 20 runs, 109 assertions, 0 failures, 0 errors |
15
+ | 2 | Workflow tests pass | PASS | 8 runs, 22 assertions, 0 failures, 0 errors |
16
+ | 3 | Full test suite passes | PASS | 867 runs, 2898 assertions, 0 failures, 0 errors |
17
+ | 4 | RuboCop clean | PASS | 376 files inspected, no offenses detected |
18
+ | 5 | Brakeman clean | PASS | 0 warnings, 0 errors |
19
+
20
+ ## Code Review Checks
21
+
22
+ | # | Check | Result | Notes |
23
+ |---|-------|--------|-------|
24
+ | 6 | Public method order correct | PASS | Order is: `add_routes_mount` (L17), `create_initializer` (L24), `configure_recurring_jobs` (L36), `patch_procfile_dev` (L52), `configure_queue_dispatcher` (L70), `print_next_steps` (L90) |
25
+ | 7 | `patch_procfile_dev` handles 3 cases | PASS | Create (L64-66), append (L62-63), skip (L57-60) |
26
+ | 8 | `configure_queue_dispatcher` handles 4 cases | PASS | Missing file (L73-76), already configured (L80-83), needs patching (L85-87), no dispatchers (L254-256 via `add_recurring_schedule_to_dispatchers!`) |
27
+ | 9 | Private methods/constants in private section | PASS | `PROCFILE_JOBS_ENTRY` (L104), `RECURRING_SCHEDULE_VALUE` (L201), `DEFAULT_DISPATCHER` (L203), `queue_config_has_recurring_schedule?` (L209), `add_recurring_schedule_to_dispatchers!` (L229) -- all after `private` on L102 |
28
+ | 10 | `frozen_string_literal: true` on all new files | PASS | `procfile_patcher.rb` (L1), `queue_config_patcher.rb` (L1) both have it |
29
+ | 11 | Idempotency: both steps safe to run multiple times | PASS | `patch_procfile_dev`: checks `/^jobs:/` before acting; `configure_queue_dispatcher`: checks `has_recurring_schedule?` before acting. Test `test_does_not_duplicate_jobs_entry_when_rerun` explicitly verifies. |
30
+ | 12 | `YAML.safe_load` uses `aliases: true` | PASS | Generator L78: `YAML.safe_load(File.read(queue_path), aliases: true)`. QueueConfigPatcher L24: `YAML.safe_load(path.read, aliases: true)` |
31
+ | 13 | ProcfilePatcher matches `/^jobs:/` regex | PASS | Both generator (L57) and ProcfilePatcher (L17) use `/^jobs:/` regex |
32
+ | 14 | QueueConfigPatcher recursive dispatcher search | PASS | `has_recurring_schedule?` (L36-53) recursively iterates `parsed.each_value`, checks nested hashes, and also checks top-level `dispatchers` key for flat configs |
33
+ | 15 | Workflow has both new kwargs with defaults | PASS | `procfile_patcher: ProcfilePatcher.new` (L40), `queue_config_patcher: QueueConfigPatcher.new` (L41) |
34
+ | 16 | Workflow calls patchers in correct position | PASS | Called at L73-74, after `initializer_patcher.ensure_navigation_hint` (L72) and before devise check (L76) |
35
+ | 17 | Autoload entries in `lib/source_monitor.rb` | PASS | `ProcfilePatcher` (L164), `QueueConfigPatcher` (L165) both present in `Setup` module autoloads |
36
+ | 18 | No hardcoded paths or env-specific assumptions | PASS | Paths are relative to `destination_root` (generator) or injected via constructor kwargs (patcher classes). No absolute paths. |
37
+ | 19 | Test isolation (no cross-test contamination) | PASS | Generator tests use `prepare_destination` in setup. Workflow tests use Spy/Mock objects for all collaborators. No shared state. WORKER_SUFFIX handles parallel workers. |
38
+ | 20 | `say_status` calls use correct symbols | PASS | `:create` (L66), `:append` (L63), `:skip` (L58, L74, L81), `:info` (L91-99) -- all correct |
39
+
40
+ ## Edge Case Verification
41
+
42
+ | # | Check | Result | Notes |
43
+ |---|-------|--------|-------|
44
+ | 21 | Procfile.dev has `jobs:` with different content | PASS | Regex `/^jobs:/` matches any line starting with `jobs:` regardless of the command after it. Correctly skips without overwriting. |
45
+ | 22 | queue.yml nested environments with different dispatchers | PASS | `queue_config_has_recurring_schedule?` recursively walks all hash values. `add_recurring_schedule_to_dispatchers!` processes all nested dispatcher arrays under any env key. Both handle flat and nested configs. |
46
+ | 23 | queue.yml empty/nil after parsing | PASS | L78: `|| {}` ensures nil YAML parse result becomes empty hash. `queue_config_has_recurring_schedule?({})` returns false. `add_recurring_schedule_to_dispatchers!({})` adds default dispatcher section. |
47
+ | 24 | Procfile.dev has trailing newlines | PASS | When appending (L62), uses `puts("", PROCFILE_JOBS_ENTRY)` which adds a blank line before the jobs entry. This handles trailing newlines gracefully -- the blank line acts as separator. |
48
+ | 25 | queue.yml dispatchers is empty array | PASS | If `dispatchers: []`, the `any?` check in `has_recurring_schedule?` returns false (no items to check). `add_recurring_schedule_to_dispatchers!` iterates the empty array (no-op) but sets `found_dispatchers = true` since the key exists. This means an empty dispatchers array stays empty with no recurring_schedule added. **Minor note**: This is a debatable edge case -- an empty dispatchers array means the user intentionally has no dispatchers, so not adding one is reasonable behavior. |
49
+
50
+ ## Requirements Verification
51
+
52
+ | # | Check | Result | Notes |
53
+ |---|-------|--------|-------|
54
+ | 26 | REQ-16: Generator patches Procfile.dev | PASS | `patch_procfile_dev` creates/appends/skips as needed. 4 tests cover all scenarios. |
55
+ | 27 | REQ-17: Generator patches queue.yml | PASS | `configure_queue_dispatcher` patches/skips/handles-missing as needed. 4 tests cover all scenarios. |
56
+ | 28 | REQ-18: Workflow integrates both | PASS | `workflow.rb` L73-74 calls both patchers. Workflow test L96-97 verifies both are called. |
57
+ | 29 | Test count increased | PASS | Was 841, now 867 (+26 tests: 8 new generator tests + 18 other additions from this phase) |
58
+ | 30 | No regressions in existing tests | PASS | Full suite: 867 runs, 2898 assertions, 0 failures, 0 errors |
59
+
60
+ ---
61
+
62
+ ## Issues Found
63
+
64
+ ### Minor (non-blocking)
65
+
66
+ 1. **Empty dispatchers array edge case (Check #25)**: If `queue.yml` has `dispatchers: []`, the code sees `found_dispatchers = true` (the key exists) but doesn't add any recurring_schedule since there are no dispatcher hashes to iterate. This means the empty array is left as-is. This is arguably correct behavior (respecting user intent), but worth documenting.
67
+
68
+ 2. **Generator test count**: The plan expected "11 existing + 8 new = 19" but the actual count is 20 (12 existing + 8 new). The plan summary correctly notes 20. The 12th existing test was likely the `test_outputs_next_steps_with_doc_links` test that was already present. No issue -- just a plan estimate mismatch.
69
+
70
+ ### None (blocking)
71
+
72
+ No blocking issues found.
73
+
74
+ ---
75
+
76
+ ## Risk Assessment
77
+
78
+ **Risk level: LOW**
79
+
80
+ - All 867 tests pass with 0 failures
81
+ - RuboCop and Brakeman clean
82
+ - Both new generator steps follow established idempotent patterns
83
+ - Workflow integration is minimal (2 unconditional patcher calls)
84
+ - New files are self-contained with no side effects
85
+ - Test coverage includes all happy paths, skip paths, and edge cases
86
+ - No security concerns (file operations are scoped to destination_root)
@@ -0,0 +1,61 @@
1
+ # PLAN-01 Summary: procfile-queue-generator-steps
2
+
3
+ ## Status: COMPLETE
4
+
5
+ ## What was done
6
+
7
+ ### Task 1: Add Procfile.dev generator step
8
+ - Added `patch_procfile_dev` public method to `InstallGenerator` (between `configure_recurring_jobs` and `print_next_steps`)
9
+ - Handles 3 cases: create new file, append to existing, skip if already present
10
+ - Added `PROCFILE_JOBS_ENTRY` private constant
11
+ - Updated `print_next_steps` with Procfile.dev info message
12
+
13
+ ### Task 2: Add queue.yml dispatcher step
14
+ - Added `configure_queue_dispatcher` public method to `InstallGenerator` (between `patch_procfile_dev` and `print_next_steps`)
15
+ - Handles 4 cases: missing file, already configured, needs patching, no dispatchers key
16
+ - Added private helpers: `queue_config_has_recurring_schedule?`, `add_recurring_schedule_to_dispatchers!`
17
+ - Added `RECURRING_SCHEDULE_VALUE` and `DEFAULT_DISPATCHER` private constants
18
+
19
+ ### Task 3: Add 8 generator tests
20
+ - 4 Procfile.dev tests: create, append, skip, no-duplicate
21
+ - 4 queue.yml tests: patch dispatchers, skip when configured, skip when missing, add default dispatcher
22
+ - All 20 generator tests pass (12 existing + 8 new)
23
+
24
+ ### Task 4: Add workflow helpers and integration
25
+ - Created `lib/source_monitor/setup/procfile_patcher.rb` (lightweight Pathname-based helper)
26
+ - Created `lib/source_monitor/setup/queue_config_patcher.rb` (YAML parsing with recursive dispatcher search)
27
+ - Modified `lib/source_monitor/setup/workflow.rb`: added 2 new kwargs, wired into `run` after `initializer_patcher` and before devise check
28
+ - Added autoload entries in `lib/source_monitor.rb`
29
+ - Updated all 8 workflow tests with new collaborator spies
30
+
31
+ ### Task 5: Full suite verification
32
+ - 867 test runs, 2898 assertions, 0 failures, 0 errors
33
+ - RuboCop: 376 files, 0 offenses
34
+ - Brakeman: 0 warnings
35
+
36
+ ## Files modified
37
+ - `lib/generators/source_monitor/install/install_generator.rb` -- 2 new public methods, 4 private helpers/constants
38
+ - `test/lib/generators/install_generator_test.rb` -- 8 new tests
39
+ - `lib/source_monitor/setup/procfile_patcher.rb` -- NEW
40
+ - `lib/source_monitor/setup/queue_config_patcher.rb` -- NEW
41
+ - `lib/source_monitor/setup/workflow.rb` -- 2 new kwargs, 2 new patcher calls
42
+ - `test/lib/source_monitor/setup/workflow_test.rb` -- added patcher spies to all tests
43
+ - `lib/source_monitor.rb` -- 2 new autoload entries
44
+
45
+ ## Commits
46
+ 1. `af59468` feat(generator): add Procfile.dev patching step to install generator
47
+ 2. `29250af` feat(generator): add queue.yml dispatcher step to install generator
48
+ 3. `96365b9` feat(generator): add 8 tests for Procfile.dev and queue.yml steps
49
+ 4. `4393d17` feat(generator): add workflow helpers and wire patchers into guided install
50
+
51
+ ## Acceptance criteria met
52
+ - Generator creates Procfile.dev with web: + jobs: entries when none exists (REQ-16)
53
+ - Generator appends jobs: entry to existing Procfile.dev without duplicating (REQ-16)
54
+ - Generator skips Procfile.dev when jobs: entry already present (REQ-16 idempotency)
55
+ - Generator patches queue.yml dispatchers with recurring_schedule (REQ-17)
56
+ - Generator skips queue.yml when recurring_schedule already configured (REQ-17 idempotency)
57
+ - Generator handles missing queue.yml gracefully (REQ-17 edge case)
58
+ - Guided workflow runs both patchers after generator step (REQ-18)
59
+ - All existing tests continue to pass (no regressions)
60
+ - 8 new generator tests cover all scenarios
61
+ - RuboCop clean, Brakeman clean
@@ -0,0 +1,380 @@
1
+ ---
2
+ phase: 1
3
+ plan: "01"
4
+ title: procfile-queue-generator-steps
5
+ type: execute
6
+ wave: 1
7
+ depends_on: []
8
+ cross_phase_deps: []
9
+ autonomous: true
10
+ effort_override: thorough
11
+ skills_used: []
12
+ files_modified:
13
+ - lib/generators/source_monitor/install/install_generator.rb
14
+ - test/lib/generators/install_generator_test.rb
15
+ - lib/source_monitor/setup/procfile_patcher.rb
16
+ - lib/source_monitor/setup/queue_config_patcher.rb
17
+ - lib/source_monitor/setup/workflow.rb
18
+ - test/lib/source_monitor/setup/workflow_test.rb
19
+ must_haves:
20
+ truths:
21
+ - "Running `bin/rails test test/lib/generators/install_generator_test.rb` exits 0 with 0 failures"
22
+ - "Running `bin/rails test test/lib/source_monitor/setup/workflow_test.rb` exits 0 with 0 failures"
23
+ - "Running `bin/rubocop lib/generators/source_monitor/install/install_generator.rb lib/source_monitor/setup/procfile_patcher.rb lib/source_monitor/setup/queue_config_patcher.rb lib/source_monitor/setup/workflow.rb` exits 0 with no offenses"
24
+ - "Running the generator against an empty destination creates a Procfile.dev with both `web:` and `jobs:` lines"
25
+ - "Running the generator against an existing Procfile.dev that has no `jobs:` line appends one"
26
+ - "Running the generator against an existing Procfile.dev that already has a `jobs:` line skips with say_status :skip"
27
+ - "Running the generator against a queue.yml with dispatchers but no recurring_schedule adds it"
28
+ - "Running the generator against a queue.yml that already has recurring_schedule skips"
29
+ artifacts:
30
+ - path: "lib/generators/source_monitor/install/install_generator.rb"
31
+ provides: "Procfile.dev and queue.yml generator steps"
32
+ contains: "def patch_procfile_dev"
33
+ - path: "lib/generators/source_monitor/install/install_generator.rb"
34
+ provides: "queue.yml dispatcher patching"
35
+ contains: "def configure_queue_dispatcher"
36
+ - path: "test/lib/generators/install_generator_test.rb"
37
+ provides: "Tests for both new generator steps"
38
+ contains: "test_creates_procfile_dev_when_none_exists"
39
+ - path: "lib/source_monitor/setup/procfile_patcher.rb"
40
+ provides: "Workflow helper for Procfile.dev patching"
41
+ contains: "class ProcfilePatcher"
42
+ - path: "lib/source_monitor/setup/queue_config_patcher.rb"
43
+ provides: "Workflow helper for queue.yml patching"
44
+ contains: "class QueueConfigPatcher"
45
+ - path: "lib/source_monitor/setup/workflow.rb"
46
+ provides: "Workflow integration of both new steps"
47
+ contains: "procfile_patcher"
48
+ key_links:
49
+ - from: "install_generator.rb#patch_procfile_dev"
50
+ to: "REQ-16"
51
+ via: "Generator patches Procfile.dev with jobs: entry"
52
+ - from: "install_generator.rb#configure_queue_dispatcher"
53
+ to: "REQ-17"
54
+ via: "Generator patches queue config with recurring_schedule"
55
+ - from: "workflow.rb"
56
+ to: "REQ-18"
57
+ via: "Guided workflow integrates both new steps"
58
+ ---
59
+ <objective>
60
+ Add two new idempotent steps to the install generator (Procfile.dev patching and queue.yml dispatcher wiring) and integrate both into the guided Setup::Workflow. All steps must follow existing generator conventions: idempotent, skip-if-present, say_status output. REQ-16, REQ-17, REQ-18.
61
+ </objective>
62
+ <context>
63
+ @lib/generators/source_monitor/install/install_generator.rb -- The existing generator with 3 public steps: add_routes_mount, create_initializer, configure_recurring_jobs. New steps must be added as public methods BEFORE print_next_steps (Rails generators execute public methods in definition order). Each step follows the pattern: check-if-already-done -> skip with say_status :skip OR perform action with say_status :create/:append. The recurring jobs step is the best pattern to follow -- it handles both fresh-file and existing-file cases with YAML parsing.
64
+
65
+ @test/lib/generators/install_generator_test.rb -- 11 existing tests using Rails::Generators::TestCase. Tests use `run_generator` to execute, `assert_file` to check contents, and manually create pre-existing files in `destination_root` to test idempotent skip paths. The WORKER_SUFFIX pattern handles parallel test isolation. New tests should follow the same patterns.
66
+
67
+ @lib/source_monitor/setup/workflow.rb -- The guided installer. Collaborators are injected via constructor kwargs with defaults. The `run` method calls them in sequence. New patchers should be added AFTER `install_generator.run` (which runs the generator) and BEFORE `verifier.call`. Two new kwargs needed: `procfile_patcher:` and `queue_config_patcher:`.
68
+
69
+ @lib/source_monitor/setup/install_generator.rb -- Existing wrapper that shells out to `bin/rails generate source_monitor:install`. The generator handles Procfile.dev and queue.yml, so the workflow helpers only need to handle the guided-mode post-generator patching if the generator didn't run (edge case) OR to provide explicit say-status output in the guided flow. However, since the workflow already delegates to the generator, the simplest approach is to add the patching logic directly to the generator (which the workflow already calls) and add lightweight workflow helpers that handle standalone guided-mode usage.
70
+
71
+ @test/lib/source_monitor/setup/workflow_test.rb -- Uses Spy objects and Minitest::Mock for all collaborators. New patchers need Spy instances in test setup. Existing test "run orchestrates all installers" expects a specific prompter call count -- adding new steps MUST NOT add new prompter calls (the patching is unconditional, no user prompt needed).
72
+
73
+ @.vbw-planning/phases/01-generator-steps/01-CONTEXT.md -- Phase context with user decisions and acceptance criteria.
74
+
75
+ **Rationale:** The generator is the primary entry point -- both `bin/rails g source_monitor:install` and the guided workflow funnel through it. Adding the steps directly to the generator ensures both paths are covered. The workflow helpers provide explicit visibility in the guided flow.
76
+
77
+ **Key design decisions:**
78
+ 1. Procfile.dev default content: `web: bin/rails server -p 3000\njobs: bundle exec rake solid_queue:start` (standard Rails 8 Procfile.dev pattern)
79
+ 2. The `jobs:` line check should match any line starting with `jobs:` (case-sensitive, anchored to line start)
80
+ 3. Queue.yml patching targets any dispatcher block under any environment key and adds `recurring_schedule: config/recurring.yml` if not present
81
+ 4. Queue.yml patching must handle both `default: &default` with env aliases AND flat per-environment configs
82
+ 5. If queue.yml does not exist, skip with a helpful message (the file is Rails-generated; creating it from scratch could conflict with the host app's queue backend choice)
83
+ </context>
84
+ <tasks>
85
+ <task type="auto">
86
+ <name>add-procfile-dev-generator-step</name>
87
+ <files>
88
+ lib/generators/source_monitor/install/install_generator.rb
89
+ </files>
90
+ <action>
91
+ Add a new public method `patch_procfile_dev` to InstallGenerator, placed AFTER `configure_recurring_jobs` and BEFORE `print_next_steps`. This ensures Rails generators execute it in the correct order.
92
+
93
+ The method must:
94
+ 1. Build the Procfile.dev path: `File.join(destination_root, "Procfile.dev")`
95
+ 2. If the file exists AND already contains a line matching `/^jobs:/` -> `say_status :skip, "Procfile.dev (jobs entry already present)", :yellow` and return
96
+ 3. If the file exists but has no `jobs:` line -> append `\njobs: bundle exec rake solid_queue:start\n` to the file, then `say_status :append, "Procfile.dev", :green`
97
+ 4. If the file does not exist -> create it with content:
98
+ ```
99
+ web: bin/rails server -p 3000
100
+ jobs: bundle exec rake solid_queue:start
101
+ ```
102
+ Then `say_status :create, "Procfile.dev", :green`
103
+
104
+ Add a private constant for the jobs line:
105
+ ```ruby
106
+ PROCFILE_JOBS_ENTRY = "jobs: bundle exec rake solid_queue:start"
107
+ ```
108
+
109
+ Update `print_next_steps` to mention Procfile.dev:
110
+ ```ruby
111
+ say_status :info,
112
+ "Procfile.dev configured -- run bin/dev to start both web server and Solid Queue workers.",
113
+ :green
114
+ ```
115
+ </action>
116
+ <verify>
117
+ Read the modified file and confirm: (a) `patch_procfile_dev` is defined between `configure_recurring_jobs` and `print_next_steps`, (b) it handles all 3 cases (create, append, skip), (c) the PROCFILE_JOBS_ENTRY constant is in the private section.
118
+ </verify>
119
+ <done>
120
+ The generator has a `patch_procfile_dev` method that creates, appends to, or skips Procfile.dev depending on current state. The method follows the same idempotent pattern as `configure_recurring_jobs`.
121
+ </done>
122
+ </task>
123
+ <task type="auto">
124
+ <name>add-queue-config-generator-step</name>
125
+ <files>
126
+ lib/generators/source_monitor/install/install_generator.rb
127
+ </files>
128
+ <action>
129
+ Add a new public method `configure_queue_dispatcher` to InstallGenerator, placed AFTER `patch_procfile_dev` and BEFORE `print_next_steps`.
130
+
131
+ The method must:
132
+ 1. Build the queue.yml path: `File.join(destination_root, "config/queue.yml")`
133
+ 2. If the file does not exist -> `say_status :skip, "config/queue.yml (file not found -- create it or run rails app:update to generate)", :yellow` and return
134
+ 3. Read and parse the YAML with `YAML.safe_load(File.read(path), aliases: true)`
135
+ 4. If the parsed content already contains `recurring_schedule` anywhere in any dispatcher entry -> `say_status :skip, "config/queue.yml (recurring_schedule already configured)", :yellow` and return
136
+ 5. Otherwise, find dispatcher entries and add `"recurring_schedule" => "config/recurring.yml"` to each one. The structure is:
137
+ - Environment-based: `{ "default" => { "dispatchers" => [{ "polling_interval" => 1 }] } }`
138
+ - Or flat: `{ "dispatchers" => [{ "polling_interval" => 1 }] }`
139
+ 6. Write back the modified YAML and `say_status :append, "config/queue.yml (added recurring_schedule to dispatchers)", :green`
140
+
141
+ Add a private helper `queue_config_has_recurring_schedule?(parsed)` that recursively checks if any hash value under `dispatchers` contains `recurring_schedule`.
142
+
143
+ Add a private helper `add_recurring_schedule_to_dispatchers!(parsed)` that walks the parsed hash and adds `"recurring_schedule" => "config/recurring.yml"` to each dispatcher hash that lacks it.
144
+
145
+ **Important edge case:** If the queue.yml has no `dispatchers` key at any level, add a default dispatcher section:
146
+ ```yaml
147
+ dispatchers:
148
+ - polling_interval: 1
149
+ batch_size: 500
150
+ recurring_schedule: config/recurring.yml
151
+ ```
152
+
153
+ Write back using `YAML.dump` (the file is machine-managed YAML, not hand-formatted like recurring.yml).
154
+ </action>
155
+ <verify>
156
+ Read the modified file and confirm: (a) `configure_queue_dispatcher` is defined between `patch_procfile_dev` and `print_next_steps`, (b) it handles all cases (missing file, already configured, needs patching, no dispatchers key), (c) helper methods are private.
157
+ </verify>
158
+ <done>
159
+ The generator has a `configure_queue_dispatcher` method that patches queue.yml dispatchers with recurring_schedule. It handles missing files, already-configured files, and files needing patching.
160
+ </done>
161
+ </task>
162
+ <task type="auto">
163
+ <name>add-generator-tests</name>
164
+ <files>
165
+ test/lib/generators/install_generator_test.rb
166
+ </files>
167
+ <action>
168
+ Add the following tests to InstallGeneratorTest, following existing patterns:
169
+
170
+ **Procfile.dev tests:**
171
+
172
+ 1. `test_creates_procfile_dev_when_none_exists` -- Run generator, assert Procfile.dev exists with both `web:` and `jobs:` lines.
173
+
174
+ 2. `test_appends_jobs_entry_to_existing_procfile_dev` -- Create a Procfile.dev with only `web: bin/rails server -p 3000`, run generator, assert file now contains `jobs:` line AND still contains `web:` line.
175
+
176
+ 3. `test_skips_procfile_dev_when_jobs_entry_already_present` -- Create a Procfile.dev with `web:` and `jobs:` lines, run generator, capture output, assert output contains "skip".
177
+
178
+ 4. `test_does_not_duplicate_jobs_entry_when_rerun` -- Run generator twice, assert Procfile.dev contains exactly 1 line matching `/^jobs:/`.
179
+
180
+ **Queue config tests:**
181
+
182
+ 5. `test_patches_queue_yml_dispatcher_with_recurring_schedule` -- Create a config/queue.yml with dispatchers section (no recurring_schedule), run generator, assert file contains `recurring_schedule`.
183
+
184
+ 6. `test_skips_queue_yml_when_recurring_schedule_already_present` -- Create a config/queue.yml that already has `recurring_schedule: config/recurring.yml` in the dispatcher, run generator, capture output, assert output contains "skip".
185
+
186
+ 7. `test_skips_queue_yml_when_file_missing` -- Do NOT create config/queue.yml, run generator, capture output, assert output contains "skip" and "not found".
187
+
188
+ 8. `test_adds_default_dispatcher_when_none_exists_in_queue_yml` -- Create a config/queue.yml with queues but no dispatchers section, run generator, assert file now contains both `dispatchers` and `recurring_schedule`.
189
+
190
+ For queue.yml tests, create realistic YAML content matching the Rails 8 default structure (from host_app_harness.rb):
191
+ ```yaml
192
+ default: &default
193
+ dispatchers:
194
+ - polling_interval: 1
195
+ batch_size: 500
196
+ workers:
197
+ - queues: "*"
198
+ threads: 3
199
+ polling_interval: 0.1
200
+
201
+ development:
202
+ <<: *default
203
+
204
+ test:
205
+ <<: *default
206
+
207
+ production:
208
+ <<: *default
209
+ ```
210
+
211
+ Use `File.join(destination_root, "config")` + `FileUtils.mkdir_p` to create pre-existing files, matching the pattern from `test_merges_into_existing_recurring_yml_with_default_key`.
212
+ </action>
213
+ <verify>
214
+ Run `PARALLEL_WORKERS=1 bin/rails test test/lib/generators/install_generator_test.rb` and confirm all tests pass (existing + new). Run `bin/rubocop test/lib/generators/install_generator_test.rb` and confirm no offenses.
215
+ </verify>
216
+ <done>
217
+ 8 new tests covering all Procfile.dev and queue.yml scenarios. All 19 tests (11 existing + 8 new) pass. RuboCop clean.
218
+ </done>
219
+ </task>
220
+ <task type="auto">
221
+ <name>add-workflow-helpers-and-integration</name>
222
+ <files>
223
+ lib/source_monitor/setup/procfile_patcher.rb
224
+ lib/source_monitor/setup/queue_config_patcher.rb
225
+ lib/source_monitor/setup/workflow.rb
226
+ test/lib/source_monitor/setup/workflow_test.rb
227
+ </files>
228
+ <action>
229
+ **Create `lib/source_monitor/setup/procfile_patcher.rb`:**
230
+
231
+ Follow the pattern from `install_generator.rb` (the setup wrapper, not the Rails generator). This is a lightweight wrapper that patches Procfile.dev in the host app's root directory. Since the Rails generator already handles this when run via `bin/rails g source_monitor:install`, this helper exists for guided workflow usage where explicit step visibility is desired.
232
+
233
+ ```ruby
234
+ # frozen_string_literal: true
235
+
236
+ module SourceMonitor
237
+ module Setup
238
+ class ProcfilePatcher
239
+ JOBS_ENTRY = "jobs: bundle exec rake solid_queue:start"
240
+
241
+ def initialize(path: "Procfile.dev")
242
+ @path = Pathname.new(path)
243
+ end
244
+
245
+ def patch
246
+ if path.exist?
247
+ content = path.read
248
+ return false if content.match?(/^jobs:/)
249
+ path.open("a") { |f| f.puts("", JOBS_ENTRY) }
250
+ else
251
+ path.write("web: bin/rails server -p 3000\n#{JOBS_ENTRY}\n")
252
+ end
253
+ true
254
+ end
255
+
256
+ private
257
+
258
+ attr_reader :path
259
+ end
260
+ end
261
+ end
262
+ ```
263
+
264
+ **Create `lib/source_monitor/setup/queue_config_patcher.rb`:**
265
+
266
+ ```ruby
267
+ # frozen_string_literal: true
268
+
269
+ require "yaml"
270
+
271
+ module SourceMonitor
272
+ module Setup
273
+ class QueueConfigPatcher
274
+ RECURRING_SCHEDULE_VALUE = "config/recurring.yml"
275
+
276
+ def initialize(path: "config/queue.yml")
277
+ @path = Pathname.new(path)
278
+ end
279
+
280
+ def patch
281
+ return false unless path.exist?
282
+
283
+ parsed = YAML.safe_load(path.read, aliases: true) || {}
284
+ return false if has_recurring_schedule?(parsed)
285
+
286
+ add_recurring_schedule!(parsed)
287
+ path.write(YAML.dump(parsed))
288
+ true
289
+ end
290
+
291
+ private
292
+
293
+ attr_reader :path
294
+
295
+ # (include the same recursive helpers as the generator)
296
+ end
297
+ end
298
+ end
299
+ ```
300
+
301
+ **Modify `lib/source_monitor/setup/workflow.rb`:**
302
+
303
+ 1. Add `require_relative "procfile_patcher"` and `require_relative "queue_config_patcher"` at the top (after existing require_relative lines).
304
+ 2. Add two new kwargs to `initialize`: `procfile_patcher: ProcfilePatcher.new` and `queue_config_patcher: QueueConfigPatcher.new`.
305
+ 3. Store them as instance variables and add to `attr_reader`.
306
+ 4. In the `run` method, call both AFTER `initializer_patcher.ensure_navigation_hint` and BEFORE the devise check:
307
+ ```ruby
308
+ procfile_patcher.patch
309
+ queue_config_patcher.patch
310
+ ```
311
+ These are unconditional (no user prompt -- maximum hand-holding per user decision).
312
+
313
+ **Modify `test/lib/source_monitor/setup/workflow_test.rb`:**
314
+
315
+ 1. In the "run orchestrates all installers" test: add `procfile_patcher = Spy.new(true)` and `queue_config_patcher = Spy.new(true)`, pass them to `Workflow.new`, and assert `assert_equal :patch, procfile_patcher.calls.first.first` and `assert_equal :patch, queue_config_patcher.calls.first.first`.
316
+ 2. In ALL other tests that construct a Workflow: add `procfile_patcher: Spy.new(true)` and `queue_config_patcher: Spy.new(true)` kwargs to prevent test failures from missing the new required collaborators (they have defaults, but the Spy pattern is used for isolation).
317
+ </action>
318
+ <verify>
319
+ Run `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/workflow_test.rb` and confirm all tests pass. Run `bin/rubocop lib/source_monitor/setup/procfile_patcher.rb lib/source_monitor/setup/queue_config_patcher.rb lib/source_monitor/setup/workflow.rb` and confirm no offenses.
320
+ </verify>
321
+ <done>
322
+ Two new workflow helper classes created. Workflow.rb wires both patchers into the guided install flow. All workflow tests pass with the new collaborators injected. RuboCop clean.
323
+ </done>
324
+ </task>
325
+ <task type="auto">
326
+ <name>full-suite-verification</name>
327
+ <files>
328
+ lib/generators/source_monitor/install/install_generator.rb
329
+ test/lib/generators/install_generator_test.rb
330
+ lib/source_monitor/setup/workflow.rb
331
+ test/lib/source_monitor/setup/workflow_test.rb
332
+ </files>
333
+ <action>
334
+ Run the full test suite and linting to confirm no regressions:
335
+
336
+ 1. `PARALLEL_WORKERS=1 bin/rails test test/lib/generators/install_generator_test.rb test/lib/source_monitor/setup/workflow_test.rb` -- all targeted tests pass
337
+ 2. `bin/rails test` -- full suite passes with 841+ runs and 0 failures
338
+ 3. `bin/rubocop` -- zero offenses
339
+ 4. Review the final state of all modified files to confirm:
340
+ - Generator public methods are in correct order: add_routes_mount, create_initializer, configure_recurring_jobs, patch_procfile_dev, configure_queue_dispatcher, print_next_steps
341
+ - All new private methods and constants are in the private section
342
+ - Workflow constructor has all collaborators with sensible defaults
343
+ - Workflow.run calls patchers in correct position (after generator, before verifier)
344
+
345
+ If any test failures or RuboCop offenses are found, fix them before completing.
346
+ </action>
347
+ <verify>
348
+ `bin/rails test` exits 0 with 841+ runs, 0 failures. `bin/rubocop` exits 0 with 0 offenses. `bin/brakeman --no-pager` exits 0 with 0 warnings.
349
+ </verify>
350
+ <done>
351
+ Full test suite passes. RuboCop clean. Brakeman clean. All REQ-16, REQ-17, REQ-18 acceptance criteria met.
352
+ </done>
353
+ </task>
354
+ </tasks>
355
+ <verification>
356
+ 1. `PARALLEL_WORKERS=1 bin/rails test test/lib/generators/install_generator_test.rb` -- 19+ tests pass (11 existing + 8 new)
357
+ 2. `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/workflow_test.rb` -- all workflow tests pass
358
+ 3. `bin/rails test` -- 841+ runs, 0 failures
359
+ 4. `bin/rubocop` -- 0 offenses
360
+ 5. `bin/brakeman --no-pager` -- 0 warnings
361
+ 6. `grep -n 'def patch_procfile_dev' lib/generators/source_monitor/install/install_generator.rb` returns a match
362
+ 7. `grep -n 'def configure_queue_dispatcher' lib/generators/source_monitor/install/install_generator.rb` returns a match
363
+ 8. `grep -n 'procfile_patcher' lib/source_monitor/setup/workflow.rb` returns matches in initialize and run
364
+ 9. `grep -n 'queue_config_patcher' lib/source_monitor/setup/workflow.rb` returns matches in initialize and run
365
+ </verification>
366
+ <success_criteria>
367
+ - Generator creates Procfile.dev with web: + jobs: entries when none exists (REQ-16)
368
+ - Generator appends jobs: entry to existing Procfile.dev without duplicating (REQ-16)
369
+ - Generator skips Procfile.dev when jobs: entry already present (REQ-16 idempotency)
370
+ - Generator patches queue.yml dispatchers with recurring_schedule (REQ-17)
371
+ - Generator skips queue.yml when recurring_schedule already configured (REQ-17 idempotency)
372
+ - Generator handles missing queue.yml gracefully (REQ-17 edge case)
373
+ - Guided workflow runs both patchers after generator step (REQ-18)
374
+ - All existing tests continue to pass (no regressions)
375
+ - 8+ new generator tests cover all scenarios
376
+ - RuboCop clean, Brakeman clean
377
+ </success_criteria>
378
+ <output>
379
+ .vbw-planning/phases/01-generator-steps/PLAN-01-SUMMARY.md
380
+ </output>