os-sdlc: crc-sweep enters through Thor; Project owns a lazy repo #1675

Closed
opened 2026-11-10 18:04:47 +00:00 by jared · 2 comments
Owner

Problem

bin/crc-sweep runs OsSdlc::Boot.run("crc-sweep", OsSdlc::ProcessIO.from_process, ARGV). Boot looks the name up in Boot::Commands::TABLE, parses argv by hand in Boot::Inputs.crc_sweep, binds the pipeline database, and then calls Crc::Sweep::Run.new(io, project, run).call. Crc::Sweep::Run reaches no Sequel model, so the database bind is unnecessary work and it creates .sdlc/pipeline.db. Crc::Sweep::Run takes the full io object, but it uses only io.git, io.stdout, and io.stderr. The owner ruled on 2026-11-10 that Thor is the entry for os-sdlc commands, that a command and the classes below it take a Project, that project.repo is lazy, and that a Run object returns a result that the command prints. ADR-0123 no longer excludes os-sdlc dispatch commands from Thor. This ticket moves crc-sweep as the first full path. The other commands follow in later tickets.

Approach

The language is Ruby. The suite command is ruby test/all.rb, run from plugins/os-sdlc.

  • OsSdlc::Project gains a lazy repo reader and a repo= writer. repo is not an attribute: it never enters attributes, the snapshot, or equality. The reader builds the repository at the project root through the existing GitRepositories seam on first use, and memoizes it. A copy made by with or bound_to keeps an assigned repository.
  • A new Thor class OsSdlc::Crc::CLI (lib/os_sdlc/crc/cli.rb) has one command, sweep RUN_DIR. Thor does the parsing: the command declares RUN_DIR as its one argument and declares the options --target NAME (required), --write (boolean), --exclude PATH (repeatable), and --history PATH (repeatable). The command takes no raw argv and calls no Boot::Inputs or Boot::ArgvOption parser. Thor error text goes to the stderr that process holds, not to the global $stderr. Follow the existing Thor classes for the form (lib/os_sdlc/evidence/collection.rb, lib/os_sdlc/boot/onboarding.rb): the entry passes collaborators through the Thor start config.
  • bin/crc-sweep becomes exit OsSdlc::Crc::CLI.start(["sweep", *ARGV], process: OsSdlc::ProcessIO.from_process).
  • The sweep command finds the project root from the working directory that process holds, loads the Project for the target, and does not connect the database. While ProcessIO still holds git, the command gives the project its repository from process.git, so the in-process tests that pass git: FakeGit... continue to work.
  • Crc::Sweep::Run takes the project and the run settings, and no io. Run#call returns a result that holds the exit status, the report (or nil), and the error line (or nil). It writes to no stream. The sweep command prints the report to stdout as JSON, prints the error line to stderr, and returns the status.
  • The "crc-sweep" entry leaves Boot::Commands::TABLE, and Boot::Inputs.crc_sweep goes. The shared test entry (test/support/in_process_bin.rb and its runners) starts Crc::CLI for the name crc-sweep.

Kept behavior. The existing suite pins these items, so they are not red-round criteria. The change must keep each one.

  • Every existing crc-sweep test passes with the same stdout bytes, stderr lines, and exit codes (test/crc/sweep_bin_test.rb, the sweep tests in test/crc_boot_entry_test.rb). This includes exit 2 with crc-sweep: git ls-files failed in <root> outside a git work tree, exit 3 with the loud line for an unexpected error, and exit 3 for a directory with no project. One thing changes: for a bad argument (no arguments, a missing --target, a missing RUN_DIR, an unknown option) the exit code stays 2 and stdout stays empty, but the stderr text becomes the error text of Thor. An existing test that pins the old usage text for crc-sweep (test/crc/sweep_bin_test.rb test_prints_usage_and_exits_two_without_arguments, and any crc-sweep usage assertion in test/crc_boot_entry_test.rb) is updated to assert exit 2, empty stdout, and non-empty stderr.
  • ADR-0232 stays in force: the script name crc-sweep, its flags, and its output shapes do not change.
  • No test starts a real process (.claude/rules/ruby-external-calls.md). ProcessStartGuard enforces this.
  • The other ten crc-* commands and every other command stay on Boot.run in this ticket.

Required work:

  1. present Crc::CLI.start in plugins/os-sdlc/bin/crc-sweep
  2. absent Boot.run in plugins/os-sdlc/bin/crc-sweep
  3. absent "crc-sweep" => in plugins/os-sdlc/lib/os_sdlc/boot/commands.rb
  4. absent def self.crc_sweep in plugins/os-sdlc/lib/os_sdlc/boot/inputs.rb
  5. present class CLI < Thor in plugins/os-sdlc/lib/os_sdlc/crc/cli.rb
  6. absent Boot::Inputs in plugins/os-sdlc/lib/os_sdlc/crc/cli.rb
  7. absent Boot::ArgvOption in plugins/os-sdlc/lib/os_sdlc/crc/cli.rb
  8. present method_option :target in plugins/os-sdlc/lib/os_sdlc/crc/cli.rb

