os-sdlc: move Metrics/ClassLength and Metrics/ModuleLength to the sweep tier with zero Excludes, drain, promote #1317

Open
opened 2026-10-25 14:32:00 +00:00 by jared · 2 comments
Owner

Problem

plugins/os-sdlc/.rubocop.yml excludes every test directory from Metrics/ClassLength and does not enable Metrics/ModuleLength. The exclusion is a file list, and a file list is a baseline: it hides new offenses in test classes and rots as files move. The missing module cop leaves open the cheapest wrong fix for a long class: move the method bodies into a module that only that class includes. Session 345203e9 took that path in os-backlog, and the gate passed. The rulings "code is code; no test-file Exclude without shown degradation" and "no cop Exclude lists" both apply.

Observed

A probe on 2026-10-25 ran rubocop over plugins/os-sdlc with Metrics/ClassLength: Exclude: [] and Metrics/ModuleLength: Max: 100.

bundle exec rubocop -c <probe.yml> --only Metrics/ClassLength,Metrics/ModuleLength
1701 files inspected, 57 offenses detected

53 Metrics/ClassLength hits, all in test classes, none in lib. The six largest test classes count 536, 504, 364, 353, 331, and 324 lines.
4 Metrics/ModuleLength hits, all in lib:

lib/os_sdlc/db.rb:7:3: Module has too many lines. [334/100]
lib/os_sdlc/boot/inputs/usage_text.rb:5:5: Module has too many lines. [159/100]
lib/os_sdlc/boot/inputs.rb:7:5: Module has too many lines. [138/100]
lib/os_sdlc/boot/commands.rb:5:5: Module has too many lines. [136/100]

Reproduce

printf 'inherit_from: plugins/os-sdlc/.rubocop.yml\nMetrics/ClassLength:\n  Exclude: []\nMetrics/ModuleLength:\n  Enabled: true\n  Max: 100\n' > /tmp/probe.yml
cd plugins/os-sdlc && bundle exec rubocop -c /tmp/probe.yml --only Metrics/ClassLength,Metrics/ModuleLength --format emacs

Approach

Register both cops in the sweep tier with zero Excludes, drain the hits in batches through the fast path, then promote both cops to the gate and delete the Exclude list. The lesson plugins/os-sdlc/lint-lessons/metrics-class-length.yml (commit a830d451) gives the correction for the four modules: count a single-includer module with its class, build each repeated collaborator once, give the repeated shape one owner. The test classes follow the Metrics/AbcSize lesson shape: one helper that returns the result, one test per outcome, and a split by fixture family when one class still holds two subjects.

Required work:

  1. Metrics/ClassLength with Exclude: [] and Metrics/ModuleLength with Max: 100 registered in the sweep config — check: present Metrics/ModuleLength in plugins/os-sdlc/.rubocop-sweep.yml
  2. The four lib modules pass Metrics/ModuleLength at 100 through /os-sdlc:fast-batch, one finding each.
  3. The 53 test classes pass Metrics/ClassLength at 100, in fast-batch rounds of about eight files, largest first.
  4. Both cops promoted to the gate and the Exclude: list removed — check: absent ../*/tests/**/* in plugins/os-sdlc/.rubocop.yml
  5. Metrics/ModuleLength enabled in plugins/os-sdlc/templates/rubocop-base.yml at the same Max as Metrics/ClassLength there — check: present Metrics/ModuleLength in plugins/os-sdlc/templates/rubocop-base.yml

Orchestrator work: open one fast-batch per round; record each round in the "What runs measured" table of docs/sdlc-fast-path.md.

Rejected: keep the test Exclude because tests are long by nature — rejected by the 2026-10-15 ruling; a long test class is an Eager Test or two subjects in one file, and both corrections make the test better.
Rejected: a custom cop that sums a class with its single-includer modules — not needed until the stock module cop fails to catch a case.

