Give os-sdlc cops/ a Zeitwerk loader so tests and .rubocop.yml stop naming each cop file #1393

Closed
opened 2026-10-26 13:57:45 +00:00 by jared · 5 comments
Owner

Problem

plugins/os-sdlc/lib/os_sdlc_loader.rb:16 runs loader.ignore(File.join(os_sdlc_dir, "cops")). No loader autoloads RuboCop::Cop::Sdlc::*. Each file that needs a cop must name it with require_relative.

Today:

  • Each of the 48 cop tests in plugins/os-sdlc/tests/cops/ carries require_relative "../../lib/os_sdlc/cops/<cop>". 19 of them wrap it in begin ... rescue LoadError; nil; end, which hides a missing file.
  • plugins/os-sdlc/.rubocop.yml has a require: list of 39 per-cop entries.
  • Files in cops/ carry about 130 top-level require_relative calls to their helpers.
  • 7 detector/ files require cop files: detector/reach_through.rb:3, holders.rb:3-4, inward_edges.rb:3-4, error_lineage/rescue_index.rb:3, effect_sites.rb:3. detector/cop_sites.rb:4 requires cops/cop_source.

Reproduce

n/a: design change. Count with grep -rc 'require_relative.*cops/' plugins/os-sdlc/tests/cops.

Approach

Investigation, then change. Untested sketch: add a second Zeitwerk loader for cops/ with namespace: RuboCop::Cop::Sdlc. Require it once from test_helper and as the single .rubocop.yml require entry.

Decided 2026-10-26: the cop namespaces are mixed (Structural 128 files, Dry 18, Minitest 7, Sequel 6, plain Sdlc 5) and the flat cops/ paths do not name them. #1397 moves the files into department folders (cops/structural/, cops/dry/, ...) so each path names its constant; this ticket starts after #1397. The loader lives in a new lib/os_sdlc_cops_loader.rb beside lib/os_sdlc_loader.rb, outside the OsSdlc loader's root. Cops register on class definition, so the loader calls eager_load under rubocop.

Required work:

  1. Prove the loader registers every Sdlc cop under rubocop (rubocop --show-cops | grep Sdlc/ count unchanged).
  2. Remove the per-cop require_relative lines from cop tests, including the 19 rescue LoadError blocks.
  3. Collapse the .rubocop.yml require: list to one entry.
  4. Remove the cops/ and detector/ require_relative calls that the loader makes redundant. A detector/ file that needs a cop requires os_sdlc_cops_loader once in place of its per-cop lines.
  5. Keep the 8 sweep-tier cops off in the default config. A Zeitwerk eager_load registers 48 Sdlc cops; .rubocop.yml requires 40 today, and the 8 others are the sweep-tier cops that only .rubocop-sweep.yml requires: Sdlc/Dry/RepeatedPathLiteral, Sdlc/Structural/EffectInModel, ForwardingClass, InwardDependency, ReachThroughChain, RescueSite, UnderscoredParameter, UnlayeredConstant. Set Enabled: false for each in .rubocop.yml, with no *Path parameter (lint/worklist_rubocop_config.rb rewrites *Path parameters per layer). Collapse the .rubocop-sweep.yml require: list to the loader entry and keep its Enabled: true entries; lint/sweeping.rb layers the sweep config after .rubocop.yml, so its Enabled: true must win.

Acceptance criteria

  1. Given the full os-sdlc suite, when it runs, then it passes with zero require_relative ".../cops/..." lines under plugins/os-sdlc/tests/cops/.
  2. Given rubocop --show-cops with .rubocop.yml, when the Sdlc cops whose Enabled is not false are listed, then the list equals the list before the change (38 cops on 2026-10-26; --show-cops also lists disabled cops, so count enabled ones).
  3. Given .rubocop-sweep.yml layered after .rubocop.yml, as lint/sweeping.rb runs it, when the enabled Sdlc cops are listed, then each of the 8 sweep-tier cops is enabled. (Corrected 2026-10-26 during implementation 169: lint/sweeping.rb is the only code consumer of the sweep config, and it layers the file and passes only:; the standalone -c .rubocop-sweep.yml count is not an interface.)

Owner sketch

# lib/os_sdlc_cops_loader.rb
module OsSdlc
  def self.cops_loader
end
# Zeitwerk::Loader: push_dir(lib/os_sdlc/cops, namespace: RuboCop::Cop::Sdlc); setup; eager_load

Blocking edges

Blocked by #1397.

Origin

  • Trigger: Jared approved this ticket as "A2" in the session.
  • Improvised this session: none
  • Chain: per-cop require_relative in every test ← loader.ignore of cops/ in plugins/os-sdlc/lib/os_sdlc_loader.rb:16 ← DESIGN (loader file, line 16)
  • Root candidate: none found; searched os_sdlc_loader, loader.ignore, cops/ zeitwerk, require_relative cops, .rubocop.yml require, RequireInsideCode
  • Where: plugins/os-sdlc/lib/os_sdlc_loader.rb:16
  • Session: fe2e7204-cf6e-4241-b776-f9c5f032f4a9
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/fe2e7204-cf6e-4241-b776-f9c5f032f4a9.jsonl

