os-sdlc: crc-census, crc-records, crc-tickets, crc-score, crc-placement, crc-file enter through Thor #1677
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
rule-proposal
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#1677
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Six
crc-*commands still enter throughOsSdlc::Boot.run:crc-census,crc-records,crc-tickets,crc-score,crc-placement, andcrc-file.Bootparses their argv by hand inBoot::Inputs, binds the pipeline database, and callsRun.new(io, project, request).call. None of the six reaches a Sequel model, so the database bind is unnecessary work and it creates.sdlc/pipeline.db. EachRuntakes the fullioobject but reads onlyio.stdoutandio.stderr;Crc::Census::Runalso readsio.git, andCrc::Filing::Runalso readsio.cwdandio.backlog_process. The other fivecrc-*commands moved to Thor in #1675 and #1676. 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 aProject, thatproject.repois lazy, and that aRunobject returns a result that the command prints. Rulings 121 and 122 (docs/decisions/rulings.tsv) set the shape: one Thor class per concept over the baseCrc::CLI, and the help that Thor generates.Approach
The language is Ruby. The suite command is
ruby test/all.rb, run fromplugins/os-sdlc. Read these files first; they are the pattern from #1676:lib/os_sdlc/crc/cli.rb,lib/os_sdlc/crc/cli/invocation.rb,lib/os_sdlc/crc/cli/shell.rb,lib/os_sdlc/crc/command_result.rb,lib/os_sdlc/crc/shares/cli.rb,lib/os_sdlc/crc/shares/run.rb,bin/crc-shares, and the teststest/crc/shares_cli_test.rb,test/crc/thor_entries_test.rb,test/support/crc_thor_scenario.rb.Crc::CLI:Crc::Census::CLI,Crc::Records::CLI,Crc::Tickets::CLI,Crc::Score::CLI,Crc::Placement::CLI, andCrc::Filing::CLI(forcrc-file), each atlib/os_sdlc/crc/<concept>/cli.rb. Each declares one command with its arguments and options through Thor (desc,method_option). The command takes no raw argv and calls noBoot::Inputs,Boot::Inputs::CrcArgv, orBoot::ArgvOptionparser. Do not move option declarations into a module to pass the class-length cop.bin/script calls its own class with the command name first, asbin/crc-sharesdoes: for exampleexit OsSdlc::Crc::Census::CLI.start(["census", *ARGV], process: OsSdlc::ProcessIO.from_process).--target NAME(required).census RUN_DIR [PATH...]with--loader FILE,--exclude PREFIX(repeatable), and--since SHA.records RUN_DIRwith--write(boolean).tickets RUN_DIRwith--session ID(required),--transcript PATH(required),--out DIR,--scope-label,--priority,--suite-command, and--eager-load-command.score RUN_DIR.placementwith no positional argument; a positional argument is refused with exit 2. The run directory staysdocs/crc/<target>.file TICKETS_JSONwith--os-backlog CMD(required) and--order KEYS(required, a comma list).Runtakes the project and its request, and noio.Run#callreturns a result and writes to no stream. The command prints what the result holds and returns the status.crc-scoreandcrc-placementprint TSV text, not JSON, andcrc-filecan print a JSON array to stdout and problem lines to stderr in the same run; widen the shared result valueCrc::CommandResultso that it carries the text for stdout and the lines for stderr for all eleven commands, and keep every byte of today's stdout (including the absence of a final newline where a command usesprinttoday).Crc::Census::Runreads the repository fromproject.repo.Crc::Filing::Runstill needs the backlog client. The Thor command builds it from the working directory and the backlog process thatprocessholds, asCrc::Filing::Runbuilds it today, and passes it in.Crc::Filing::Result#report(out:, err:)no longer takes streams.ledger.tsvin the run directory?" gets one owner. TodayCrc::CommandResult.for_ledgerholds it inside the value and runs the whole command throughyield, andCrc::Sweep::Run,Crc::Records::Run, andCrc::Tickets::Runeach hold their own copy with the same error text. Move the check to one small object (for exampleCrc::LedgerGate.new(command, path).call { ... }, which returns the failed result<command>: no ledger.tsv in <run>or the reported result of the block), use it in every run object that has the check, and removefor_ledgerfromCrc::CommandResultso that the value only carries its fields (lens heuristic A18; the contract audit of #1676 flagged this and a challenge confirmed a code defect).Boot::Commands::TABLE. Everycrc_*method leavesBoot::Inputs(includingcrc_run_dir_input). TheCrcArgvmethods andUsageTextentries that only these six read go.CrcArgvkeeps whatdetector,fast-gate, andcollaborator-passread.Test-writer work. These existing tests pin behavior that this ticket changes, and no other node may edit tests. The test-writer changes them in the red round, with the new tests:
test/support/in_process_dispatch_runner.rb: add the six script names to the lookupTHOR_ENTRIES. The lookup must not name a constant that does not exist yet at load time (resolve the class when the command runs), or every test in the suite fails to load before the production code exists.usage: crc-<name>(test/crc/census_bin_test.rb,filing_bin_test.rb,placement_bin_test.rb,records_bin_test.rb,tickets_bin_test.rb): assert exit 2, empty stdout, and non-empty stderr; rename a test whose name says "usage".test/crc/*_bin_smoke_test.rb): useassert_thor_helpfromtest/support/crc_help_assertions.rb(script name, each positional name, each option name, exit 0). Delete the old help helpers from that support file when no test uses them.test/detector/entry_points_test.rb(mapsbin/crc-censusto"OsSdlc::Boot") andtest/system_model_entry_journeys_test.rb(callsBoot.run("crc-census", ...)directly): change each to the new entry class, or to a command that stays onBootwhen the test is aboutBootand not about census. Read the test to see which.test/crc_boot_entry_test.rbandtest/crc/placement_rulings_boot_test.rb: they reach the commands throughInProcessDispatchRunner; keep them and correct only an assertion that pins usage text.Kept behavior. The existing suite pins these items. The change must keep each one.
crc-census: no loader file X under R,crc-records: no ledger.tsv in <run>,crc-score: missing <file>,crc-file: <problem>, and the like), the same files written (ledger.tsv,vocabulary.tsv,batch-*.txt,wave-progress.json, ticket bodies under--out), and the same exit codes 0, 1, 2, and 3. For a bad argument the exit code stays 2 and stdout stays empty; the stderr text becomes the error text of Thor or a one-line error from code.skills/crc-refactor/SKILL.mdrunsbin/crc-score --target P DIR > score.tsv: stdout ofcrc-scoreholds the TSV only..claude/rules/ruby-external-calls.md).ProcessStartGuardenforces this.crc-*command stays onBoot.runin this ticket.Excludelist, no inlinerubocop:disable.Required work:
Crc::Census::CLI.startinplugins/os-sdlc/bin/crc-censusCrc::Records::CLI.startinplugins/os-sdlc/bin/crc-recordsCrc::Tickets::CLI.startinplugins/os-sdlc/bin/crc-ticketsCrc::Score::CLI.startinplugins/os-sdlc/bin/crc-scoreCrc::Placement::CLI.startinplugins/os-sdlc/bin/crc-placementCrc::Filing::CLI.startinplugins/os-sdlc/bin/crc-fileBoot.runinplugins/os-sdlc/bin/crc-censusBoot.runinplugins/os-sdlc/bin/crc-recordsBoot.runinplugins/os-sdlc/bin/crc-ticketsBoot.runinplugins/os-sdlc/bin/crc-scoreBoot.runinplugins/os-sdlc/bin/crc-placementBoot.runinplugins/os-sdlc/bin/crc-file"crc-inplugins/os-sdlc/lib/os_sdlc/boot/commands.rbcrc_run_dir_inputinplugins/os-sdlc/lib/os_sdlc/boot/inputs.rbcrc_censusinplugins/os-sdlc/lib/os_sdlc/boot/inputs.rbcrc_ticketsinplugins/os-sdlc/lib/os_sdlc/boot/inputs.rbusage: crc-inplugins/os-sdlc/lib/os_sdlc/boot/inputs/usage_text.rbticket_optionsinplugins/os-sdlc/lib/os_sdlc/boot/inputs/crc_argv.rbfor_ledgerinplugins/os-sdlc/lib/os_sdlc/crc/command_result.rbAcceptance criteria
OsSdlc::Crc::Census::CLI.start(["census", "--target", target, run_dir], process: process)reads the tracked files through the repository thatprocess.gitsupplies to the project, prints the census report as pretty JSON to the stdout ofprocess, and returns the status of the report.OsSdlc::Crc::Records::CLI.start(["records", "--target", target, run_dir], process: process)prints the records report as pretty JSON to the stdout ofprocessand returns its status.OsSdlc::Crc::Tickets::CLI.start(["tickets", "--target", target, "--session", id, "--transcript", path, run_dir], process: process)prints the tickets report as pretty JSON to the stdout ofprocessand returns its status.OsSdlc::Crc::Score::CLI.start(["score", "--target", target, run_dir], process: process)prints the score lines (file<TAB>score<TAB>detectors) to the stdout ofprocess, with the same bytes ascrc-scoreprints today, and returns 0.OsSdlc::Crc::Placement::CLI.start(["placement", "--target", target], process: process)prints the placement TSV to the stdout ofprocess, with the same bytes ascrc-placementprints today, and returns its status.OsSdlc::Crc::Filing::CLI.start(["file", "--target", target, "--os-backlog", cmd, "--order", "a,b", tickets_json], process: process)files through the backlog process thatprocessholds, prints the JSON array of filed tickets to the stdout ofprocess, and returns 0 when every ticket is filed and confirmed.Run.new(project, request).callreturns a result whosestatusis an Integer and takes no stream argument, for each ofCrc::Census::Run(with a fake repository assigned toproject.repo),Crc::Records::Run,Crc::Tickets::Run,Crc::Score::Run,Crc::Placement::Run, andCrc::Filing::Run.Crc::Score::Run.new(project, request).callreturns a result withstatus2, nothing for stdout, and the error linecrc-score: missing <file>.Db.connect_project!and withSequel::Model.dbunchanged.startwith--helpafter the command name returns 0, prints help text to the stdout ofprocessthat names the script (crc-censusforCrc::Census::CLI), each positional argument, and each option, and prints nothing to the global$stdout.startwith no--targetreturns 2, prints nothing to the stdout ofprocess, and prints a line that names--targetto the stderr ofprocess.Crc::Tickets::CLI.startwith no--sessionreturns 2 with empty stdout and a stderr line that names--session; with no--transcriptit returns 2 with a stderr line that names--transcript.Crc::Placement::CLI.start(["placement", "--target", target, "extra"], process: process)returns 2 with empty stdout and a non-empty stderr line.Crc::Filing::CLI.startwith no--os-backlogreturns 2 with a stderr line that names--os-backlog; with no--orderit returns 2 with a stderr line that names--order.Crc::Census::CLI.start(["census", "--target", target, "--exclude", "a", "--exclude", "b", "--since", sha, "--loader", file, run_dir, "p1", "p2"], process: process)givesCrc::Census::Runthe excludesaandb, the since commit, the loader file, and the pathsp1andp2, shown through the result of the run.Crc::Records::CLI.startwith--writerewrites the ledger, and without--writeit leaves the ledger unchanged.Boot::Commands::TABLEholds no key that starts withcrc-.ledger.tsvin the run directory, the one ledger check object returns a result withstatus2, nothing for stdout, and the error linecrc-records: no ledger.tsv in <run>for the command namecrc-records, and it does not run its block; with aledger.tsvit returns the reported result of its block.Owner sketch
Each
Crc::<Concept>::CLI(Thor) owns the command name, the arguments, and the options of its one command; it builds the request, hands the run class toCrc::CLI::Invocation, and returns the status.Crc::CLIandCrc::CLI::Invocationkeep what everycrc-*entry shares. EachCrc::<Concept>::Runowns the work for one request and returns the result; it writes nothing.Crc::CommandResultowns the exit status, the text for stdout, and the lines for stderr of one run, for all eleven commands.OsSdlc::Projectowns its repository:Crc::Census::Runreadsproject.repo.Crc::Filing::CLIowns the build of the backlog client fromprocess, until a later ticket moves adapters to the project.Out of scope
Commands that are not
crc-*commands,project.connect(:db), the removal ofBoot, the removal ofgitandbacklog_processfromProcessIO, a new name forBoot::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: six
crc-*commands bind a database they never use and pass aniothey mostly ignore ← every command enters throughBoot.runand one command table ← ADR-0250 (retired habit) put all entries behindBootSession: 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/boot/commands.rb (Boot::Commands::TABLE), plugins/os-sdlc/lib/os_sdlc/crc/census/run.rb (OsSdlc::Crc::Census::Run), plugins/os-sdlc/lib/os_sdlc/crc/filing/run.rb (OsSdlc::Crc::Filing::Run)
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 124 (predicted and orchestrator).
VERDICT: CREATE
REASON: The user named and approved it, and no open ticket covers moving these six crc commands to Thor. #1646 is the broad decision ticket that this slice implements.
Work started on branch ticket-1677 through /os-sdlc:implement.
Resolution
Done: The six commands crc-census, crc-records, crc-tickets, crc-score, crc-placement and crc-file enter through per-concept Thor classes over Crc::CLI. Each Run takes (project, request), receives no stream, and returns Crc::CommandResult. Crc::LedgerGate is the one owner of the ledger check; CommandResult.for_ledger is gone. No crc-* entry remains in Boot.
Evidence: Implementation 275 complete at merge-ready (commit
08be0436bon branch ticket-1677, plus the follow-up test commit). Gates passed: programmer, behavior-verifier, suite, survivors, contract-auditor round 2. Full suite: 4026 runs, 0 failures.Follow-ups: 1) Detector::EntryPoints::InputHops reads only Inputs.method(:name), so the real table (input(:name) form) gives no hops; goes into the Boot-removal ticket, where the detector must follow Thor entries. 2) Evidence-tool delay: recorded on #1678. 3) Runner-run suite and survivor gates with no agent: to be captured through /os-backlog:capture.