Acceptance criteria

  1. Given the sweep config, when bin/lint-sweep runs, then it reports Metrics/ClassLength and Metrics/ModuleLength counts with no Exclude entries.
  2. Given plugins/os-sdlc, when rubocop runs with the gate config, then Metrics/ClassLength and Metrics/ModuleLength both report zero offenses in lib and tests.
  3. Given plugins/os-sdlc/.rubocop.yml, when read, then Metrics/ClassLength has no Exclude: key.

Non-goals

The os-backlog side: cli_command_wiring.rb and forgejo_backend.rb are in fast-batch-14, and plugins/os-backlog/.rubocop.yml already enables Metrics/ModuleLength (commit a830d451). The two long os-backlog test classes (tests/checkout_test.rb 145, tests/close_protocol_test.rb 161) are not covered by this ticket; os-backlog has no sweep tier.

Origin

  • Trigger: /os-sdlc:smell-analysis Metrics/ClassLength after session 345203e9 hit the cop on plugins/os-backlog/lib/backlog/cli.rb [108/100] with no lesson
  • Improvised this session: none in os-sdlc; session 345203e9 moved three command bodies into cli_command_wiring.rb to pass the class cop
  • Chain: gate passes a 229-line single-includer module ← Metrics/ModuleLength not enabled, DisabledByDefault: true in plugins/os-backlog/.rubocop.yml (source: the file) ← os-sdlc's own config excludes tests from ClassLength and has no ModuleLength (source: plugins/os-sdlc/.rubocop.yml:70-79) ← DESIGN: no ADR covers the metrics cops; /os-adr:find on 2026-10-25 found ADR-0082 (layered config, informs only) and ADR-0066 (base plus delta, informs only)
  • Root candidate: none found; searched ClassLength, ModuleLength, test Exclude, sweep tier, lint-sweep, rubocop Exclude list, long test classes
  • VERDICT: CREATE
  • Where: plugins/os-sdlc/.rubocop.yml Metrics/ClassLength Exclude: list, lines 70-79
  • Session: b0b47602-97a4-4f51-a9b8-54b228fc3ed3
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/b0b47602-97a4-4f51-a9b8-54b228fc3ed3.jsonl
## Problem `plugins/os-sdlc/.rubocop.yml` excludes every test directory from `Metrics/ClassLength` and does not enable `Metrics/ModuleLength`. The exclusion is a file list, and a file list is a baseline: it hides new offenses in test classes and rots as files move. The missing module cop leaves open the cheapest wrong fix for a long class: move the method bodies into a module that only that class includes. Session 345203e9 took that path in os-backlog, and the gate passed. The rulings "code is code; no test-file Exclude without shown degradation" and "no cop Exclude lists" both apply. ## Observed A probe on 2026-10-25 ran rubocop over `plugins/os-sdlc` with `Metrics/ClassLength: Exclude: []` and `Metrics/ModuleLength: Max: 100`. ``` bundle exec rubocop -c <probe.yml> --only Metrics/ClassLength,Metrics/ModuleLength ``` ``` 1701 files inspected, 57 offenses detected ``` 53 `Metrics/ClassLength` hits, all in test classes, none in lib. The six largest test classes count 536, 504, 364, 353, 331, and 324 lines. 4 `Metrics/ModuleLength` hits, all in lib: ``` lib/os_sdlc/db.rb:7:3: Module has too many lines. [334/100] lib/os_sdlc/boot/inputs/usage_text.rb:5:5: Module has too many lines. [159/100] lib/os_sdlc/boot/inputs.rb:7:5: Module has too many lines. [138/100] lib/os_sdlc/boot/commands.rb:5:5: Module has too many lines. [136/100] ``` ## Reproduce ``` printf 'inherit_from: plugins/os-sdlc/.rubocop.yml\nMetrics/ClassLength:\n Exclude: []\nMetrics/ModuleLength:\n Enabled: true\n Max: 100\n' > /tmp/probe.yml cd plugins/os-sdlc && bundle exec rubocop -c /tmp/probe.yml --only Metrics/ClassLength,Metrics/ModuleLength --format emacs ``` ## Approach Register both cops in the sweep tier with zero Excludes, drain the hits in batches through the fast path, then promote both cops to the gate and delete the Exclude list. The lesson `plugins/os-sdlc/lint-lessons/metrics-class-length.yml` (commit a830d451) gives the correction for the four modules: count a single-includer module with its class, build each repeated collaborator once, give the repeated shape one owner. The test classes follow the `Metrics/AbcSize` lesson shape: one helper that returns the result, one test per outcome, and a split by fixture family when one class still holds two subjects. Required work: 1. `Metrics/ClassLength` with `Exclude: []` and `Metrics/ModuleLength` with `Max: 100` registered in the sweep config — check: present `Metrics/ModuleLength` in `plugins/os-sdlc/.rubocop-sweep.yml` 2. The four lib modules pass `Metrics/ModuleLength` at 100 through `/os-sdlc:fast-batch`, one finding each. 3. The 53 test classes pass `Metrics/ClassLength` at 100, in fast-batch rounds of about eight files, largest first. 4. Both cops promoted to the gate and the `Exclude:` list removed — check: absent `../*/tests/**/*` in `plugins/os-sdlc/.rubocop.yml` 5. `Metrics/ModuleLength` enabled in `plugins/os-sdlc/templates/rubocop-base.yml` at the same `Max` as `Metrics/ClassLength` there — check: present `Metrics/ModuleLength` in `plugins/os-sdlc/templates/rubocop-base.yml` Orchestrator work: open one fast-batch per round; record each round in the "What runs measured" table of `docs/sdlc-fast-path.md`. Rejected: keep the test Exclude because tests are long by nature — rejected by the 2026-10-15 ruling; a long test class is an Eager Test or two subjects in one file, and both corrections make the test better. Rejected: a custom cop that sums a class with its single-includer modules — not needed until the stock module cop fails to catch a case. ## Acceptance criteria 1. Given the sweep config, when `bin/lint-sweep` runs, then it reports `Metrics/ClassLength` and `Metrics/ModuleLength` counts with no Exclude entries. 2. Given `plugins/os-sdlc`, when rubocop runs with the gate config, then `Metrics/ClassLength` and `Metrics/ModuleLength` both report zero offenses in lib and tests. 3. Given `plugins/os-sdlc/.rubocop.yml`, when read, then `Metrics/ClassLength` has no `Exclude:` key. ## Non-goals The os-backlog side: `cli_command_wiring.rb` and `forgejo_backend.rb` are in fast-batch-14, and `plugins/os-backlog/.rubocop.yml` already enables `Metrics/ModuleLength` (commit a830d451). The two long os-backlog test classes (`tests/checkout_test.rb` 145, `tests/close_protocol_test.rb` 161) are not covered by this ticket; os-backlog has no sweep tier. ## Origin - Trigger: `/os-sdlc:smell-analysis Metrics/ClassLength` after session 345203e9 hit the cop on `plugins/os-backlog/lib/backlog/cli.rb` [108/100] with no lesson - Improvised this session: none in os-sdlc; session 345203e9 moved three command bodies into `cli_command_wiring.rb` to pass the class cop - Chain: gate passes a 229-line single-includer module ← `Metrics/ModuleLength` not enabled, `DisabledByDefault: true` in `plugins/os-backlog/.rubocop.yml` (source: the file) ← os-sdlc's own config excludes tests from ClassLength and has no ModuleLength (source: `plugins/os-sdlc/.rubocop.yml:70-79`) ← DESIGN: no ADR covers the metrics cops; `/os-adr:find` on 2026-10-25 found ADR-0082 (layered config, informs only) and ADR-0066 (base plus delta, informs only) - Root candidate: none found; searched ClassLength, ModuleLength, test Exclude, sweep tier, lint-sweep, rubocop Exclude list, long test classes - VERDICT: CREATE - Where: `plugins/os-sdlc/.rubocop.yml` `Metrics/ClassLength` `Exclude:` list, lines 70-79 - Session: b0b47602-97a4-4f51-a9b8-54b228fc3ed3 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/b0b47602-97a4-4f51-a9b8-54b228fc3ed3.jsonl
Author
Owner

