Run rubocop --server through procman in lint gates (ADR-0219 slice 2) #713

Closed
opened 2026-09-25 15:01:03 +00:00 by jared · 4 comments
Owner

Problem

gate_commands.rb starts rubocop --server through a shell string and never stops it. rubocop keys its server by a digest of Gemfile.lock and .rubocop.yml, not by directory, and has no idle exit. On 2026-09-25 the machine held 509 orphaned rubocop servers rooted at deleted test tmpdirs.

Approach

The procman gem 0.2.0 already ships Procman::Kinds::Rubocop, which runs rubocop --start-server --no-detach under the procman supervisor with chdir root, and a procman with KIND --root DIR -- CMD CLI that runs CMD and releases the lease on exit. The lint gate command string changes to run rubocop through that CLI. procman is not on PATH today, so os-sdlc gets a bin/procman executable, which lives on PATH because ~/.claude/plugins/os-sdlc/bin does.

Required work:

  1. Add plugins/os-sdlc/bin/procman: require_relative "../lib/os_sdlc" (pins BUNDLE_GEMFILE and runs bundler/setup), then require "procman/cli" and Procman::CLI.start(ARGV), rescuing Procman::Error to warn its message and exit 1, following the shape of bin/lsp-probe.
  2. OsSdlc::Pipeline::GateCommands#rubocop_command_for returns procman with rubocop --root <project.path> -- rubocop --server --format json <files>. project.path is the same directory GateEvaluator chdirs into (both come from GateHandoff#project_root).
  3. One integration test behind skip unless ENV["PROCMAN_REAL"] == "1" runs the lint gate command string through Model::ProjectShell.capture3 for real against a fixture project with one Ruby file, asserts stdout parses as rubocop JSON with a files key, and asserts procman status rubocop --root <root> exits 0 afterwards.

Out of scope: lint.rb, rubocop_report_fetcher.rb, autocorrect_prepass.rb, and hook/lint_changed/autocorrect.rb run one-shot rubocop without --server; they start no server and stay as they are. Stopping servers whose root is gone is #714.

Acceptance criteria

  1. GateCommands#to_h for the lint and test-lint gates yields a command that starts with procman with rubocop --root followed by the project path and -- rubocop --server --format json, and yields nil when no Ruby files changed.
  2. plugins/os-sdlc/bin/procman list exits 0 from a shell whose cwd is a user project with its own Gemfile.
  3. The PROCMAN_REAL=1 integration test passes once by hand and is skipped in the default suite.
  4. Lint gate stdout is pure rubocop JSON; procman writes nothing to stdout around the child command.

Owner sketch

  • OsSdlc::Pipeline::GateCommands owns the lint command string; it reads project.path through its existing attr_reader :project.
  • plugins/os-sdlc/bin/procman owns PATH exposure of the gem CLI; no library code loads it.
  • Procman::CLI (gem) owns the lease, readiness, and release; cc-os adds no wrapper class.
  • Test: plugins/os-sdlc/tests/pipeline/gate_commands_rubocop_procman_test.rb for criterion 1; plugins/os-sdlc/tests/pipeline/lint_gate_procman_integration_test.rb for criterion 3.

Blocked by: #712 (closed).

VERDICT: CREATE — slice 2 of ADR-0219 with concrete provenance (509 orphaned servers), remaining required work items not yet shipped, criteria distinct from #712.

Origin

Session: ecfe6e11-0810-4431-88e5-8e24d34a239e
Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/ecfe6e11-
0810-4431-88e5-8e24d34a239e.jsonl
Where: plugins/os-sdlc/lib/os_sdlc/pipeline/gate_commands.rb
ADR: 0219

