Route every os-sdlc rubocop call through one lint entry point #1319

Closed
opened 2026-10-25 16:44:40 +00:00 by jared · 2 comments
Owner

Problem

Eight sites in os-sdlc start rubocop (a ninth, gate/unchanged_check/measurement.rb, is listed under Observed). Each site chooses its own config. Four sites use the layered config (os-sdlc .rubocop.yml first, then the target config). Four sites do not.
The implement gate does not use the os-sdlc config. So the implement gate on os-backlog uses only the os-backlog .rubocop.yml (DisabledByDefault, no Sdlc cops). The gate on os, os-adr, os-aidd-lint, and os-me uses the cc-os root .rubocop.yml (stock cops, no Sdlc cops). All five are targets in .sdlc/project.yaml.
ADR-0298 (commit 09da348a) says os-sdlc layers its rubocop config at run time and installs nothing into target repos. The code does not yet do this at every site.

Observed

Sites that use the layered config: plugins/os-sdlc/lib/os_sdlc/lint.rb:11, lint/worklist/rubocop_run.rb:19, lint/sweeping.rb:29, fast_gate/cop_check.rb:28.
Sites that do not: gate/commands.rb:85 (implement gate lint and test-lint), fast_gate/new_offense_check.rb via fast_gate/rubocop.rb, lint/autocorrection/rubocop_run.rb:16 (rubocop -a). detector/cop_sites.rb runs in-process with its own config.
A ninth site that does not use the layered config: gate/unchanged_check/measurement.rb:31 (os-sdlc-unchanged-check counts offenses with only the target lint.config).
fast_gate/cop_check.rb:28 and fast_gate/new_offense_check.rb both start rubocop through one runner, fast_gate/rubocop.rb:14. cop_check passes the layered --config; new_offense_check passes none. lint.rb:11 is the command constant; the run is at lint.rb:30.
lint/worklist/listing.rb:34 prints a rubocop -a --only ... hint for the human. It starts no process and is not a call site.
What the sites need from the entry point: a file list; the target root; lint_config; extra config layers (sweeping.rb and cop_check.rb add .rubocop-sweep.yml); an --only cop list (sweeping, cop_check); JSON or clang output (lint.rb and sweeping.rb print clang); --stdin source for base-commit offenses (new_offense_check); autocorrect -a (autocorrect prepass); the procman rubocop server (implement gate).
The gate command at gate/commands.rb:85:

procman with rubocop --root <root> -- rubocop --server --format json <files>

Evidence: the layered config run on the pre-fix os-backlog file (git show 7c228093:plugins/os-backlog/lib/backlog/cli_command_wiring.rb, 279 lines) fires Sdlc/Structural/ModuleOwnsInstanceState and ParameterThreadedThroughPrivateMethods. The gate did not report them.
plugins/os-backlog/.rubocop.yml has a comment that says Sdlc require: paths crash rubocop there. That is true only for a plain rubocop run. It is not true for the layered config.

Reproduce

n/a: design gap. Read gate/commands.rb:85 and compare it with lint.rb:11.

Approach

Build one lint entry point. One object (for example OsSdlc::Lint::Run) and one CLI (for example os-sdlc-lint) own config resolution, rubocop invocation, and result parsing. All eight sites call it, except detector/cop_sites.rb (see Non-goals), and gate/unchanged_check/measurement.rb also. Use the existing chain builder OsSdlc::Lint::WorklistRubocopConfig (lib/os_sdlc/lint/worklist_rubocop_config.rb) for config resolution; it already raises HarnessError for a missing lint_config.
Keep these constraints. ADR-0219: gate rubocop runs through the procman-owned rubocop server. ADR-0179: a nil lint_config means the target .rubocop.yml, and a configured path that is missing raises HarnessError. ADR-0082: one pass, and a missing gem fails loudly and names the gem.
Layering order: os-sdlc plugin .rubocop.yml first, then the target .rubocop.yml or lint_config. Target settings win.
The implement gate must fail only on new offenses against the base commit, as the fast gate new-offense check does. Old offenses in newly covered plugins then do not block unrelated work.

Required work:

  1. Add the lint entry point object that resolves config, runs rubocop, and parses the result — check: present class Run in plugins/os-sdlc/lib/os_sdlc/lint/run.rb
  2. Add the os-sdlc-lint CLI that calls the entry point — check: present OsSdlc::Lint::Run in plugins/os-sdlc/bin/os-sdlc-lint
  3. Change the implement gate (gate/commands.rb), the fast-gate new-offense check, and the autocorrect prepass to call the entry point.
  4. Change lint.rb, lint/worklist/rubocop_run.rb, lint/sweeping.rb, and fast_gate/cop_check.rb to call the entry point.
  5. Make the implement gate fail on new offenses against the base commit. Gate::Commands has no base-source reader today; FastGate has one (fast_gate/new_offense_check.rb:13, :26) — reuse that mechanism.
  6. Delete the crash comment in plugins/os-backlog/.rubocop.yml — check: absent crash in plugins/os-backlog/.rubocop.yml
  7. Change gate/unchanged_check/measurement.rb to call the entry point — check: absent rubocop #{config_flag} in plugins/os-sdlc/lib/os_sdlc/gate/unchanged_check/measurement.rb