Sweep-tier counts for the five plugins that never ran under the Sdlc cops (HANDOFF.md:47, after #1319). Main SHA swept: 9c348bb3 (full 9c348bb3e2). No autocorrect.

Command, run from the repo root, once per plugin path:

plugins/os-sdlc/bin/lint-sweep plugins/<plugin>

.sdlc/project.yaml names these targets os-backlog, os, os-adr, os-aidd-lint, os-me; each has path: plugins/<name>. Only os-backlog sets lint_config; the other four have no lint_config target key, so the root .rubocop.yml applies. The path was passed directly.

Plugin Files Total Sdlc/* Stock Top cops
os-backlog 192 65 65 0 RescueSite 50, RepeatedPathLiteral 12, ReachThroughChain 2, UnderscoredParameter 1
os 60 18 18 0 RescueSite 10, RepeatedPathLiteral 8
os-adr 68 44 44 0 RescueSite 23, RepeatedPathLiteral 13, ReachThroughChain 8
os-aidd-lint 131 71 71 0 RepeatedPathLiteral 40, RescueSite 17, ReachThroughChain 13, ForwardingClass 1
os-me 211 79 79 0 RescueSite 63, ReachThroughChain 8, RepeatedPathLiteral 8
Total 662 277 277 0 RescueSite 163, RepeatedPathLiteral 81, ReachThroughChain 31, UnderscoredParameter 1, ForwardingClass 1

Cop names are Sdlc/Structural/* except Sdlc/Dry/RepeatedPathLiteral.

Metrics/ClassLength: 0 in all five (not measured). Metrics/ModuleLength: 0 in all five (not measured). lint-sweep runs only the cops listed in plugins/os-sdlc/.rubocop-sweep.yml, and that file has no Metrics entry yet (required work item 1 of this ticket). So the stock-cop column is 0 by construction, not by a clean result. After item 1 lands, re-run to get real Metrics counts.

Sweep-tier counts for the five plugins that never ran under the Sdlc cops (HANDOFF.md:47, after #1319). Main SHA swept: 9c348bb3 (full 9c348bb3e2af8b775f92308fdd818b889592cf45). No autocorrect. Command, run from the repo root, once per plugin path: plugins/os-sdlc/bin/lint-sweep plugins/<plugin> `.sdlc/project.yaml` names these targets `os-backlog`, `os`, `os-adr`, `os-aidd-lint`, `os-me`; each has `path: plugins/<name>`. Only `os-backlog` sets `lint_config`; the other four have no `lint_config` target key, so the root `.rubocop.yml` applies. The path was passed directly. | Plugin | Files | Total | Sdlc/* | Stock | Top cops | |---|---|---|---|---|---| | os-backlog | 192 | 65 | 65 | 0 | RescueSite 50, RepeatedPathLiteral 12, ReachThroughChain 2, UnderscoredParameter 1 | | os | 60 | 18 | 18 | 0 | RescueSite 10, RepeatedPathLiteral 8 | | os-adr | 68 | 44 | 44 | 0 | RescueSite 23, RepeatedPathLiteral 13, ReachThroughChain 8 | | os-aidd-lint | 131 | 71 | 71 | 0 | RepeatedPathLiteral 40, RescueSite 17, ReachThroughChain 13, ForwardingClass 1 | | os-me | 211 | 79 | 79 | 0 | RescueSite 63, ReachThroughChain 8, RepeatedPathLiteral 8 | | Total | 662 | 277 | 277 | 0 | RescueSite 163, RepeatedPathLiteral 81, ReachThroughChain 31, UnderscoredParameter 1, ForwardingClass 1 | Cop names are `Sdlc/Structural/*` except `Sdlc/Dry/RepeatedPathLiteral`. Metrics/ClassLength: 0 in all five (not measured). Metrics/ModuleLength: 0 in all five (not measured). `lint-sweep` runs only the cops listed in `plugins/os-sdlc/.rubocop-sweep.yml`, and that file has no Metrics entry yet (required work item 1 of this ticket). So the stock-cop column is 0 by construction, not by a clean result. After item 1 lands, re-run to get real Metrics counts.
Author
Owner

Sweep-tier recount after #1367 (RequireInMethod renamed to RequireInsideCode). Counted at main 63bc2ae83b (63bc2ae8). It is 5de58339 (includes #1367) plus one docs/lesson commit (#1367 lesson wording, 3 files). No autocorrect.

Command, from the repo root, once per plugin path (same as the 2026-10-25 count):

for p in os-backlog os os-adr os-aidd-lint os-me; do plugins/os-sdlc/bin/lint-sweep plugins/$p; done

Counts come from the emacs-style offense lines in the output, grouped by cop; files are distinct paths per cop. The sweep tier is the 8 cops in plugins/os-sdlc/.rubocop-sweep.yml.

Total: 277 offenses in 662 files (192 + 60 + 68 + 131 + 211). Same as the earlier 277; per-cop counts also match (163 / 81 / 31 / 1 / 1).

cop offenses files FP tickets
Sdlc/Structural/RescueSite 163 93 #1376
Sdlc/Dry/RepeatedPathLiteral 81 64 #1374
Sdlc/Structural/ReachThroughChain 31 22 #1378
Sdlc/Structural/UnderscoredParameter 1 1 #1370
Sdlc/Structural/ForwardingClass 1 1 #1360

Cops in the sweep tier with zero offenses: Sdlc/Structural/InwardDependency (FP #1366), Sdlc/Structural/UnlayeredConstant (FP #1359), Sdlc/Structural/EffectInModel (FP #1351).

Notes: Sdlc/RequireInsideCode is not in the sweep config, so it is not counted (its ticket is #1367, closed). Metrics/ClassLength and Metrics/ModuleLength are still not in the sweep config (required work item 1), so they are not measured.

Sweep-tier recount after #1367 (RequireInMethod renamed to RequireInsideCode). Counted at main 63bc2ae83bdb662ee55f72b55664ccc6ea4b201d (63bc2ae8). It is 5de58339 (includes #1367) plus one docs/lesson commit (#1367 lesson wording, 3 files). No autocorrect. Command, from the repo root, once per plugin path (same as the 2026-10-25 count): for p in os-backlog os os-adr os-aidd-lint os-me; do plugins/os-sdlc/bin/lint-sweep plugins/$p; done Counts come from the emacs-style offense lines in the output, grouped by cop; files are distinct paths per cop. The sweep tier is the 8 cops in `plugins/os-sdlc/.rubocop-sweep.yml`. Total: 277 offenses in 662 files (192 + 60 + 68 + 131 + 211). Same as the earlier 277; per-cop counts also match (163 / 81 / 31 / 1 / 1). | cop | offenses | files | FP tickets | |---|---|---|---| | Sdlc/Structural/RescueSite | 163 | 93 | #1376 | | Sdlc/Dry/RepeatedPathLiteral | 81 | 64 | #1374 | | Sdlc/Structural/ReachThroughChain | 31 | 22 | #1378 | | Sdlc/Structural/UnderscoredParameter | 1 | 1 | #1370 | | Sdlc/Structural/ForwardingClass | 1 | 1 | #1360 | Cops in the sweep tier with zero offenses: Sdlc/Structural/InwardDependency (FP #1366), Sdlc/Structural/UnlayeredConstant (FP #1359), Sdlc/Structural/EffectInModel (FP #1351). Notes: `Sdlc/RequireInsideCode` is not in the sweep config, so it is not counted (its ticket is #1367, closed). `Metrics/ClassLength` and `Metrics/ModuleLength` are still not in the sweep config (required work item 1), so they are not measured.
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#1317
No description provided.