## Problem gate_commands.rb starts rubocop --server through a shell string and never stops it. rubocop keys its server by a digest of Gemfile.lock and .rubocop.yml, not by directory, and has no idle exit. On 2026-09-25 the machine held 509 orphaned rubocop servers rooted at deleted test tmpdirs. ## Approach The procman gem 0.2.0 already ships `Procman::Kinds::Rubocop`, which runs `rubocop --start-server --no-detach` under the procman supervisor with chdir root, and a `procman with KIND --root DIR -- CMD` CLI that runs CMD and releases the lease on exit. The lint gate command string changes to run rubocop through that CLI. `procman` is not on PATH today, so os-sdlc gets a `bin/procman` executable, which lives on PATH because `~/.claude/plugins/os-sdlc/bin` does. Required work: 1. Add `plugins/os-sdlc/bin/procman`: `require_relative "../lib/os_sdlc"` (pins BUNDLE_GEMFILE and runs bundler/setup), then `require "procman/cli"` and `Procman::CLI.start(ARGV)`, rescuing `Procman::Error` to warn its message and exit 1, following the shape of `bin/lsp-probe`. 2. `OsSdlc::Pipeline::GateCommands#rubocop_command_for` returns `procman with rubocop --root <project.path> -- rubocop --server --format json <files>`. `project.path` is the same directory `GateEvaluator` chdirs into (both come from `GateHandoff#project_root`). 3. One integration test behind `skip unless ENV["PROCMAN_REAL"] == "1"` runs the lint gate command string through `Model::ProjectShell.capture3` for real against a fixture project with one Ruby file, asserts stdout parses as rubocop JSON with a `files` key, and asserts `procman status rubocop --root <root>` exits 0 afterwards. Out of scope: `lint.rb`, `rubocop_report_fetcher.rb`, `autocorrect_prepass.rb`, and `hook/lint_changed/autocorrect.rb` run one-shot rubocop without `--server`; they start no server and stay as they are. Stopping servers whose root is gone is #714. ## Acceptance criteria 1. `GateCommands#to_h` for the lint and test-lint gates yields a command that starts with `procman with rubocop --root ` followed by the project path and ` -- rubocop --server --format json `, and yields nil when no Ruby files changed. 2. `plugins/os-sdlc/bin/procman list` exits 0 from a shell whose cwd is a user project with its own Gemfile. 3. The PROCMAN_REAL=1 integration test passes once by hand and is skipped in the default suite. 4. Lint gate stdout is pure rubocop JSON; procman writes nothing to stdout around the child command. ## Owner sketch - `OsSdlc::Pipeline::GateCommands` owns the lint command string; it reads `project.path` through its existing `attr_reader :project`. - `plugins/os-sdlc/bin/procman` owns PATH exposure of the gem CLI; no library code loads it. - `Procman::CLI` (gem) owns the lease, readiness, and release; cc-os adds no wrapper class. - Test: `plugins/os-sdlc/tests/pipeline/gate_commands_rubocop_procman_test.rb` for criterion 1; `plugins/os-sdlc/tests/pipeline/lint_gate_procman_integration_test.rb` for criterion 3. Blocked by: #712 (closed). VERDICT: CREATE — slice 2 of ADR-0219 with concrete provenance (509 orphaned servers), remaining required work items not yet shipped, criteria distinct from #712. ## Origin Session: ecfe6e11-0810-4431-88e5-8e24d34a239e Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/ecfe6e11- 0810-4431-88e5-8e24d34a239e.jsonl Where: plugins/os-sdlc/lib/os_sdlc/pipeline/gate_commands.rb ADR: 0219
Author
Owner

Split decided 2026-09-25: the rubocop kind and CLI kind resolution move to procman. See procman #2 (Kinds::RubocopServer with cache dir under the root hash) jared/procman#2 and procman #3 (CLI resolves KIND by catalog name) jared/procman#3.

This ticket narrows to replacing the shell rubocop --server call in plugins/os-sdlc/lib/os_sdlc/pipeline/gate_commands.rb with Procman.with.

Blocked until procman #4 (Release procman 0.2.0 as the stable line for cc-os) ships: jared/procman#4.

