os-sdlc: crc-jobs, crc-inward-delta, crc-ledger-commits, crc-shares enter through Thor; one Thor class per concept; Thor generates help #1676

Closed
opened 2026-11-10 19:01:19 +00:00 by jared · 2 comments
Owner

Problem

Four commands still enter through OsSdlc::Boot.run: crc-jobs, crc-inward-delta, crc-ledger-commits, and crc-shares. Boot parses their argv by hand in Boot::Inputs, binds the pipeline database, and calls Run.new(io, project, request).call. None of the four reaches a Sequel model, so the database bind is unnecessary work and it creates .sdlc/pipeline.db. Each Run passes the full io object to Crc::Emitter, which reads only io.stdout and io.stderr; Crc::Ledger::Commits::Run also reads io.git. crc-sweep moved to Thor in #1675, but its class Crc::CLI holds a hand-written USAGE text and intercepts -h and --help itself, so Thor does not generate the help. One Thor class cannot hold all eleven crc-* commands under the Metrics/ClassLength limit of 100 lines. 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. Rulings 121 and 122 (docs/decisions/rulings.tsv) set the shape: one Thor class per concept over a small base class, and the help that Thor generates.

Approach

The language is Ruby. The suite command is ruby test/all.rb, run from plugins/os-sdlc. Read lib/os_sdlc/crc/cli.rb, lib/os_sdlc/crc/sweep/run.rb, lib/os_sdlc/crc/sweep/result.rb, and test/crc/cli_test.rb first: they are the pattern from #1675.

  • OsSdlc::Crc::CLI (lib/os_sdlc/crc/cli.rb) becomes a small Thor base class with no command of its own. It holds only what every crc-* command shares: the guarded start that returns the exit status, the Thor error to stderr with exit 2, the project lookup from the working directory that process holds (exit 3 with no .sdlc/project.yaml at or above <dir> when there is none), the project load for --target, and the printing of a result. Do not move option declarations into a module to pass the class-length cop.
  • One Thor class per concept inherits from the base: Crc::Sweep::CLI (crc/sweep/cli.rb), Crc::Jobs::CLI (crc/jobs/cli.rb), Crc::InwardDelta::CLI (crc/inward_delta/cli.rb), Crc::Ledger::Commits::CLI (crc/ledger/commits/cli.rb), and Crc::Shares::CLI (crc/shares/cli.rb). Each declares one command with its arguments and options through Thor (desc, method_option). The command takes no raw argv and calls no Boot::Inputs, Boot::Inputs::CrcArgv, or Boot::ArgvOption parser.
  • Each bin/ script calls its own class with the command name first, in the form bin/crc-sweep has today: exit OsSdlc::Crc::Jobs::CLI.start(["jobs", *ARGV], process: OsSdlc::ProcessIO.from_process). The command names are sweep, jobs, inward-delta (or the Thor method name for it), ledger-commits, and shares.
  • Arguments and options, as today: jobs RUN_DIR; inward-delta RUN_DIR BEFORE AFTER; ledger-commits RUN_DIR SHA; shares SUBCOMMAND RUN_DIR with --max-bytes N (default 45000) and --ticket N. Every command has --target NAME (required). Options can come before or after the positional arguments (skills/crc-refactor/SKILL.md writes crc-shares --target X --ticket N rows-merge DIR).
  • Rules that Thor cannot declare stay in code below the command and give exit 2 with a non-empty line on stderr and empty stdout: SUBCOMMAND must be one of Crc::Shares::SUBCOMMANDS; --max-bytes must be a positive Integer; --ticket is required for rows-merge and refused for every other subcommand; BEFORE and AFTER are expanded against the working directory that process holds and each must be a file.
  • Help comes from Thor. Delete the USAGE constant and the -h/--help intercept from crc/cli.rb. --help and -h print the help that Thor generates for the command to the stdout that process holds and return 0. Thor writes through its shell to the global $stdout by default, so the base class gives Thor a shell that writes to the streams of process; nothing goes to the global $stdout or $stderr. The usage line of the help names the script (crc-jobs, not the test program name).
  • Each Run takes the project and its request, and no io. Run#call returns a result that holds the exit status, the report (or nil), and the error line (or nil), and writes to no stream. The command prints the report to stdout as pretty JSON, prints the error line to stderr, and returns the status. The four commands share one result shape with crc-sweep; make one shared result value for the five (Crc::Sweep::Result is the present form) and remove the stream writes from Crc::Emitter, or remove Crc::Emitter when nothing else needs it.
  • Crc::Ledger::Commits::Run reads the repository from project.repo. While ProcessIO still holds git, the base class gives the project its repository from process.git, as crc/cli.rb does today.
  • No command of the five connects the database.
  • The four entries leave Boot::Commands::TABLE; Boot::Inputs.crc_jobs, crc_inward_delta, crc_ledger_commits, and crc_shares go; the CrcArgv methods and UsageText entries that only these four read go. Boot::Inputs.crc_run_dir_input stays while crc-score reads it.
  • The shared test entry (test/support/in_process_dispatch_runner.rb) starts the Thor class for each of the five names; replace the single command == "crc-sweep" branch with one lookup from name to class and command.

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

  • Every existing test of the five commands passes with the same stdout bytes, the same stderr lines from the run objects (crc-<name>: no ledger.tsv in <run> and the like), and the same exit codes 0, 1, 2, and 3. Two things change. First, for a bad argument the exit code stays 2 and stdout stays empty, but the stderr text becomes the error text of Thor or the one-line error of the rule in code; a test that asserts usage: crc-<name> on a bad argument (test/crc/jobs_bin_test.rb, inward_delta_bin_test.rb, ledger_commits_bin_test.rb, shares_bin_test.rb, shares_row_merge_bin_test.rb) is updated to assert exit 2, empty stdout, and non-empty stderr. Second, the help format becomes the format of Thor; the smoke tests of the five commands (test/crc/*_bin_smoke_test.rb, test/support/crc_help_assertions.rb) are updated to assert exit 0, the script name, each positional name, and each option name in the Thor help. The smoke tests of the six commands that stay on Boot.run keep their present assertions.
  • test/detector/entry_points_test.rb and any other test that maps a moved bin/crc-* script to OsSdlc::Boot is updated to the new entry class.
  • ADR-0232 stays in force: script names, flags, stdout shapes, and exit codes do not change. An unknown option is now refused with exit 2.
  • No test starts a real process (.claude/rules/ruby-external-calls.md). ProcessStartGuard enforces this.
  • crc-census, crc-records, crc-tickets, crc-score, crc-placement, crc-file, and every other command stay on Boot.run in this ticket.
  • No code comments, no cop Exclude list, no inline rubocop:disable.

Required work:

  1. present Crc::Sweep::CLI.start in plugins/os-sdlc/bin/crc-sweep
  2. present Crc::Jobs::CLI.start in plugins/os-sdlc/bin/crc-jobs
  3. present Crc::InwardDelta::CLI.start in plugins/os-sdlc/bin/crc-inward-delta
  4. present Crc::Ledger::Commits::CLI.start in plugins/os-sdlc/bin/crc-ledger-commits
  5. present Crc::Shares::CLI.start in plugins/os-sdlc/bin/crc-shares
  6. absent Boot.run in plugins/os-sdlc/bin/crc-jobs
  7. absent Boot.run in plugins/os-sdlc/bin/crc-inward-delta
  8. absent Boot.run in plugins/os-sdlc/bin/crc-ledger-commits
  9. absent Boot.run in plugins/os-sdlc/bin/crc-shares
  10. absent "crc-jobs" => in plugins/os-sdlc/lib/os_sdlc/boot/commands.rb
  11. absent "crc-inward-delta" => in plugins/os-sdlc/lib/os_sdlc/boot/commands.rb
  12. absent "crc-ledger-commits" => in plugins/os-sdlc/lib/os_sdlc/boot/commands.rb
  13. absent "crc-shares" => in plugins/os-sdlc/lib/os_sdlc/boot/commands.rb
  14. absent USAGE in plugins/os-sdlc/lib/os_sdlc/crc/cli.rb
  15. absent method_option in plugins/os-sdlc/lib/os_sdlc/crc/cli.rb
  16. absent shares_request in plugins/os-sdlc/lib/os_sdlc/boot/inputs/crc_argv.rb
  17. absent usage: crc-shares in plugins/os-sdlc/lib/os_sdlc/boot/inputs/usage_text.rb

Acceptance criteria

  1. OsSdlc::Crc::Jobs::CLI.start(["jobs", "--target", target, run_dir], process: process) prints the jobs report as pretty JSON to the stdout of process and returns the status of the report (0 for an ok report).
  2. OsSdlc::Crc::InwardDelta::CLI.start(["inward-delta", "--target", target, run_dir, before, after], process: process) prints the delta report as pretty JSON to the stdout of process and returns its status.
  3. OsSdlc::Crc::Ledger::Commits::CLI.start(["ledger-commits", "--target", target, run_dir, sha], process: process) reads the commit through the repository that process.git supplies to the project, prints the report as pretty JSON to the stdout of process, and returns its status.
  4. OsSdlc::Crc::Shares::CLI.start(["shares", "--target", target, "verdicts-split", run_dir], process: process) prints the shares report as pretty JSON to the stdout of process and returns its status.
  5. OsSdlc::Crc::Sweep::CLI.start(["sweep", "--target", target, run_dir], process: process) with a ledger that has one live hit prints the sweep report as JSON and returns 1, and OsSdlc::Crc::CLI has no sweep command.
  6. Crc::Jobs::Run.new(project, request).call returns a result whose status is an Integer and whose report holds the report, and it takes no stream argument. The same holds for Crc::InwardDelta::Run, Crc::Ledger::Commits::Run (with a fake repository assigned to project.repo), and Crc::Shares::Run.
  7. With no ledger.tsv in the run directory, Crc::Jobs::Run.new(project, request).call returns a result with status 2, a nil report, and the error line crc-jobs: no ledger.tsv in <run>.
  8. None of the four new commands calls the database bind: a test that starts each class in a project directory finishes with no call to Db.connect_project! and with Sequel::Model.db unchanged.
  9. For each of the five classes, start with --help after the command name returns 0, prints help text to the stdout of process that names the script (crc-jobs for Crc::Jobs::CLI), each positional argument, and each option, and prints nothing to the global $stdout.
  10. For each of the four new classes, start with no --target returns 2, prints nothing to the stdout of process, and prints a line that names --target to the stderr of process.
  11. Crc::Shares::CLI.start(["shares", "--target", target, "--max-bytes", "0", "verdicts-split", run_dir], process: process) returns 2 with empty stdout and a non-empty stderr line.
  12. Crc::Shares::CLI.start with the subcommand rows-merge and no --ticket returns 2 with empty stdout and a non-empty stderr line; with --ticket 5 and the subcommand verdicts-split it also returns 2; with an unknown subcommand it also returns 2.
  13. Crc::Shares::CLI.start(["shares", "--target", target, "--ticket", "5", "rows-merge", run_dir], process: process) (options before the positional arguments) reaches Crc::Shares::Run with the subcommand rows-merge, the ticket 5, and the run directory.
  14. Crc::InwardDelta::CLI.start with a BEFORE path that names no file returns 2 with empty stdout and a non-empty stderr line; a relative BEFORE path is read against the working directory that process holds.

Owner sketch

OsSdlc::Crc::CLI (Thor base class) owns what every crc-* entry shares: the guarded start, the streams of process for Thor output, the project lookup and load, and the printing of a result. Each Crc::<Concept>::CLI owns the command name, the arguments, and the options of its one command; it builds the request, calls the run object, and returns the status. Each Crc::<Concept>::Run owns the work for one request and returns the result; it writes nothing. One shared result value owns the exit status, the report, and the error line for the five commands. OsSdlc::Project owns its repository: Crc::Ledger::Commits::Run reads project.repo. The error-to-exit-code wrapper stays with ProcessIO::Outcome (guarded).

Out of scope

crc-census, crc-records, crc-tickets, crc-score, crc-placement, and crc-file (the next tickets of the same change), project.connect(:db), the removal of Boot, the removal of git from ProcessIO, a new name for Boot::Inputs::CrcArgv, and cop changes.

Reproduce

n/a: feature ticket.

Origin

Trigger: n/a: design ticket named by the user, not a run that showed a fault
Improvised this session: none
Chain: four crc-* commands bind a database they never use and pass an io they mostly ignore ← 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/lib/os_sdlc/crc/cli.rb (OsSdlc::Crc::CLI), plugins/os-sdlc/lib/os_sdlc/crc/emitter.rb (OsSdlc::Crc::Emitter), 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."). Shape per rulings 121 to 123 (predicted).

VERDICT: CREATE
REASON: Jared named and approved this slice. The backlog search found no open ticket that moves crc-jobs, crc-inward-delta, crc-ledger-commits and crc-shares from Boot to Thor. #1646 is the parent decision ticket, not this slice.

## Problem Four commands still enter through `OsSdlc::Boot.run`: `crc-jobs`, `crc-inward-delta`, `crc-ledger-commits`, and `crc-shares`. `Boot` parses their argv by hand in `Boot::Inputs`, binds the pipeline database, and calls `Run.new(io, project, request).call`. None of the four reaches a Sequel model, so the database bind is unnecessary work and it creates `.sdlc/pipeline.db`. Each `Run` passes the full `io` object to `Crc::Emitter`, which reads only `io.stdout` and `io.stderr`; `Crc::Ledger::Commits::Run` also reads `io.git`. `crc-sweep` moved to Thor in #1675, but its class `Crc::CLI` holds a hand-written `USAGE` text and intercepts `-h` and `--help` itself, so Thor does not generate the help. One Thor class cannot hold all eleven `crc-*` commands under the `Metrics/ClassLength` limit of 100 lines. 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. Rulings 121 and 122 (`docs/decisions/rulings.tsv`) set the shape: one Thor class per concept over a small base class, and the help that Thor generates. ## Approach The language is Ruby. The suite command is `ruby test/all.rb`, run from `plugins/os-sdlc`. Read `lib/os_sdlc/crc/cli.rb`, `lib/os_sdlc/crc/sweep/run.rb`, `lib/os_sdlc/crc/sweep/result.rb`, and `test/crc/cli_test.rb` first: they are the pattern from #1675. - `OsSdlc::Crc::CLI` (`lib/os_sdlc/crc/cli.rb`) becomes a small Thor base class with no command of its own. It holds only what every `crc-*` command shares: the guarded start that returns the exit status, the Thor error to stderr with exit 2, the project lookup from the working directory that `process` holds (exit 3 with `no .sdlc/project.yaml at or above <dir>` when there is none), the project load for `--target`, and the printing of a result. Do not move option declarations into a module to pass the class-length cop. - One Thor class per concept inherits from the base: `Crc::Sweep::CLI` (`crc/sweep/cli.rb`), `Crc::Jobs::CLI` (`crc/jobs/cli.rb`), `Crc::InwardDelta::CLI` (`crc/inward_delta/cli.rb`), `Crc::Ledger::Commits::CLI` (`crc/ledger/commits/cli.rb`), and `Crc::Shares::CLI` (`crc/shares/cli.rb`). Each declares one command with its arguments and options through Thor (`desc`, `method_option`). The command takes no raw argv and calls no `Boot::Inputs`, `Boot::Inputs::CrcArgv`, or `Boot::ArgvOption` parser. - Each `bin/` script calls its own class with the command name first, in the form `bin/crc-sweep` has today: `exit OsSdlc::Crc::Jobs::CLI.start(["jobs", *ARGV], process: OsSdlc::ProcessIO.from_process)`. The command names are `sweep`, `jobs`, `inward-delta` (or the Thor method name for it), `ledger-commits`, and `shares`. - Arguments and options, as today: `jobs RUN_DIR`; `inward-delta RUN_DIR BEFORE AFTER`; `ledger-commits RUN_DIR SHA`; `shares SUBCOMMAND RUN_DIR` with `--max-bytes N` (default 45000) and `--ticket N`. Every command has `--target NAME` (required). Options can come before or after the positional arguments (`skills/crc-refactor/SKILL.md` writes `crc-shares --target X --ticket N rows-merge DIR`). - Rules that Thor cannot declare stay in code below the command and give exit 2 with a non-empty line on stderr and empty stdout: `SUBCOMMAND` must be one of `Crc::Shares::SUBCOMMANDS`; `--max-bytes` must be a positive Integer; `--ticket` is required for `rows-merge` and refused for every other subcommand; `BEFORE` and `AFTER` are expanded against the working directory that `process` holds and each must be a file. - Help comes from Thor. Delete the `USAGE` constant and the `-h`/`--help` intercept from `crc/cli.rb`. `--help` and `-h` print the help that Thor generates for the command to the stdout that `process` holds and return 0. Thor writes through its shell to the global `$stdout` by default, so the base class gives Thor a shell that writes to the streams of `process`; nothing goes to the global `$stdout` or `$stderr`. The usage line of the help names the script (`crc-jobs`, not the test program name). - Each `Run` takes the project and its request, and no `io`. `Run#call` returns a result that holds the exit status, the report (or nil), and the error line (or nil), and writes to no stream. The command prints the report to stdout as pretty JSON, prints the error line to stderr, and returns the status. The four commands share one result shape with `crc-sweep`; make one shared result value for the five (`Crc::Sweep::Result` is the present form) and remove the stream writes from `Crc::Emitter`, or remove `Crc::Emitter` when nothing else needs it. - `Crc::Ledger::Commits::Run` reads the repository from `project.repo`. While `ProcessIO` still holds `git`, the base class gives the project its repository from `process.git`, as `crc/cli.rb` does today. - No command of the five connects the database. - The four entries leave `Boot::Commands::TABLE`; `Boot::Inputs.crc_jobs`, `crc_inward_delta`, `crc_ledger_commits`, and `crc_shares` go; the `CrcArgv` methods and `UsageText` entries that only these four read go. `Boot::Inputs.crc_run_dir_input` stays while `crc-score` reads it. - The shared test entry (`test/support/in_process_dispatch_runner.rb`) starts the Thor class for each of the five names; replace the single `command == "crc-sweep"` branch with one lookup from name to class and command. Kept behavior. The existing suite pins these items, so they are not red-round criteria. The change must keep each one. - Every existing test of the five commands passes with the same stdout bytes, the same stderr lines from the run objects (`crc-<name>: no ledger.tsv in <run>` and the like), and the same exit codes 0, 1, 2, and 3. Two things change. First, for a bad argument the exit code stays 2 and stdout stays empty, but the stderr text becomes the error text of Thor or the one-line error of the rule in code; a test that asserts `usage: crc-<name>` on a bad argument (`test/crc/jobs_bin_test.rb`, `inward_delta_bin_test.rb`, `ledger_commits_bin_test.rb`, `shares_bin_test.rb`, `shares_row_merge_bin_test.rb`) is updated to assert exit 2, empty stdout, and non-empty stderr. Second, the help format becomes the format of Thor; the smoke tests of the five commands (`test/crc/*_bin_smoke_test.rb`, `test/support/crc_help_assertions.rb`) are updated to assert exit 0, the script name, each positional name, and each option name in the Thor help. The smoke tests of the six commands that stay on `Boot.run` keep their present assertions. - `test/detector/entry_points_test.rb` and any other test that maps a moved `bin/crc-*` script to `OsSdlc::Boot` is updated to the new entry class. - ADR-0232 stays in force: script names, flags, stdout shapes, and exit codes do not change. An unknown option is now refused with exit 2. - No test starts a real process (`.claude/rules/ruby-external-calls.md`). `ProcessStartGuard` enforces this. - `crc-census`, `crc-records`, `crc-tickets`, `crc-score`, `crc-placement`, `crc-file`, and every other command stay on `Boot.run` in this ticket. - No code comments, no cop `Exclude` list, no inline `rubocop:disable`. Required work: 1. present `Crc::Sweep::CLI.start` in `plugins/os-sdlc/bin/crc-sweep` 2. present `Crc::Jobs::CLI.start` in `plugins/os-sdlc/bin/crc-jobs` 3. present `Crc::InwardDelta::CLI.start` in `plugins/os-sdlc/bin/crc-inward-delta` 4. present `Crc::Ledger::Commits::CLI.start` in `plugins/os-sdlc/bin/crc-ledger-commits` 5. present `Crc::Shares::CLI.start` in `plugins/os-sdlc/bin/crc-shares` 6. absent `Boot.run` in `plugins/os-sdlc/bin/crc-jobs` 7. absent `Boot.run` in `plugins/os-sdlc/bin/crc-inward-delta` 8. absent `Boot.run` in `plugins/os-sdlc/bin/crc-ledger-commits` 9. absent `Boot.run` in `plugins/os-sdlc/bin/crc-shares` 10. absent `"crc-jobs" =>` in `plugins/os-sdlc/lib/os_sdlc/boot/commands.rb` 11. absent `"crc-inward-delta" =>` in `plugins/os-sdlc/lib/os_sdlc/boot/commands.rb` 12. absent `"crc-ledger-commits" =>` in `plugins/os-sdlc/lib/os_sdlc/boot/commands.rb` 13. absent `"crc-shares" =>` in `plugins/os-sdlc/lib/os_sdlc/boot/commands.rb` 14. absent `USAGE` in `plugins/os-sdlc/lib/os_sdlc/crc/cli.rb` 15. absent `method_option` in `plugins/os-sdlc/lib/os_sdlc/crc/cli.rb` 16. absent `shares_request` in `plugins/os-sdlc/lib/os_sdlc/boot/inputs/crc_argv.rb` 17. absent `usage: crc-shares` in `plugins/os-sdlc/lib/os_sdlc/boot/inputs/usage_text.rb` ## Acceptance criteria 1. `OsSdlc::Crc::Jobs::CLI.start(["jobs", "--target", target, run_dir], process: process)` prints the jobs report as pretty JSON to the stdout of `process` and returns the status of the report (0 for an ok report). 2. `OsSdlc::Crc::InwardDelta::CLI.start(["inward-delta", "--target", target, run_dir, before, after], process: process)` prints the delta report as pretty JSON to the stdout of `process` and returns its status. 3. `OsSdlc::Crc::Ledger::Commits::CLI.start(["ledger-commits", "--target", target, run_dir, sha], process: process)` reads the commit through the repository that `process.git` supplies to the project, prints the report as pretty JSON to the stdout of `process`, and returns its status. 4. `OsSdlc::Crc::Shares::CLI.start(["shares", "--target", target, "verdicts-split", run_dir], process: process)` prints the shares report as pretty JSON to the stdout of `process` and returns its status. 5. `OsSdlc::Crc::Sweep::CLI.start(["sweep", "--target", target, run_dir], process: process)` with a ledger that has one live hit prints the sweep report as JSON and returns 1, and `OsSdlc::Crc::CLI` has no `sweep` command. 6. `Crc::Jobs::Run.new(project, request).call` returns a result whose `status` is an Integer and whose `report` holds the report, and it takes no stream argument. The same holds for `Crc::InwardDelta::Run`, `Crc::Ledger::Commits::Run` (with a fake repository assigned to `project.repo`), and `Crc::Shares::Run`. 7. With no `ledger.tsv` in the run directory, `Crc::Jobs::Run.new(project, request).call` returns a result with `status` 2, a nil `report`, and the error line `crc-jobs: no ledger.tsv in <run>`. 8. None of the four new commands calls the database bind: a test that starts each class in a project directory finishes with no call to `Db.connect_project!` and with `Sequel::Model.db` unchanged. 9. For each of the five classes, `start` with `--help` after the command name returns 0, prints help text to the stdout of `process` that names the script (`crc-jobs` for `Crc::Jobs::CLI`), each positional argument, and each option, and prints nothing to the global `$stdout`. 10. For each of the four new classes, `start` with no `--target` returns 2, prints nothing to the stdout of `process`, and prints a line that names `--target` to the stderr of `process`. 11. `Crc::Shares::CLI.start(["shares", "--target", target, "--max-bytes", "0", "verdicts-split", run_dir], process: process)` returns 2 with empty stdout and a non-empty stderr line. 12. `Crc::Shares::CLI.start` with the subcommand `rows-merge` and no `--ticket` returns 2 with empty stdout and a non-empty stderr line; with `--ticket 5` and the subcommand `verdicts-split` it also returns 2; with an unknown subcommand it also returns 2. 13. `Crc::Shares::CLI.start(["shares", "--target", target, "--ticket", "5", "rows-merge", run_dir], process: process)` (options before the positional arguments) reaches `Crc::Shares::Run` with the subcommand `rows-merge`, the ticket 5, and the run directory. 14. `Crc::InwardDelta::CLI.start` with a `BEFORE` path that names no file returns 2 with empty stdout and a non-empty stderr line; a relative `BEFORE` path is read against the working directory that `process` holds. ## Owner sketch `OsSdlc::Crc::CLI` (Thor base class) owns what every `crc-*` entry shares: the guarded start, the streams of `process` for Thor output, the project lookup and load, and the printing of a result. Each `Crc::<Concept>::CLI` owns the command name, the arguments, and the options of its one command; it builds the request, calls the run object, and returns the status. Each `Crc::<Concept>::Run` owns the work for one request and returns the result; it writes nothing. One shared result value owns the exit status, the report, and the error line for the five commands. `OsSdlc::Project` owns its repository: `Crc::Ledger::Commits::Run` reads `project.repo`. The error-to-exit-code wrapper stays with `ProcessIO::Outcome` (`guarded`). ## Out of scope `crc-census`, `crc-records`, `crc-tickets`, `crc-score`, `crc-placement`, and `crc-file` (the next tickets of the same change), `project.connect(:db)`, the removal of `Boot`, the removal of `git` from `ProcessIO`, a new name for `Boot::Inputs::CrcArgv`, and cop changes. ## Reproduce n/a: feature ticket. ## Origin Trigger: n/a: design ticket named by the user, not a run that showed a fault Improvised this session: none Chain: four `crc-*` commands bind a database they never use and pass an `io` they mostly ignore ← 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/lib/os_sdlc/crc/cli.rb (OsSdlc::Crc::CLI), plugins/os-sdlc/lib/os_sdlc/crc/emitter.rb (OsSdlc::Crc::Emitter), 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."). Shape per rulings 121 to 123 (predicted). VERDICT: CREATE REASON: Jared named and approved this slice. The backlog search found no open ticket that moves crc-jobs, crc-inward-delta, crc-ledger-commits and crc-shares from Boot to Thor. #1646 is the parent decision ticket, not this slice.
Author
Owner

Work started on branch ticket-1676 through /os-sdlc:implement.

Work started on branch ticket-1676 through /os-sdlc:implement.
Author
Owner

Resolution

Done: crc-jobs, crc-inward-delta, crc-ledger-commits and crc-shares enter through Thor. One Thor class per concept inherits from the base Crc::CLI, which holds no command; crc-sweep moved to Crc::Sweep::CLI. Thor generates the help and writes it to the streams of process. Each Run takes the project and its request, receives no streams, and returns Crc::CommandResult; Crc::Emitter is deleted. No command of the five binds the pipeline database. The four entries left Boot::Commands::TABLE, Boot::Inputs, CrcArgv and UsageText.

Evidence: Implementation 274 ended at merge-ready (15 dispatches, one reroute after a test-defect escalation). Commits d2e97d81e and b8704b72b on branch ticket-1676. Full suite: 3966 runs, 0 failures, 5.9 s. Suite gate, mutation survivor gate, behavior-verifier and contract audit passed. Implementations 272 and 273 failed at change-auditor on an evidence-tool fault before any code existed.

Follow-ups: None from this ticket. The six crc-* commands that stay on Boot (census, records, tickets, score, placement, file) get the next ticket of the plan through /os-backlog:capture. One lens proposal (A18, class-level factory on a Data value) and one tool defect (evidence collect with general names) go through /os-backlog:capture.

## Resolution **Done:** crc-jobs, crc-inward-delta, crc-ledger-commits and crc-shares enter through Thor. One Thor class per concept inherits from the base Crc::CLI, which holds no command; crc-sweep moved to Crc::Sweep::CLI. Thor generates the help and writes it to the streams of process. Each Run takes the project and its request, receives no streams, and returns Crc::CommandResult; Crc::Emitter is deleted. No command of the five binds the pipeline database. The four entries left Boot::Commands::TABLE, Boot::Inputs, CrcArgv and UsageText. **Evidence:** Implementation 274 ended at merge-ready (15 dispatches, one reroute after a test-defect escalation). Commits d2e97d81e and b8704b72b on branch ticket-1676. Full suite: 3966 runs, 0 failures, 5.9 s. Suite gate, mutation survivor gate, behavior-verifier and contract audit passed. Implementations 272 and 273 failed at change-auditor on an evidence-tool fault before any code existed. **Follow-ups:** None from this ticket. The six crc-* commands that stay on Boot (census, records, tickets, score, placement, file) get the next ticket of the plan through /os-backlog:capture. One lens proposal (A18, class-level factory on a Data value) and one tool defect (evidence collect with general names) go through /os-backlog:capture. <!-- os-backlog kind: resolution -->
jared closed this issue 2026-11-10 19:42:58 +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#1676
No description provided.