Acceptance criteria

  1. Project.new(name: "p", root: root).repo returns an OsGit::Repository whose root is the project root as a String, and a second call returns the same object.
  2. After project.repo = fake, project.repo returns fake, and project.with(tracker: "t").repo returns the same fake.
  3. Two projects with equal attributes are == when only one of them has an assigned repo, and project.attributes has no "repo" key.
  4. OsSdlc::Crc::CLI.start(["sweep", "--target", target, run_dir], process: process) with a ledger that has one live hit prints the sweep report as JSON to the stdout of process and returns 1.
  5. Crc::Sweep::Run.new(project, run).call, with a fake repository assigned to project.repo, returns a result whose status is 1 and whose report holds the live count, and it takes no stream argument.
  6. The sweep command does not call the database bind: a test that starts Crc::CLI in a project directory finishes with no call to Db.connect_project! and with Sequel::Model.db unchanged.
  7. Crc::CLI.start(["sweep", run_dir], process: process) with no --target returns 2, prints nothing to the stdout of process, and prints a line that names --target to the stderr of process.
  8. Crc::CLI.start(["sweep", "--target", target], process: process) with no RUN_DIR returns 2, prints nothing to the stdout of process, and prints a non-empty error to the stderr of process.
  9. Crc::CLI.start(["sweep", "--target", target, "--exclude", "a", "--exclude", "b", "--history", "h", "--write", run_dir], process: process) gives Crc::Sweep::Run the settings excludes: ["a", "b"], histories: ["h"], and write: true, shown by the result of the run (the missing-input error line names a, b, and h when those paths do not exist).

Owner sketch

OsSdlc::Crc::CLI (Thor) owns the command names and option parsing for the crc-* commands; in this ticket it has sweep only. It finds the project, calls the run object, prints the result, and returns the status. OsSdlc::Crc::Sweep::Run owns the sweep for one request and returns the result; it writes nothing. OsSdlc::Project owns its repository: other objects read project.repo. The error-to-exit-code wrapper stays with ProcessIO::Outcome (guarded), which the command calls.

Out of scope

The other crc-* commands, project.connect(:db), the removal of Boot, the removal of git from ProcessIO, and cop changes. These are later tickets of the same change.

Origin

Trigger: n/a: design ticket named by the user, not a run that showed a fault
Improvised this session: none
Chain: crc-sweep binds a database it never uses and takes an io it mostly ignores ← every command enters through Boot.run and one command table ← ADR-0250 (retired habit) put all entries behind Boot
Session: a48658d5-b566-40ce-8257-ebeabf56f7e1
Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os-plugins-os-sdlc/a48658d5-b566-40ce-8257-ebeabf56f7e1.jsonl
Where: plugins/os-sdlc/bin/crc-sweep, plugins/os-sdlc/lib/os_sdlc/crc/sweep/run.rb (OsSdlc::Crc::Sweep::Run), plugins/os-sdlc/lib/os_sdlc/boot/commands.rb
Root candidate: #1646
Approved by: Jared named and approved this change in session on 2026-11-10 ("List the changes we are making. Then implement them.").

VERDICT: CREATE
REASON: #1646 is the open decision ticket for moving all os-sdlc entries from Boot to Thor. This ticket is the first concrete slice (crc-sweep only), and no open ticket covers its pieces.