VERDICT: CREATE

Related: #1367 (being rewritten into Sdlc/RequireInsideCode).

## Problem `plugins/os-sdlc/lib/os_sdlc_loader.rb:16` runs `loader.ignore(File.join(os_sdlc_dir, "cops"))`. No loader autoloads `RuboCop::Cop::Sdlc::*`. Each file that needs a cop must name it with `require_relative`. Today: - Each of the 48 cop tests in `plugins/os-sdlc/tests/cops/` carries `require_relative "../../lib/os_sdlc/cops/<cop>"`. 19 of them wrap it in `begin ... rescue LoadError; nil; end`, which hides a missing file. - `plugins/os-sdlc/.rubocop.yml` has a `require:` list of 39 per-cop entries. - Files in `cops/` carry about 130 top-level `require_relative` calls to their helpers. - 7 `detector/` files require cop files: `detector/reach_through.rb:3`, `holders.rb:3-4`, `inward_edges.rb:3-4`, `error_lineage/rescue_index.rb:3`, `effect_sites.rb:3`. `detector/cop_sites.rb:4` requires `cops/cop_source`. ## Reproduce n/a: design change. Count with `grep -rc 'require_relative.*cops/' plugins/os-sdlc/tests/cops`. ## Approach Investigation, then change. Untested sketch: add a second Zeitwerk loader for `cops/` with `namespace: RuboCop::Cop::Sdlc`. Require it once from `test_helper` and as the single `.rubocop.yml` `require` entry. Decided 2026-10-26: the cop namespaces are mixed (Structural 128 files, Dry 18, Minitest 7, Sequel 6, plain `Sdlc` 5) and the flat `cops/` paths do not name them. #1397 moves the files into department folders (`cops/structural/`, `cops/dry/`, ...) so each path names its constant; this ticket starts after #1397. The loader lives in a new `lib/os_sdlc_cops_loader.rb` beside `lib/os_sdlc_loader.rb`, outside the `OsSdlc` loader's root. Cops register on class definition, so the loader calls `eager_load` under rubocop. Required work: 1. Prove the loader registers every Sdlc cop under rubocop (`rubocop --show-cops | grep Sdlc/` count unchanged). 2. Remove the per-cop `require_relative` lines from cop tests, including the 19 `rescue LoadError` blocks. 3. Collapse the `.rubocop.yml` `require:` list to one entry. 4. Remove the `cops/` and `detector/` `require_relative` calls that the loader makes redundant. A `detector/` file that needs a cop requires `os_sdlc_cops_loader` once in place of its per-cop lines. 5. Keep the 8 sweep-tier cops off in the default config. A Zeitwerk eager_load registers 48 Sdlc cops; `.rubocop.yml` requires 40 today, and the 8 others are the sweep-tier cops that only `.rubocop-sweep.yml` requires: `Sdlc/Dry/RepeatedPathLiteral`, `Sdlc/Structural/EffectInModel`, `ForwardingClass`, `InwardDependency`, `ReachThroughChain`, `RescueSite`, `UnderscoredParameter`, `UnlayeredConstant`. Set `Enabled: false` for each in `.rubocop.yml`, with no `*Path` parameter (`lint/worklist_rubocop_config.rb` rewrites `*Path` parameters per layer). Collapse the `.rubocop-sweep.yml` `require:` list to the loader entry and keep its `Enabled: true` entries; `lint/sweeping.rb` layers the sweep config after `.rubocop.yml`, so its `Enabled: true` must win. ## Acceptance criteria 1. Given the full os-sdlc suite, when it runs, then it passes with zero `require_relative ".../cops/..."` lines under `plugins/os-sdlc/tests/cops/`. 2. Given `rubocop --show-cops` with `.rubocop.yml`, when the Sdlc cops whose `Enabled` is not false are listed, then the list equals the list before the change (38 cops on 2026-10-26; `--show-cops` also lists disabled cops, so count enabled ones). 3. Given `.rubocop-sweep.yml` layered after `.rubocop.yml`, as `lint/sweeping.rb` runs it, when the enabled Sdlc cops are listed, then each of the 8 sweep-tier cops is enabled. (Corrected 2026-10-26 during implementation 169: `lint/sweeping.rb` is the only code consumer of the sweep config, and it layers the file and passes `only:`; the standalone `-c .rubocop-sweep.yml` count is not an interface.) ## Owner sketch ```ruby # lib/os_sdlc_cops_loader.rb module OsSdlc def self.cops_loader end # Zeitwerk::Loader: push_dir(lib/os_sdlc/cops, namespace: RuboCop::Cop::Sdlc); setup; eager_load ``` ## Blocking edges Blocked by #1397. ## Origin - Trigger: Jared approved this ticket as "A2" in the session. - Improvised this session: none - Chain: per-cop `require_relative` in every test ← `loader.ignore` of `cops/` in `plugins/os-sdlc/lib/os_sdlc_loader.rb:16` ← DESIGN (loader file, line 16) - Root candidate: none found; searched os_sdlc_loader, loader.ignore, cops/ zeitwerk, require_relative cops, .rubocop.yml require, RequireInsideCode - Where: plugins/os-sdlc/lib/os_sdlc_loader.rb:16 - Session: fe2e7204-cf6e-4241-b776-f9c5f032f4a9 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/fe2e7204-cf6e-4241-b776-f9c5f032f4a9.jsonl VERDICT: CREATE Related: #1367 (being rewritten into Sdlc/RequireInsideCode).
Author
Owner

