os-sdlc: fast-gate and detector enter through Thor #1682

Closed
opened 2026-11-10 21:28:27 +00:00 by jared · 2 comments
Owner

Problem

bin/fast-gate and bin/detector enter through OsSdlc::Boot.run. Boot looks each name up in Boot::Commands::TABLE, parses argv by hand (Boot::Inputs::FastGateArgv, Boot::Inputs::DetectorArgv, Boot::Inputs::CrcArgv, and the hand-written text in Boot::Inputs::UsageText), binds the pipeline database through ProjectBind, and then calls FastGate::Run.new(io, project, request).call or Detector::Run.new(io, project, request).call. Neither command reaches a Sequel model, so the database bind is unnecessary work. Both Run classes take the full io object. Detector::Run uses only io.stdout and io.stderr. FastGate::Run uses io.stdout, io.lint_process, io.git, and io.project_shell. 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 collaborators on Project are lazy, and that a Run object returns a result that the Thor command prints. Eleven crc-* commands already follow this shape (#1675, #1676, #1677), and #1681 moved the commandless base to OsSdlc::CLI. This ticket moves fast-gate and detector to the same shape.

Approach

The language is Ruby. The suite command is ruby test/all.rb, run from plugins/os-sdlc. Rulings 126 to 128 in docs/decisions/rulings.tsv decided the shape. Follow the existing pattern: lib/os_sdlc/cli.rb, lib/os_sdlc/cli/invocation.rb, lib/os_sdlc/cli/shell.rb, lib/os_sdlc/command_result.rb, lib/os_sdlc/crc/census/cli.rb, bin/crc-census, test/crc/census_cli_test.rb, test/support/crc_thor_scenario.rb.

  • OsSdlc::Project gains two lazy readers with writers for fakes, in the same form as repo and repo=: shell (default ProjectShell.new) and lint_process (default Lint::RubocopProcess.new). They are not attributes: they never enter attributes, the snapshot, or equality. A copy made by with or bound_to keeps an assigned value.
  • While ProcessIO still holds git, project_shell, and lint_process, OsSdlc::CLI::Invocation gives the project all three from process. In-process tests that pass fakes on the process (test/support/fast_gate_scenario.rb) continue to work.
  • OsSdlc::FastGate::CLI < OsSdlc::CLI (lib/os_sdlc/fast_gate/cli.rb) has one command, gate PATH.... bin/fast-gate becomes exit OsSdlc::FastGate::CLI.start(["gate", *ARGV], process: OsSdlc::ProcessIO.from_process). Thor declares --target NAME (required), --cop COP (repeatable, required), and --base REF. The command refuses an empty path list with a Thor::Error. Paths are expanded against the working directory that process holds, as today.
  • OsSdlc::Detector::CLI < OsSdlc::CLI (lib/os_sdlc/detector/cli.rb) has ten real Thor subcommands, one per name in Detector::Run::NAMES: holders, reach-through, error-lineage, cycle-edges, inward, effects, params, calls, flay, reek. bin/detector becomes exit OsSdlc::Detector::CLI.start(ARGV, process: OsSdlc::ProcessIO.from_process), with no added first word. Each subcommand declares its own options: --target NAME (required) and --brief for all; --ledger FILE (required) only for inward and effects; --flay-mass N (numeric, default Detector::Measures::FLAY_MASS) only for flay. Thor refuses an option that a subcommand does not declare. No custom unknown-command message. If Metrics/ClassLength (Max 100) is exceeded, declare the subcommands from one data table; do not add a cop Exclude.
  • FastGate::Run and Detector::Run take (project, request) and no io. call returns OsSdlc::CommandResult and writes to no stream. The Thor command prints through OsSdlc::CLI::Invocation. FastGate::Run reads project.repo, project.shell, and project.lint_process.
  • Neither command binds the database.
  • The "fast-gate" and "detector" entries leave Boot::Commands::TABLE. Boot::Inputs.fast_gate, Boot::Inputs.detector, Boot::Inputs::FastGateArgv, Boot::Inputs::DetectorArgv, and Boot::Inputs::UsageText are deleted. Boot::Inputs::CrcArgv stays, because collaborator-pass and mutation-pass still use it; remove only its methods that no caller uses after this ticket.

Kept behavior. The change must keep each item.

  • Script names, flags, stdout bytes, and exit codes: fast-gate exits 0 with one PASS line, 1 with the failure blocks on stdout; detector prints Signal TSV or the --brief markdown with exit 0, and prints detector NAME: <message> on stderr with exit 2 for Project::LibRootCountError. Exit 3 with no .sdlc/project.yaml at or above <cwd> when no project exists.
  • For a bad argument the exit code stays 2 and stdout stays empty. The stderr text becomes the error text of Thor. --help prints Thor help on stdout with exit 0.
  • No test starts a real process (.claude/rules/ruby-external-calls.md).
  • Every other command stays on Boot.run in this ticket.

Test-writer work. These existing tests and helpers pin the old text or the old shape. The test-writer changes each one in the red round; the programmer never edits a test.

  • test/support/in_process_dispatch_runner.rb:17-36: add fast-gate ([OsSdlc::FastGate::CLI, "gate"]) and detector (no prepended word) to THOR_ENTRIES; run must accept an entry with no command word.
  • test/support/crc_help_assertions.rb:4-10 assert_help_describes: only detector tests (5 methods) and one fast-gate test use it, and it requires a -h row that Thor help does not print. Change its callers to assert_thor_help or direct assertions on the Thor help text, then delete it.
  • test/detector/detector_bin_test.rb: :12-16 (unknown name lists NAMES) asserts exit 2, empty stdout, and a stderr line that names the bad word; :18-21 and :23-29 (help) assert Thor help; :43-48 and :50-55 (--ledger texts built from UsageText) assert exit 2, empty stdout, and a stderr line that names --ledger; :66 usage(name) reads Boot::Inputs::UsageText and goes.
  • test/detector/holders_bin_test.rb:68-69, test/detector/cycle_edges_bin_test.rb:12-13, test/detector/error_lineage_bin_test.rb:27-28, test/detector/reach_through_bin_test.rb:11-13: help assertions follow Thor help.
  • test/detector/reach_through_bin_test.rb:24-28: no --target asserts exit 2, empty stdout, and a stderr line that names --target.
  • test/fast_gate/fast_gate_bin_test.rb:21-26: no --cop asserts exit 2 and an output line that names --cop (not Boot::Inputs::UsageText).
  • test/fast_gate/fast_gate_bin_smoke_test.rb: help assertion follows Thor help.
  • Any test that calls FastGate::Run.new(io, ...) or Detector::Run.new(io, ...) directly follows the new constructor and reads the returned result.
  • test/os_sdlc/boot/crc_commands_test.rb:8: also assert that the keys fast-gate and detector are absent from Boot::Commands::TABLE.
  • Search test/ for UsageText, FastGateArgv, DetectorArgv, Inputs.fast_gate, and Inputs.detector; change every hit.

Required work:

  1. present FastGate::CLI.start in plugins/os-sdlc/bin/fast-gate
  2. absent Boot.run in plugins/os-sdlc/bin/fast-gate
  3. present Detector::CLI.start in plugins/os-sdlc/bin/detector
  4. absent Boot.run in plugins/os-sdlc/bin/detector
  5. present class CLI < OsSdlc::CLI in plugins/os-sdlc/lib/os_sdlc/fast_gate/cli.rb
  6. present class CLI < OsSdlc::CLI in plugins/os-sdlc/lib/os_sdlc/detector/cli.rb
  7. absent "fast-gate" => in plugins/os-sdlc/lib/os_sdlc/boot/commands.rb
  8. absent "detector" => in plugins/os-sdlc/lib/os_sdlc/boot/commands.rb
  9. absent def self.fast_gate in plugins/os-sdlc/lib/os_sdlc/boot/inputs.rb
  10. absent def self.detector in plugins/os-sdlc/lib/os_sdlc/boot/inputs.rb
  11. absent UsageText in plugins/os-sdlc/lib/os_sdlc/**/*.rb
  12. absent FastGateArgv in plugins/os-sdlc/lib/os_sdlc/**/*.rb
  13. absent DetectorArgv in plugins/os-sdlc/lib/os_sdlc/**/*.rb
  14. absent @io in plugins/os-sdlc/lib/os_sdlc/fast_gate/run.rb
  15. absent @io in plugins/os-sdlc/lib/os_sdlc/detector/run.rb
  16. absent assert_help_describes in plugins/os-sdlc/test/**/*.rb

Acceptance criteria

  1. Project.new(name: "p", root: root).shell returns a ProjectShell, and a second call returns the same object; lint_process returns a Lint::RubocopProcess, and a second call returns the same object.
  2. After project.shell = fake and project.lint_process = other, the readers return fake and other, and project.with(tracker: "t") returns a copy whose readers return the same two objects.
  3. Two projects with equal attributes are == when only one of them has an assigned shell and lint_process, and project.attributes has no "shell" key and no "lint_process" key.
  4. OsSdlc::FastGate::CLI.start(["gate", "--target", target, "--cop", cop, path], process: process), with fakes on process that report no offense and a green suite, prints one PASS line to the stdout of process and returns 0.
  5. FastGate::Run.new(project, request).call, with fakes assigned to project.repo, project.shell, and project.lint_process, returns an OsSdlc::CommandResult whose status is 0 and whose stdout holds the PASS line; the constructor takes no stream argument.
  6. A FastGate::CLI start in a project directory finishes with no call to Db.connect_project! and with Sequel::Model.db unchanged.
  7. FastGate::CLI.start(["gate", "--target", target, path], process: process) with no --cop returns 2, prints nothing to the stdout of process, and prints a line that names --cop to the stderr of process.
  8. FastGate::CLI.start(["gate", "--target", target, "--cop", cop], process: process) with no path returns 2, prints nothing to the stdout of process, and prints a non-empty error to the stderr of process.
  9. OsSdlc::Detector::CLI.start(["holders", "--target", target], process: process) prints the Signal TSV of the holders detector to the stdout of process and returns 0.
  10. Detector::Run.new(project, request).call returns an OsSdlc::CommandResult whose status is 0 and whose stdout holds the Signal TSV; the constructor takes no stream argument.
  11. Detector::CLI.start(["bogus", "--target", target], process: process) returns 2, prints nothing to the stdout of process, and prints a line that names bogus to the stderr of process.
  12. Detector::CLI.start(["holders", "--target", target, "--ledger", "x.tsv"], process: process) returns 2, prints nothing to stdout, and prints a line that names --ledger to stderr.
  13. Detector::CLI.start(["inward", "--target", target], process: process) with no --ledger returns 2, prints nothing to stdout, and prints a line that names --ledger to stderr.
  14. Detector::CLI.start(["flay", "--target", target, "--flay-mass", "abc"], process: process) returns 2, prints nothing to stdout, and prints a non-empty error to stderr.
  15. Detector::CLI.start(["reach-through", "--target", target], process: process) runs the reach-through detector: it returns 0 and prints its Signal TSV.
  16. Detector::CLI.start(["holders", "--help"], process: process) returns 0 and prints Thor help that names --target and --brief to the stdout of process.
  17. Detector::CLI.start(["holders", "--target", target], process: process) in a project with no Zeitwerk loader file returns 2 and prints a line that starts with detector holders: to the stderr of process.
  18. A Detector::CLI start in a project directory finishes with no call to Db.connect_project! and with Sequel::Model.db unchanged.

Owner sketch

OsSdlc::FastGate::CLI and OsSdlc::Detector::CLI (Thor) own the command names and option parsing of their commands. OsSdlc::CLI::Invocation finds the project, gives it the collaborators that process still holds, calls the run object, prints the result, and returns the status. OsSdlc::FastGate::Run owns one gate run and returns the result; OsSdlc::Detector::Run owns one detector run and returns the result; neither writes to a stream. OsSdlc::Project owns its collaborators: other objects read project.repo, project.shell, and project.lint_process.

Out of scope

collaborator-pass, system-model, the mutation commands, project.connect(:db), removal of Boot, 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: fast-gate and detector bind a database they never use and take 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--claude-worktrees-ticket-1677/a48658d5-b566-40ce-8257-ebeabf56f7e1.jsonl
Where: plugins/os-sdlc/bin/fast-gate, plugins/os-sdlc/bin/detector, plugins/os-sdlc/lib/os_sdlc/fast_gate/run.rb (OsSdlc::FastGate::Run), plugins/os-sdlc/lib/os_sdlc/detector/run.rb (OsSdlc::Detector::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: No open ticket covers moving fast-gate and detector off Boot onto Thor. The only match is #1646, the parent decision ticket.

## Problem `bin/fast-gate` and `bin/detector` enter through `OsSdlc::Boot.run`. `Boot` looks each name up in `Boot::Commands::TABLE`, parses argv by hand (`Boot::Inputs::FastGateArgv`, `Boot::Inputs::DetectorArgv`, `Boot::Inputs::CrcArgv`, and the hand-written text in `Boot::Inputs::UsageText`), binds the pipeline database through `ProjectBind`, and then calls `FastGate::Run.new(io, project, request).call` or `Detector::Run.new(io, project, request).call`. Neither command reaches a Sequel model, so the database bind is unnecessary work. Both `Run` classes take the full `io` object. `Detector::Run` uses only `io.stdout` and `io.stderr`. `FastGate::Run` uses `io.stdout`, `io.lint_process`, `io.git`, and `io.project_shell`. 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 collaborators on `Project` are lazy, and that a `Run` object returns a result that the Thor command prints. Eleven `crc-*` commands already follow this shape (#1675, #1676, #1677), and #1681 moved the commandless base to `OsSdlc::CLI`. This ticket moves `fast-gate` and `detector` to the same shape. ## Approach The language is Ruby. The suite command is `ruby test/all.rb`, run from `plugins/os-sdlc`. Rulings 126 to 128 in `docs/decisions/rulings.tsv` decided the shape. Follow the existing pattern: `lib/os_sdlc/cli.rb`, `lib/os_sdlc/cli/invocation.rb`, `lib/os_sdlc/cli/shell.rb`, `lib/os_sdlc/command_result.rb`, `lib/os_sdlc/crc/census/cli.rb`, `bin/crc-census`, `test/crc/census_cli_test.rb`, `test/support/crc_thor_scenario.rb`. - `OsSdlc::Project` gains two lazy readers with writers for fakes, in the same form as `repo` and `repo=`: `shell` (default `ProjectShell.new`) and `lint_process` (default `Lint::RubocopProcess.new`). They are not attributes: they never enter `attributes`, the snapshot, or equality. A copy made by `with` or `bound_to` keeps an assigned value. - While `ProcessIO` still holds `git`, `project_shell`, and `lint_process`, `OsSdlc::CLI::Invocation` gives the project all three from `process`. In-process tests that pass fakes on the process (`test/support/fast_gate_scenario.rb`) continue to work. - `OsSdlc::FastGate::CLI < OsSdlc::CLI` (`lib/os_sdlc/fast_gate/cli.rb`) has one command, `gate PATH...`. `bin/fast-gate` becomes `exit OsSdlc::FastGate::CLI.start(["gate", *ARGV], process: OsSdlc::ProcessIO.from_process)`. Thor declares `--target NAME` (required), `--cop COP` (repeatable, required), and `--base REF`. The command refuses an empty path list with a `Thor::Error`. Paths are expanded against the working directory that `process` holds, as today. - `OsSdlc::Detector::CLI < OsSdlc::CLI` (`lib/os_sdlc/detector/cli.rb`) has ten real Thor subcommands, one per name in `Detector::Run::NAMES`: `holders`, `reach-through`, `error-lineage`, `cycle-edges`, `inward`, `effects`, `params`, `calls`, `flay`, `reek`. `bin/detector` becomes `exit OsSdlc::Detector::CLI.start(ARGV, process: OsSdlc::ProcessIO.from_process)`, with no added first word. Each subcommand declares its own options: `--target NAME` (required) and `--brief` for all; `--ledger FILE` (required) only for `inward` and `effects`; `--flay-mass N` (numeric, default `Detector::Measures::FLAY_MASS`) only for `flay`. Thor refuses an option that a subcommand does not declare. No custom unknown-command message. If `Metrics/ClassLength` (Max 100) is exceeded, declare the subcommands from one data table; do not add a cop Exclude. - `FastGate::Run` and `Detector::Run` take `(project, request)` and no `io`. `call` returns `OsSdlc::CommandResult` and writes to no stream. The Thor command prints through `OsSdlc::CLI::Invocation`. `FastGate::Run` reads `project.repo`, `project.shell`, and `project.lint_process`. - Neither command binds the database. - The `"fast-gate"` and `"detector"` entries leave `Boot::Commands::TABLE`. `Boot::Inputs.fast_gate`, `Boot::Inputs.detector`, `Boot::Inputs::FastGateArgv`, `Boot::Inputs::DetectorArgv`, and `Boot::Inputs::UsageText` are deleted. `Boot::Inputs::CrcArgv` stays, because `collaborator-pass` and `mutation-pass` still use it; remove only its methods that no caller uses after this ticket. Kept behavior. The change must keep each item. - Script names, flags, stdout bytes, and exit codes: `fast-gate` exits 0 with one `PASS` line, 1 with the failure blocks on stdout; `detector` prints Signal TSV or the `--brief` markdown with exit 0, and prints `detector NAME: <message>` on stderr with exit 2 for `Project::LibRootCountError`. Exit 3 with `no .sdlc/project.yaml at or above <cwd>` when no project exists. - For a bad argument the exit code stays 2 and stdout stays empty. The stderr text becomes the error text of Thor. `--help` prints Thor help on stdout with exit 0. - No test starts a real process (`.claude/rules/ruby-external-calls.md`). - Every other command stays on `Boot.run` in this ticket. Test-writer work. These existing tests and helpers pin the old text or the old shape. The test-writer changes each one in the red round; the programmer never edits a test. - `test/support/in_process_dispatch_runner.rb:17-36`: add `fast-gate` (`[OsSdlc::FastGate::CLI, "gate"]`) and `detector` (no prepended word) to `THOR_ENTRIES`; `run` must accept an entry with no command word. - `test/support/crc_help_assertions.rb:4-10` `assert_help_describes`: only detector tests (5 methods) and one fast-gate test use it, and it requires a `-h` row that Thor help does not print. Change its callers to `assert_thor_help` or direct assertions on the Thor help text, then delete it. - `test/detector/detector_bin_test.rb`: `:12-16` (unknown name lists `NAMES`) asserts exit 2, empty stdout, and a stderr line that names the bad word; `:18-21` and `:23-29` (help) assert Thor help; `:43-48` and `:50-55` (`--ledger` texts built from `UsageText`) assert exit 2, empty stdout, and a stderr line that names `--ledger`; `:66` `usage(name)` reads `Boot::Inputs::UsageText` and goes. - `test/detector/holders_bin_test.rb:68-69`, `test/detector/cycle_edges_bin_test.rb:12-13`, `test/detector/error_lineage_bin_test.rb:27-28`, `test/detector/reach_through_bin_test.rb:11-13`: help assertions follow Thor help. - `test/detector/reach_through_bin_test.rb:24-28`: no `--target` asserts exit 2, empty stdout, and a stderr line that names `--target`. - `test/fast_gate/fast_gate_bin_test.rb:21-26`: no `--cop` asserts exit 2 and an output line that names `--cop` (not `Boot::Inputs::UsageText`). - `test/fast_gate/fast_gate_bin_smoke_test.rb`: help assertion follows Thor help. - Any test that calls `FastGate::Run.new(io, ...)` or `Detector::Run.new(io, ...)` directly follows the new constructor and reads the returned result. - `test/os_sdlc/boot/crc_commands_test.rb:8`: also assert that the keys `fast-gate` and `detector` are absent from `Boot::Commands::TABLE`. - Search `test/` for `UsageText`, `FastGateArgv`, `DetectorArgv`, `Inputs.fast_gate`, and `Inputs.detector`; change every hit. Required work: 1. present `FastGate::CLI.start` in `plugins/os-sdlc/bin/fast-gate` 2. absent `Boot.run` in `plugins/os-sdlc/bin/fast-gate` 3. present `Detector::CLI.start` in `plugins/os-sdlc/bin/detector` 4. absent `Boot.run` in `plugins/os-sdlc/bin/detector` 5. present `class CLI < OsSdlc::CLI` in `plugins/os-sdlc/lib/os_sdlc/fast_gate/cli.rb` 6. present `class CLI < OsSdlc::CLI` in `plugins/os-sdlc/lib/os_sdlc/detector/cli.rb` 7. absent `"fast-gate" =>` in `plugins/os-sdlc/lib/os_sdlc/boot/commands.rb` 8. absent `"detector" =>` in `plugins/os-sdlc/lib/os_sdlc/boot/commands.rb` 9. absent `def self.fast_gate` in `plugins/os-sdlc/lib/os_sdlc/boot/inputs.rb` 10. absent `def self.detector` in `plugins/os-sdlc/lib/os_sdlc/boot/inputs.rb` 11. absent `UsageText` in `plugins/os-sdlc/lib/os_sdlc/**/*.rb` 12. absent `FastGateArgv` in `plugins/os-sdlc/lib/os_sdlc/**/*.rb` 13. absent `DetectorArgv` in `plugins/os-sdlc/lib/os_sdlc/**/*.rb` 14. absent `@io` in `plugins/os-sdlc/lib/os_sdlc/fast_gate/run.rb` 15. absent `@io` in `plugins/os-sdlc/lib/os_sdlc/detector/run.rb` 16. absent `assert_help_describes` in `plugins/os-sdlc/test/**/*.rb` ## Acceptance criteria 1. `Project.new(name: "p", root: root).shell` returns a `ProjectShell`, and a second call returns the same object; `lint_process` returns a `Lint::RubocopProcess`, and a second call returns the same object. 2. After `project.shell = fake` and `project.lint_process = other`, the readers return `fake` and `other`, and `project.with(tracker: "t")` returns a copy whose readers return the same two objects. 3. Two projects with equal attributes are `==` when only one of them has an assigned `shell` and `lint_process`, and `project.attributes` has no `"shell"` key and no `"lint_process"` key. 4. `OsSdlc::FastGate::CLI.start(["gate", "--target", target, "--cop", cop, path], process: process)`, with fakes on `process` that report no offense and a green suite, prints one `PASS` line to the stdout of `process` and returns 0. 5. `FastGate::Run.new(project, request).call`, with fakes assigned to `project.repo`, `project.shell`, and `project.lint_process`, returns an `OsSdlc::CommandResult` whose `status` is 0 and whose `stdout` holds the `PASS` line; the constructor takes no stream argument. 6. A `FastGate::CLI` start in a project directory finishes with no call to `Db.connect_project!` and with `Sequel::Model.db` unchanged. 7. `FastGate::CLI.start(["gate", "--target", target, path], process: process)` with no `--cop` returns 2, prints nothing to the stdout of `process`, and prints a line that names `--cop` to the stderr of `process`. 8. `FastGate::CLI.start(["gate", "--target", target, "--cop", cop], process: process)` with no path returns 2, prints nothing to the stdout of `process`, and prints a non-empty error to the stderr of `process`. 9. `OsSdlc::Detector::CLI.start(["holders", "--target", target], process: process)` prints the Signal TSV of the holders detector to the stdout of `process` and returns 0. 10. `Detector::Run.new(project, request).call` returns an `OsSdlc::CommandResult` whose `status` is 0 and whose `stdout` holds the Signal TSV; the constructor takes no stream argument. 11. `Detector::CLI.start(["bogus", "--target", target], process: process)` returns 2, prints nothing to the stdout of `process`, and prints a line that names `bogus` to the stderr of `process`. 12. `Detector::CLI.start(["holders", "--target", target, "--ledger", "x.tsv"], process: process)` returns 2, prints nothing to stdout, and prints a line that names `--ledger` to stderr. 13. `Detector::CLI.start(["inward", "--target", target], process: process)` with no `--ledger` returns 2, prints nothing to stdout, and prints a line that names `--ledger` to stderr. 14. `Detector::CLI.start(["flay", "--target", target, "--flay-mass", "abc"], process: process)` returns 2, prints nothing to stdout, and prints a non-empty error to stderr. 15. `Detector::CLI.start(["reach-through", "--target", target], process: process)` runs the `reach-through` detector: it returns 0 and prints its Signal TSV. 16. `Detector::CLI.start(["holders", "--help"], process: process)` returns 0 and prints Thor help that names `--target` and `--brief` to the stdout of `process`. 17. `Detector::CLI.start(["holders", "--target", target], process: process)` in a project with no Zeitwerk loader file returns 2 and prints a line that starts with `detector holders: ` to the stderr of `process`. 18. A `Detector::CLI` start in a project directory finishes with no call to `Db.connect_project!` and with `Sequel::Model.db` unchanged. ## Owner sketch `OsSdlc::FastGate::CLI` and `OsSdlc::Detector::CLI` (Thor) own the command names and option parsing of their commands. `OsSdlc::CLI::Invocation` finds the project, gives it the collaborators that `process` still holds, calls the run object, prints the result, and returns the status. `OsSdlc::FastGate::Run` owns one gate run and returns the result; `OsSdlc::Detector::Run` owns one detector run and returns the result; neither writes to a stream. `OsSdlc::Project` owns its collaborators: other objects read `project.repo`, `project.shell`, and `project.lint_process`. ## Out of scope `collaborator-pass`, `system-model`, the mutation commands, `project.connect(:db)`, removal of `Boot`, 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: `fast-gate` and `detector` bind a database they never use and take 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--claude-worktrees-ticket-1677/a48658d5-b566-40ce-8257-ebeabf56f7e1.jsonl Where: plugins/os-sdlc/bin/fast-gate, plugins/os-sdlc/bin/detector, plugins/os-sdlc/lib/os_sdlc/fast_gate/run.rb (OsSdlc::FastGate::Run), plugins/os-sdlc/lib/os_sdlc/detector/run.rb (OsSdlc::Detector::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: No open ticket covers moving fast-gate and detector off Boot onto Thor. The only match is #1646, the parent decision ticket.
Author
Owner

Takedown run docs/takedown/1682-20261110-162833 failed at final-red after 1131.8 s and $3.53: 'final design revise-2: failing test not red at base (plugins/os-sdlc/test/fast_gate/fast_gate_bin_smoke_test.rb names no new test method)'. The design edits an existing test that pins the old usage text, and the red check accepts only new test methods. Per ruling 125 the ticket goes to the default map; the body now has Approach, Acceptance criteria and Owner sketch. Work starts on branch ticket-1682.

Takedown run docs/takedown/1682-20261110-162833 failed at final-red after 1131.8 s and $3.53: 'final design revise-2: failing test not red at base (plugins/os-sdlc/test/fast_gate/fast_gate_bin_smoke_test.rb names no new test method)'. The design edits an existing test that pins the old usage text, and the red check accepts only new test methods. Per ruling 125 the ticket goes to the default map; the body now has Approach, Acceptance criteria and Owner sketch. Work starts on branch ticket-1682.
Author
Owner

Resolution

Done: fast-gate and detector enter through Thor (OsSdlc::FastGate::CLI, OsSdlc::Detector::CLI with ten subcommands). Both Run classes take (project, request), return OsSdlc::CommandResult, and bind no database. Project has lazy shell and lint_process readers. The two Boot table entries, FastGateArgv, DetectorArgv and UsageText are deleted. Cop AdapterBuiltOutsideEntry permits a seamed lazy reader (ruling 129).

Evidence: Commit 5ce7f5567, merged to main at fff600d2c. Suite on the merged code: 4213 runs, 8654 assertions, 0 failures. Installed commands checked after the merge: detector holders --target os-sdlc exit 0 with TSV; detector --help exit 0; detector with no argument exit 2; fast-gate without --cop exit 2. Run 277 escalated at the programmer (wrong expectations in new tests); the orchestrator finished by hand and abandoned the run.

Follow-ups: The remaining commands (collaborator-pass, system-model, mutation commands, model commands, Boot removal) are built by hand in session a48658d5 under parent #1646, by owner order of 2026-11-10. Other follow-ups (takedown final-red rejects edited tests; fast-gate with a project root below the git top level) go through /os-backlog:capture in the same session.

## Resolution **Done:** fast-gate and detector enter through Thor (OsSdlc::FastGate::CLI, OsSdlc::Detector::CLI with ten subcommands). Both Run classes take (project, request), return OsSdlc::CommandResult, and bind no database. Project has lazy shell and lint_process readers. The two Boot table entries, FastGateArgv, DetectorArgv and UsageText are deleted. Cop AdapterBuiltOutsideEntry permits a seamed lazy reader (ruling 129). **Evidence:** Commit 5ce7f5567, merged to main at fff600d2c. Suite on the merged code: 4213 runs, 8654 assertions, 0 failures. Installed commands checked after the merge: detector holders --target os-sdlc exit 0 with TSV; detector --help exit 0; detector with no argument exit 2; fast-gate without --cop exit 2. Run 277 escalated at the programmer (wrong expectations in new tests); the orchestrator finished by hand and abandoned the run. **Follow-ups:** The remaining commands (collaborator-pass, system-model, mutation commands, model commands, Boot removal) are built by hand in session a48658d5 under parent #1646, by owner order of 2026-11-10. Other follow-ups (takedown final-red rejects edited tests; fast-gate with a project root below the git top level) go through /os-backlog:capture in the same session. <!-- os-backlog kind: resolution -->
jared closed this issue 2026-11-10 22:29: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#1682
No description provided.