## Problem `bin/crc-sweep` runs `OsSdlc::Boot.run("crc-sweep", OsSdlc::ProcessIO.from_process, ARGV)`. `Boot` looks the name up in `Boot::Commands::TABLE`, parses argv by hand in `Boot::Inputs.crc_sweep`, binds the pipeline database, and then calls `Crc::Sweep::Run.new(io, project, run).call`. `Crc::Sweep::Run` reaches no Sequel model, so the database bind is unnecessary work and it creates `.sdlc/pipeline.db`. `Crc::Sweep::Run` takes the full `io` object, but it uses only `io.git`, `io.stdout`, and `io.stderr`. The owner ruled on 2026-11-10 that Thor is the entry for os-sdlc commands, that a command and the classes below it take a `Project`, that `project.repo` is lazy, and that a `Run` object returns a result that the command prints. ADR-0123 no longer excludes os-sdlc dispatch commands from Thor. This ticket moves `crc-sweep` as the first full path. The other commands follow in later tickets. ## Approach The language is Ruby. The suite command is `ruby test/all.rb`, run from `plugins/os-sdlc`. - `OsSdlc::Project` gains a lazy `repo` reader and a `repo=` writer. `repo` is not an attribute: it never enters `attributes`, the snapshot, or equality. The reader builds the repository at the project root through the existing `GitRepositories` seam on first use, and memoizes it. A copy made by `with` or `bound_to` keeps an assigned repository. - A new Thor class `OsSdlc::Crc::CLI` (`lib/os_sdlc/crc/cli.rb`) has one command, `sweep RUN_DIR`. Thor does the parsing: the command declares `RUN_DIR` as its one argument and declares the options `--target NAME` (required), `--write` (boolean), `--exclude PATH` (repeatable), and `--history PATH` (repeatable). The command takes no raw argv and calls no `Boot::Inputs` or `Boot::ArgvOption` parser. Thor error text goes to the stderr that `process` holds, not to the global `$stderr`. Follow the existing Thor classes for the form (`lib/os_sdlc/evidence/collection.rb`, `lib/os_sdlc/boot/onboarding.rb`): the entry passes collaborators through the Thor start config. - `bin/crc-sweep` becomes `exit OsSdlc::Crc::CLI.start(["sweep", *ARGV], process: OsSdlc::ProcessIO.from_process)`. - The `sweep` command finds the project root from the working directory that `process` holds, loads the `Project` for the target, and does not connect the database. While `ProcessIO` still holds `git`, the command gives the project its repository from `process.git`, so the in-process tests that pass `git: FakeGit...` continue to work. - `Crc::Sweep::Run` takes the project and the run settings, and no `io`. `Run#call` returns a result that holds the exit status, the report (or nil), and the error line (or nil). It writes to no stream. The `sweep` command prints the report to stdout as JSON, prints the error line to stderr, and returns the status. - The `"crc-sweep"` entry leaves `Boot::Commands::TABLE`, and `Boot::Inputs.crc_sweep` goes. The shared test entry (`test/support/in_process_bin.rb` and its runners) starts `Crc::CLI` for the name `crc-sweep`. Kept behavior. The existing suite pins these items, so they are not red-round criteria. The change must keep each one. - Every existing `crc-sweep` test passes with the same stdout bytes, stderr lines, and exit codes (`test/crc/sweep_bin_test.rb`, the sweep tests in `test/crc_boot_entry_test.rb`). This includes exit 2 with `crc-sweep: git ls-files failed in <root>` outside a git work tree, exit 3 with the loud line for an unexpected error, and exit 3 for a directory with no project. One thing changes: for a bad argument (no arguments, a missing `--target`, a missing `RUN_DIR`, an unknown option) the exit code stays 2 and stdout stays empty, but the stderr text becomes the error text of Thor. An existing test that pins the old usage text for `crc-sweep` (`test/crc/sweep_bin_test.rb` `test_prints_usage_and_exits_two_without_arguments`, and any `crc-sweep` usage assertion in `test/crc_boot_entry_test.rb`) is updated to assert exit 2, empty stdout, and non-empty stderr. - ADR-0232 stays in force: the script name `crc-sweep`, its flags, and its output shapes do not change. - No test starts a real process (`.claude/rules/ruby-external-calls.md`). `ProcessStartGuard` enforces this. - The other ten `crc-*` commands and every other command stay on `Boot.run` in this ticket. Required work: 1. present `Crc::CLI.start` in `plugins/os-sdlc/bin/crc-sweep` 2. absent `Boot.run` in `plugins/os-sdlc/bin/crc-sweep` 3. absent `"crc-sweep" =>` in `plugins/os-sdlc/lib/os_sdlc/boot/commands.rb` 4. absent `def self.crc_sweep` in `plugins/os-sdlc/lib/os_sdlc/boot/inputs.rb` 5. present `class CLI < Thor` in `plugins/os-sdlc/lib/os_sdlc/crc/cli.rb` 6. absent `Boot::Inputs` in `plugins/os-sdlc/lib/os_sdlc/crc/cli.rb` 7. absent `Boot::ArgvOption` in `plugins/os-sdlc/lib/os_sdlc/crc/cli.rb` 8. present `method_option :target` in `plugins/os-sdlc/lib/os_sdlc/crc/cli.rb` ## Acceptance criteria 1. `Project.new(name: "p", root: root).repo` returns an `OsGit::Repository` whose `root` is the project root as a String, and a second call returns the same object. 2. After `project.repo = fake`, `project.repo` returns `fake`, and `project.with(tracker: "t").repo` returns the same `fake`. 3. Two projects with equal attributes are `==` when only one of them has an assigned `repo`, and `project.attributes` has no `"repo"` key. 4. `OsSdlc::Crc::CLI.start(["sweep", "--target", target, run_dir], process: process)` with a ledger that has one live hit prints the sweep report as JSON to the stdout of `process` and returns 1. 5. `Crc::Sweep::Run.new(project, run).call`, with a fake repository assigned to `project.repo`, returns a result whose `status` is 1 and whose `report` holds the live count, and it takes no stream argument. 6. The `sweep` command does not call the database bind: a test that starts `Crc::CLI` in a project directory finishes with no call to `Db.connect_project!` and with `Sequel::Model.db` unchanged. 7. `Crc::CLI.start(["sweep", run_dir], process: process)` with no `--target` returns 2, prints nothing to the stdout of `process`, and prints a line that names `--target` to the stderr of `process`. 8. `Crc::CLI.start(["sweep", "--target", target], process: process)` with no `RUN_DIR` returns 2, prints nothing to the stdout of `process`, and prints a non-empty error to the stderr of `process`. 9. `Crc::CLI.start(["sweep", "--target", target, "--exclude", "a", "--exclude", "b", "--history", "h", "--write", run_dir], process: process)` gives `Crc::Sweep::Run` the settings `excludes: ["a", "b"]`, `histories: ["h"]`, and `write: true`, shown by the result of the run (the missing-input error line names `a`, `b`, and `h` when those paths do not exist). ## Owner sketch `OsSdlc::Crc::CLI` (Thor) owns the command names and option parsing for the `crc-*` commands; in this ticket it has `sweep` only. It finds the project, calls the run object, prints the result, and returns the status. `OsSdlc::Crc::Sweep::Run` owns the sweep for one request and returns the result; it writes nothing. `OsSdlc::Project` owns its repository: other objects read `project.repo`. The error-to-exit-code wrapper stays with `ProcessIO::Outcome` (`guarded`), which the command calls. ## Out of scope The other `crc-*` commands, `project.connect(:db)`, the removal of `Boot`, the removal of `git` from `ProcessIO`, and cop changes. These are later tickets of the same change. ## Origin Trigger: n/a: design ticket named by the user, not a run that showed a fault Improvised this session: none Chain: `crc-sweep` binds a database it never uses and takes an `io` it mostly ignores ← every command enters through `Boot.run` and one command table ← ADR-0250 (retired habit) put all entries behind `Boot` Session: a48658d5-b566-40ce-8257-ebeabf56f7e1 Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os-plugins-os-sdlc/a48658d5-b566-40ce-8257-ebeabf56f7e1.jsonl Where: plugins/os-sdlc/bin/crc-sweep, plugins/os-sdlc/lib/os_sdlc/crc/sweep/run.rb (OsSdlc::Crc::Sweep::Run), plugins/os-sdlc/lib/os_sdlc/boot/commands.rb Root candidate: #1646 Approved by: Jared named and approved this change in session on 2026-11-10 ("List the changes we are making. Then implement them."). VERDICT: CREATE REASON: #1646 is the open decision ticket for moving all os-sdlc entries from Boot to Thor. This ticket is the first concrete slice (crc-sweep only), and no open ticket covers its pieces.
Author
Owner

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

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