Acceptance criteria

  1. Given an Sdlc/* offense planted in a fixture file under plugins/os-backlog, when the implement gate lint runs on that file, then the gate reports the Sdlc/* offense.
  2. Given the same planted offense in a file under plugins/os, when the fast-gate new-offense check runs, then it reports the offense.
  3. Given an offense that already exists at the base commit in a file that the change edits, when the implement gate runs, then the gate does not fail on that offense.
  4. Given a gate run, when rubocop starts, then it runs through the procman-owned rubocop server.
  5. Given a configured lint_config path that does not exist, when the entry point runs, then it raises HarnessError.
  6. Given the plugins/os-sdlc/lib tree, when it is searched for code that starts a rubocop process (Open3, ProjectShell, a gate command string that runs rubocop) or runs an in-process RuboCop::Cop::Commissioner, then none exists outside the entry point and detector/cop_sites.rb.

Owner sketch

class OsSdlc::Lint::Run
  def call(files:, root:)
end

Callers after the change: Gate::Commands#rubocop_command_for, FastGate::Rubocop#offenses, Lint::Autocorrection::RubocopRun#call, Lint#call, Lint::Worklist::RubocopRun.fetch, Lint::Sweeping#report, Gate::UnchangedCheck::Measurement#offense_count. Config chain: Lint::WorklistRubocopConfig#write. New CLI: plugins/os-sdlc/bin/os-sdlc-lint.

Notes

OsSdlc::Lint#call has no library sender today (only tests/lint_test.rb constructs it); change it as listed in item 4 regardless.

Non-goals

Fixing the offenses that the layered config finds in newly covered plugins.
Routing detector/cop_sites.rb through the entry point. It is an in-process measurement: it runs selected cops with config that the code supplies and reads each cop's findings, which rubocop JSON output does not carry.

Follow-up after ship (human, not required work): run lint-sweep per plugin and post the counts on #1317.

Origin

  • Trigger: Fixing an os-backlog ClassLength offense showed that the implement gate ran no Sdlc cops on os-backlog.
  • Improvised this session: none
  • Chain: Sdlc cops did not run on plugins/os-backlog in the implement gate ← gate/commands.rb:85 starts rubocop with no os-sdlc config ← DESIGN (ADR-0298; each of the eight rubocop sites chooses its own config)
  • Root candidate: none found; searched rubocop, lint, ADR-0298, layered config, lint entry point, os-sdlc-lint, Sdlc cops, rubocop-base, gate/commands.rb
    VERDICT: CREATE
  • Where: plugins/os-sdlc/lib/os_sdlc/gate/commands.rb
  • Session: f4187368-5e7e-459d-80c6-ddf7620389cd
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/f4187368-5e7e-459d-80c6-ddf7620389cd.jsonl
## Problem Eight sites in os-sdlc start rubocop (a ninth, gate/unchanged_check/measurement.rb, is listed under Observed). Each site chooses its own config. Four sites use the layered config (os-sdlc .rubocop.yml first, then the target config). Four sites do not. The implement gate does not use the os-sdlc config. So the implement gate on os-backlog uses only the os-backlog .rubocop.yml (DisabledByDefault, no Sdlc cops). The gate on os, os-adr, os-aidd-lint, and os-me uses the cc-os root .rubocop.yml (stock cops, no Sdlc cops). All five are targets in .sdlc/project.yaml. ADR-0298 (commit 09da348a) says os-sdlc layers its rubocop config at run time and installs nothing into target repos. The code does not yet do this at every site. ## Observed Sites that use the layered config: plugins/os-sdlc/lib/os_sdlc/lint.rb:11, lint/worklist/rubocop_run.rb:19, lint/sweeping.rb:29, fast_gate/cop_check.rb:28. Sites that do not: gate/commands.rb:85 (implement gate lint and test-lint), fast_gate/new_offense_check.rb via fast_gate/rubocop.rb, lint/autocorrection/rubocop_run.rb:16 (`rubocop -a`). detector/cop_sites.rb runs in-process with its own config. A ninth site that does not use the layered config: gate/unchanged_check/measurement.rb:31 (`os-sdlc-unchanged-check` counts offenses with only the target `lint.config`). fast_gate/cop_check.rb:28 and fast_gate/new_offense_check.rb both start rubocop through one runner, fast_gate/rubocop.rb:14. cop_check passes the layered `--config`; new_offense_check passes none. lint.rb:11 is the command constant; the run is at lint.rb:30. lint/worklist/listing.rb:34 prints a `rubocop -a --only ...` hint for the human. It starts no process and is not a call site. What the sites need from the entry point: a file list; the target root; lint_config; extra config layers (sweeping.rb and cop_check.rb add `.rubocop-sweep.yml`); an `--only` cop list (sweeping, cop_check); JSON or clang output (lint.rb and sweeping.rb print clang); `--stdin` source for base-commit offenses (new_offense_check); autocorrect `-a` (autocorrect prepass); the procman rubocop server (implement gate). The gate command at gate/commands.rb:85: ``` procman with rubocop --root <root> -- rubocop --server --format json <files> ``` Evidence: the layered config run on the pre-fix os-backlog file (`git show 7c228093:plugins/os-backlog/lib/backlog/cli_command_wiring.rb`, 279 lines) fires Sdlc/Structural/ModuleOwnsInstanceState and ParameterThreadedThroughPrivateMethods. The gate did not report them. plugins/os-backlog/.rubocop.yml has a comment that says Sdlc `require:` paths crash rubocop there. That is true only for a plain rubocop run. It is not true for the layered config. ## Reproduce n/a: design gap. Read gate/commands.rb:85 and compare it with lint.rb:11. ## Approach Build one lint entry point. One object (for example OsSdlc::Lint::Run) and one CLI (for example os-sdlc-lint) own config resolution, rubocop invocation, and result parsing. All eight sites call it, except detector/cop_sites.rb (see Non-goals), and gate/unchanged_check/measurement.rb also. Use the existing chain builder `OsSdlc::Lint::WorklistRubocopConfig` (lib/os_sdlc/lint/worklist_rubocop_config.rb) for config resolution; it already raises HarnessError for a missing lint_config. Keep these constraints. ADR-0219: gate rubocop runs through the procman-owned rubocop server. ADR-0179: a nil lint_config means the target .rubocop.yml, and a configured path that is missing raises HarnessError. ADR-0082: one pass, and a missing gem fails loudly and names the gem. Layering order: os-sdlc plugin .rubocop.yml first, then the target .rubocop.yml or lint_config. Target settings win. The implement gate must fail only on new offenses against the base commit, as the fast gate new-offense check does. Old offenses in newly covered plugins then do not block unrelated work. Required work: 1. Add the lint entry point object that resolves config, runs rubocop, and parses the result — check: present `class Run` in `plugins/os-sdlc/lib/os_sdlc/lint/run.rb` 2. Add the os-sdlc-lint CLI that calls the entry point — check: present `OsSdlc::Lint::Run` in `plugins/os-sdlc/bin/os-sdlc-lint` 3. Change the implement gate (gate/commands.rb), the fast-gate new-offense check, and the autocorrect prepass to call the entry point. 4. Change lint.rb, lint/worklist/rubocop_run.rb, lint/sweeping.rb, and fast_gate/cop_check.rb to call the entry point. 5. Make the implement gate fail on new offenses against the base commit. Gate::Commands has no base-source reader today; FastGate has one (fast_gate/new_offense_check.rb:13, :26) — reuse that mechanism. 6. Delete the crash comment in plugins/os-backlog/.rubocop.yml — check: absent `crash` in `plugins/os-backlog/.rubocop.yml` 7. Change gate/unchanged_check/measurement.rb to call the entry point — check: absent `rubocop #{config_flag}` in `plugins/os-sdlc/lib/os_sdlc/gate/unchanged_check/measurement.rb` ## Acceptance criteria 1. Given an Sdlc/* offense planted in a fixture file under plugins/os-backlog, when the implement gate lint runs on that file, then the gate reports the Sdlc/* offense. 2. Given the same planted offense in a file under plugins/os, when the fast-gate new-offense check runs, then it reports the offense. 3. Given an offense that already exists at the base commit in a file that the change edits, when the implement gate runs, then the gate does not fail on that offense. 4. Given a gate run, when rubocop starts, then it runs through the procman-owned rubocop server. 5. Given a configured lint_config path that does not exist, when the entry point runs, then it raises HarnessError. 6. Given the plugins/os-sdlc/lib tree, when it is searched for code that starts a rubocop process (Open3, ProjectShell, a gate command string that runs `rubocop`) or runs an in-process `RuboCop::Cop::Commissioner`, then none exists outside the entry point and detector/cop_sites.rb. ## Owner sketch ```ruby class OsSdlc::Lint::Run def call(files:, root:) end ``` Callers after the change: Gate::Commands#rubocop_command_for, FastGate::Rubocop#offenses, Lint::Autocorrection::RubocopRun#call, Lint#call, Lint::Worklist::RubocopRun.fetch, Lint::Sweeping#report, Gate::UnchangedCheck::Measurement#offense_count. Config chain: Lint::WorklistRubocopConfig#write. New CLI: plugins/os-sdlc/bin/os-sdlc-lint. ## Notes `OsSdlc::Lint#call` has no library sender today (only tests/lint_test.rb constructs it); change it as listed in item 4 regardless. ## Non-goals Fixing the offenses that the layered config finds in newly covered plugins. Routing detector/cop_sites.rb through the entry point. It is an in-process measurement: it runs selected cops with config that the code supplies and reads each cop's `findings`, which rubocop JSON output does not carry. Follow-up after ship (human, not required work): run lint-sweep per plugin and post the counts on #1317. ## Origin - Trigger: Fixing an os-backlog ClassLength offense showed that the implement gate ran no Sdlc cops on os-backlog. - Improvised this session: none - Chain: Sdlc cops did not run on plugins/os-backlog in the implement gate ← gate/commands.rb:85 starts rubocop with no os-sdlc config ← DESIGN (ADR-0298; each of the eight rubocop sites chooses its own config) - Root candidate: none found; searched rubocop, lint, ADR-0298, layered config, lint entry point, os-sdlc-lint, Sdlc cops, rubocop-base, gate/commands.rb VERDICT: CREATE - Where: plugins/os-sdlc/lib/os_sdlc/gate/commands.rb - Session: f4187368-5e7e-459d-80c6-ddf7620389cd - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/f4187368-5e7e-459d-80c6-ddf7620389cd.jsonl
Author
Owner

Work started on branch ticket-1319 (os-sdlc implement, AFK run approved 2026-10-25).

Work started on branch ticket-1319 (os-sdlc implement, AFK run approved 2026-10-25).
Author
Owner

Resolution

Done: Every os-sdlc rubocop call now goes through OsSdlc::Lint::Run with the ADR-0298 run-time layered config: fast gate, sweep, worklist, autocorrect prepass, unchanged-check measurement, Lint#call. bin/os-sdlc-lint runs FastGate::NewOffenseReport, so the implement gate fails only on offenses new against the base branch. os-backlog target names its own .rubocop.yml. detector/cop_sites.rb out of scope per Non-goals.

Evidence: main 94d682c0 (969c4c45, 0d37643f). os-sdlc suite 2550 runs, 0 failures; os-backlog suite 327 runs, 0 failures; rubocop 30 files no offenses; eager load ok. All ACs and required-work checks pass. Implementation 161 escalated twice (73: unchecked R2, ticket fixed and rerouted; 74: bound exhausted, AC 6 a search check vs pinned tests); hand-finished with one design audit, its two must-fix findings applied.

Follow-ups: #1339 (pipeline routing gaps, skeptic CREATE); #1340 (unchecked required work reached pipeline, skeptic CREATE); #1341 (suite wall time over budget, skeptic CREATE); occurrence on #1096 (worktree guard false positive, skeptic OCCURRENCE). Dropped: configurable base branch (no target needs it yet); Lint#call has no library caller (CRC pass will rule).

## Resolution **Done:** Every os-sdlc rubocop call now goes through OsSdlc::Lint::Run with the ADR-0298 run-time layered config: fast gate, sweep, worklist, autocorrect prepass, unchanged-check measurement, Lint#call. bin/os-sdlc-lint runs FastGate::NewOffenseReport, so the implement gate fails only on offenses new against the base branch. os-backlog target names its own .rubocop.yml. detector/cop_sites.rb out of scope per Non-goals. **Evidence:** main 94d682c0 (969c4c45, 0d37643f). os-sdlc suite 2550 runs, 0 failures; os-backlog suite 327 runs, 0 failures; rubocop 30 files no offenses; eager load ok. All ACs and required-work checks pass. Implementation 161 escalated twice (73: unchecked R2, ticket fixed and rerouted; 74: bound exhausted, AC 6 a search check vs pinned tests); hand-finished with one design audit, its two must-fix findings applied. **Follow-ups:** #1339 (pipeline routing gaps, skeptic CREATE); #1340 (unchecked required work reached pipeline, skeptic CREATE); #1341 (suite wall time over budget, skeptic CREATE); occurrence on #1096 (worktree guard false positive, skeptic OCCURRENCE). Dropped: configurable base branch (no target needs it yet); Lint#call has no library caller (CRC pass will rule).
jared closed this issue 2026-10-25 20:07:57 +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#1319
No description provided.