Work started on branch ticket-1393 through /os-sdlc:implement (poodr-ticket-implementation).

Work started on branch ticket-1393 through /os-sdlc:implement (poodr-ticket-implementation).
Author
Owner

Blocked by #1397. The flat cops/ layout cannot map to RuboCop::Cop::Sdlc under Zeitwerk; Jared chose to move cop files into department folders first (#1397, fast-batch). Owner sketch and blocking edge added to the body. Implement run not opened; branch ticket-1393 removed with no changes.

Blocked by #1397. The flat cops/ layout cannot map to RuboCop::Cop::Sdlc under Zeitwerk; Jared chose to move cop files into department folders first (#1397, fast-batch). Owner sketch and blocking edge added to the body. Implement run not opened; branch ticket-1393 removed with no changes.
Author
Owner

Fact for the loader (measured 2026-10-26 in fast-batch-16): rubocop --show-cops lists 40 Sdlc cops today, but a Zeitwerk eager_load of all of cops/ registers 48. .rubocop.yml does not require 8 cop files. A loader that eager-loads every file registers those 8, and RuboCop enables a cop that has no config, so lint results change. Acceptance criterion 2 (count unchanged) catches this. The run must either disable the 8 in .rubocop.yml or make a decision to enable them.

Fact for the loader (measured 2026-10-26 in fast-batch-16): rubocop --show-cops lists 40 Sdlc cops today, but a Zeitwerk eager_load of all of cops/ registers 48. .rubocop.yml does not require 8 cop files. A loader that eager-loads every file registers those 8, and RuboCop enables a cop that has no config, so lint results change. Acceptance criterion 2 (count unchanged) catches this. The run must either disable the 8 in .rubocop.yml or make a decision to enable them.
Author
Owner

Work resumed on branch ticket-1393 through /os-sdlc:implement (poodr-ticket-implementation), after #1397 merged.

Work resumed on branch ticket-1393 through /os-sdlc:implement (poodr-ticket-implementation), after #1397 merged.
Author
Owner

Resolution

Done: OsSdlc.cops_loader (lib/os_sdlc_cops_loader.rb) eager-loads cops/ under RuboCop::Cop::Sdlc. .rubocop.yml and .rubocop-sweep.yml each require only the loader; the 8 sweep-tier cops are disabled in .rubocop.yml. test_helper and detector.rb require the loader once. Per-cop require_relative lines and 19 rescue LoadError blocks removed from tests/cops, tests/support, tests/detector, cops/ and detector/.

Evidence: main 2248b680 (implementation 169, hand-finished after escalations 77 and 78). Full suite 2675 runs 0 failures; rubocop offenses identical to main (22 pre-existing, 0 new); --show-cops: 38 Sdlc cops enabled before and after; 0 require_relative to cops/ under tests/cops; new test fails when eager_load is removed; design audit: 8 findings fixed, 3 answered.

Follow-ups: jared/decision-model#10 (copied cop require: list; replace with the loader entry). Suite wall time is over its 60 s budget on main and on this branch; measurements on a loaded machine were inconclusive (main 66-78 s, branch 85-87 s); none filed. Redundant require "rubocop" lines in about 15 cop files: dropped (harmless).

## Resolution **Done:** OsSdlc.cops_loader (lib/os_sdlc_cops_loader.rb) eager-loads cops/ under RuboCop::Cop::Sdlc. .rubocop.yml and .rubocop-sweep.yml each require only the loader; the 8 sweep-tier cops are disabled in .rubocop.yml. test_helper and detector.rb require the loader once. Per-cop require_relative lines and 19 rescue LoadError blocks removed from tests/cops, tests/support, tests/detector, cops/ and detector/. **Evidence:** main 2248b680 (implementation 169, hand-finished after escalations 77 and 78). Full suite 2675 runs 0 failures; rubocop offenses identical to main (22 pre-existing, 0 new); --show-cops: 38 Sdlc cops enabled before and after; 0 require_relative to cops/ under tests/cops; new test fails when eager_load is removed; design audit: 8 findings fixed, 3 answered. **Follow-ups:** jared/decision-model#10 (copied cop require: list; replace with the loader entry). Suite wall time is over its 60 s budget on main and on this branch; measurements on a loaded machine were inconclusive (main 66-78 s, branch 85-87 s); none filed. Redundant require "rubocop" lines in about 15 cop files: dropped (harmless).
jared closed this issue 2026-10-26 15:46:18 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1393
No description provided.