Split decided 2026-09-25: the rubocop kind and CLI kind resolution move to procman. See procman #2 (Kinds::RubocopServer with cache dir under the root hash) https://forgejo.swansoncloud.com/jared/procman/issues/2 and procman #3 (CLI resolves KIND by catalog name) https://forgejo.swansoncloud.com/jared/procman/issues/3. This ticket narrows to replacing the shell `rubocop --server` call in `plugins/os-sdlc/lib/os_sdlc/pipeline/gate_commands.rb` with `Procman.with`. Blocked until procman #4 (Release procman 0.2.0 as the stable line for cc-os) ships: https://forgejo.swansoncloud.com/jared/procman/issues/4.
Author
Owner

procman v0.2.0 is tagged on main (3c5f3b1) and is the stable line for this ticket. Public API: Procman.with(kind, root:) { |lease| ... } yields a Lease with #endpoint and releases on exit; Procman.ensure(kind, root:) returns the Lease. Catalog names: unix-socket, rubocop. Consumer kinds register with Procman::Kinds.register(name, klass); the contract is name, command(root:, dir:), endpoint(dir:), ready?(dir:) and register raises naming a missing method. Rubocop kind: Procman::Kinds.build("rubocop") runs rubocop --start-server --no-detach; rubocop keeps its default cache location so a client rubocop --server in the same root finds it with no env. CLI form for gate_commands: procman with rubocop --root DIR -- rubocop --server --format json FILES. The blocker on this ticket is cleared.

procman v0.2.0 is tagged on main (3c5f3b1) and is the stable line for this ticket. Public API: Procman.with(kind, root:) { |lease| ... } yields a Lease with #endpoint and releases on exit; Procman.ensure(kind, root:) returns the Lease. Catalog names: unix-socket, rubocop. Consumer kinds register with Procman::Kinds.register(name, klass); the contract is name, command(root:, dir:), endpoint(dir:), ready?(dir:) and register raises naming a missing method. Rubocop kind: Procman::Kinds.build("rubocop") runs rubocop --start-server --no-detach; rubocop keeps its default cache location so a client rubocop --server in the same root finds it with no env. CLI form for gate_commands: procman with rubocop --root DIR -- rubocop --server --format json FILES. The blocker on this ticket is cleared.
Author
Owner

Work started via os-sdlc pipeline on branch ticket-713 (worktree).

Work started via os-sdlc pipeline on branch ticket-713 (worktree).
Author
Owner

Resolution

Done: Lint and test-lint gates run rubocop --server through procman (Procman::Kinds::Rubocop) via plugins/os-sdlc/bin/procman, resolved by absolute path from GateCommands; three tests added, integration test behind PROCMAN_REAL=1.

Evidence: Branch ticket-713 commit fdfbb991; implementation 48, 16 dispatches, suite 1427 runs 0 failures; PROCMAN_REAL=1 integration test passed once by hand (5 assertions); procman list shows the rubocop server rooted at the test tmpdir.

Follow-ups: Pipeline defect: suite-check looped twice because bare procman was not on PATH inside a worktree (plugin symlink points at main); fixed by absolute path, recorded in the #713 close comment; no new ticket. Orphan cleanup for roots that no longer exist is #714.

## Resolution **Done:** Lint and test-lint gates run rubocop --server through procman (Procman::Kinds::Rubocop) via plugins/os-sdlc/bin/procman, resolved by absolute path from GateCommands; three tests added, integration test behind PROCMAN_REAL=1. **Evidence:** Branch ticket-713 commit fdfbb991; implementation 48, 16 dispatches, suite 1427 runs 0 failures; PROCMAN_REAL=1 integration test passed once by hand (5 assertions); procman list shows the rubocop server rooted at the test tmpdir. **Follow-ups:** Pipeline defect: suite-check looped twice because bare procman was not on PATH inside a worktree (plugin symlink points at main); fixed by absolute path, recorded in the #713 close comment; no new ticket. Orphan cleanup for roots that no longer exist is #714.
jared closed this issue 2026-09-25 21:43:14 +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#713
No description provided.