Resolution

Done: crc-sweep enters through the Thor class Crc::CLI. Thor declares RUN_DIR, --target (required), --write, --exclude and --history. Crc::Sweep::Run takes the Project, receives no streams and returns Crc::Sweep::Result; the Thor command prints. Project#repo is lazy. The command does not bind the pipeline database. The crc-sweep entry is gone from Boot::Commands::TABLE, Boot::Inputs and Boot::Inputs::UsageText.

Evidence: Implementation 271 ended at merge-ready (27 dispatches, one reroute after a ticket correction). Commit 317ebca74 on branch ticket-1675. Suite gate, mutation survivor gate, behavior-verifier and the second contract audit passed.

Follow-ups: None from this ticket. The remaining pieces of the plan that Jared ruled on 2026-11-10 (the other crc-* commands, the other commands without a model, the model commands with project.connect(:db), and the removal of Boot) each get a ticket through /os-backlog:capture.

## Resolution **Done:** crc-sweep enters through the Thor class Crc::CLI. Thor declares RUN_DIR, --target (required), --write, --exclude and --history. Crc::Sweep::Run takes the Project, receives no streams and returns Crc::Sweep::Result; the Thor command prints. Project#repo is lazy. The command does not bind the pipeline database. The crc-sweep entry is gone from Boot::Commands::TABLE, Boot::Inputs and Boot::Inputs::UsageText. **Evidence:** Implementation 271 ended at merge-ready (27 dispatches, one reroute after a ticket correction). Commit 317ebca74 on branch ticket-1675. Suite gate, mutation survivor gate, behavior-verifier and the second contract audit passed. **Follow-ups:** None from this ticket. The remaining pieces of the plan that Jared ruled on 2026-11-10 (the other crc-* commands, the other commands without a model, the model commands with project.connect(:db), and the removal of Boot) each get a ticket through /os-backlog:capture. <!-- os-backlog kind: resolution -->
jared closed this issue 2026-11-10 18:47:44 +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#1675
No description provided.