Migrate remaining case/when bins to Thor (follow-on to #422) #423
Labels
No labels
P0
P1
P2
P3
bug
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
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#423
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?
Context
ADR-0123 (amending ADR-0025) moves plugin bin CLIs onto Thor. #422 converts bin/os-backlog and bin/decision-sweep as the reference implementation; this ticket migrates the remaining hand-rolled case/when bins: os-sdlc, os-sdlc-runner, os-sdlc-workspace, wakeup-poll, adr-find.
Blocked by #422 — the reference conversion establishes the entrypoint + lib/cli.rb pattern to copy.
Tasks
Acceptance criteria
Origin
#423 decomposition — Thor migration of remaining case/when bins (per ADR-0123, reference conversion #422)
Decomposed 2026-08-17 via parallel per-bin investigation agents. Decisions locked with the user:
--target: Thorclass_option :target, withOS_SDLC_TARGETenv fallback resolved in command bodies (flag wins over env), never as a Thor option default where precedence is tested.lint-worklist,dead-code-sweep,dup-sweep— no case/when dispatch, outside ADR-0123's scope language.exit_on_failure?/error handling must never remap these to 1. Every bin's conversion carries explicit exit-code tests.rescue LoadError→ "gem install thor" guard frombin/os-backlog.bin/refresh-plugins.1. os-sdlc-workspace (plugins/os-sdlc/bin/os-sdlc-workspace)
plugins/os-sdlc/lib/os_sdlc/workspace_cli.rb—OsSdlc::WorkspaceCLI < Thor, mirroringplugins/os-backlog/lib/backlog/cli.rb(exit_on_failure?,self.startoverride with rescue).require_relative "../lib/os_sdlc", thor-guarded require of the CLI,OsSdlc::WorkspaceCLI.start(ARGV).TextIssueSourceintolib/os_sdlc/text_issue_source.rb.load_or_bootstrap_project(command, target)into the CLI as a private method; keep the bootstrap-only-on-setupguard exactly.setup <ticket>:method_option :ticket_text,:ticket_file,:target(env fallbackOS_SDLC_TARGET); preserve the if/elsif issue_source chain,Workspace.for,puts workspace.setup.teardown <ticket>:method_option :force, type: :boolean, default: false;tear_down_without_checkvstear_downper--force.rescue OsSdlc::HarnessError→ warnos-sdlc-workspace: <msg>+ exit 3 around both command bodies (or inself.start).tests/os_sdlc_workspace_bin_test.rb(+tests/support/multi_target_repo_scenario.rbfixture). Dirty-worktree refusal test is the sharpest regression signal.WorkspaceCLI: setup with--ticket-text/--ticket-file/neither/--target; teardown ±--force; HarnessError→exit-3 in-process.2. os-sdlc-runner (plugins/os-sdlc/bin/os-sdlc-runner)
plugins/os-sdlc/lib/os_sdlc/runner/cli.rb—OsSdlc::Runner::CLI < Thor, top-level StandardError rescue inself.startpreserving the exactos-sdlc-runner: harness error: <class>: <msg>+ exit 3 format.build_engine,resolved_target,emit,extract_flag, thevalidated_*helpers,recover_actioninto the CLI as private methods.intake <ticket>: options:map,:target(explicit flag wins overOS_SDLC_TARGETenv — precedence tested bycli_target_argument_test.rb; resolve in body, not option default).resume <ticket>::actionrequired (retry|retry-with-guidance|skip|abort, dash→underscore symbol conversion preserved),:note,:target.recover(no positional): booleans:rollback,:reemit, plus:target; preserverecover_actionselection behavior exactly.--action— keep explicitwarn+exit 3in command bodies; never let Thor remap to 1.$LOAD_PATH.unshift+require "os_sdlc"; must remainload-able (no__FILE__ == $0guard) foradapter_wiring_test.rb's in-processload BINdriver.emitkeepsputs JSON.generate(result); exit 0.tests/runner/cli_map_argument_test.rb,cli_target_argument_test.rb,cli_resume_argument_test.rb,cli_recover_argument_test.rb,adapter_wiring_test.rb.EngineFactory, bad--action, missing ticket id,--targetvs env precedence, StandardError→exit-3.recover's boolean options don't gain conflicting--no-*negations vs test expectations.3. adr-find (plugins/os-adr/bin/adr-find)
plugins/os-adr/lib/adr/cli.rb—Adr::CLI < Thorwith afindcommand.:root(defaultDir.pwd),:paths,:components— keep comma-string + manual.split(',')(NOT Thortype: :array, which is space-split) so the invocation inskills/find/SKILL.mdstays byte-identical;:"all-statuses"boolean.Repository/Finder/JSON.pretty_generatebody verbatim;Adr::Finder/Adr::Repositoryuntouched.{"candidates": [...]}via pretty_generate) — it feeds the/os-adr:findskill's judgment layer directly. Verify no Thor banner leakage.adr-new/adr-init/adr-migrate/adr-detectare grandfathered (ADR-0025; ADR-0123 affected-paths names adr-find singular).--paths,--components,--all-statuses, empty-results shape, registry-driven help. Register intests/all.rbif not auto-globbed.tests/finder_test.rbpasses unmodified; manually confirm output byte-compat with the documented skill invocation.4. wakeup-poll (plugins/os-backlog/bin/wakeup-poll)
Backlog::WakeupCLIinlib/backlog/wakeup_cli.rb) — NOT folded intoBacklog::CLI(ADR-0025 exemption, ADR-0034/35/36 pilot; converting internals is in scope, merging is not).list_issues_cmd,label_names,normalize_labels,normalize_issue_row,list_issues, eligibility/spawn loop) into the CLI or aBacklog::WakeupRunorchestration class;Backlog::Wakeup(pure, tested) stays untouched and Thor-unaware.pollcommand;method_option :spawn, type: :boolean, default: false.Open3.capture3tea/gh shell-out (parallel toBacklog::CLI.runner) — required for unit tests.bin/os-backlogpattern.note(...)output (dispatch-only policy); document the deviation inline.State#record+ summary append gated on--spawnonly. Dry-run must print "would run"/"would append" and touch nothing. Test both sides.--spawnparsing/default, dry-run no-mutation, spawn-mode state write + summary append, first-scan baselining without waking, degrade-on-failed-listing, gh-vs-tea row normalization.tests/wakeup_test.rbpasses unchanged.ARGV.include?("--spawn")parsing (header says no cron until ADR-0035 Accepted — likely none).plugins/os-backlog/CLAUDE.mdif wording goes stale; one-row History entry indocs/implementation-status/os-backlog.md.5. os-sdlc (plugins/os-sdlc/bin/os-sdlc) — largest, last
plugins/os-sdlc/lib/os_sdlc/cli.rb—OsSdlc::CLI < Thor.$LOAD_PATHsetup,require "os_sdlc", thor-guarded CLI require,CLI.start(ARGV).lib/os_sdlc/(GateCommand,AcLintCommand, etc., following #422'sInspectCommandsplit); CLI methods stay thin.--target: Thorclass_option :target(decision locked);load_project/SELECTED_TARGETmove from load-time module side effect into command execution. Confirm nothing reads a pre-set top-levelSELECTED_TARGET.desc+mapper theBacklog::CLIconvention):intake,red-assert,green-assert(two Thor commands sharing a private gate helper),ac-lint,verdict-assert,record-verdict,lint,lint-init,project-config(bare--showbecomes an option),preflight,review-packet,shard-plan,workspace-housekeeping,autocorrect,agent-budget.shard-plan/workspace-housekeepingpass raw ARGV to lib classes that self-parse — verify and likely keep passthrough shape rather than Thor options; explicit check, not assumption.*_OPTION_SPECS/*_DEFAULTSOptionParser pair tomethod_optiondeclarations; verify--no-lintboolean negation behaves under Thor's--no-convention.intake <ticket>,lint <run_id>,project-config <name>,preflight <ticket_path>, with existing fail_soft-style validation.rescue StandardError→os-sdlc: harness error: ...+ exit 3 inself.start. Representative unit test per code.agent-budget/autocorrectkeep JSON,lint/preflight/review-packet/project-config --showkeep plain text.tests/cli_test.rb,cli_intake_test.rb,hardening_preflight_review_packet_test.rb,lint_test.rb,lint_worklist_target_test.rb,agent_budget_test.rb,autocorrect_prepass_test.rb,cops/bin_script_load_in_test_test.rb(check it doesn't assert the old case/when shape), viatests/support/cli_shared_helpers.rb'sBINharness.ac_lint,record_verdict,project_config,verdict_assert) and the chosen--targetmechanism.#423 decomposition — Thor migration of remaining case/when bins (per ADR-0123, reference conversion #422)
Decomposed 2026-08-17 via parallel per-bin investigation agents. Decisions locked with the user:
--target: Thorclass_option :target, withOS_SDLC_TARGETenv fallback resolved in command bodies (flag wins over env), never as a Thor option default where precedence is tested.lint-worklist,dead-code-sweep,dup-sweep— no case/when dispatch, outside ADR-0123's scope language.exit_on_failure?/error handling must never remap these to 1. Every bin's conversion carries explicit exit-code tests.rescue LoadError→ "gem install thor" guard frombin/os-backlog.bin/refresh-plugins.1. os-sdlc-workspace (plugins/os-sdlc/bin/os-sdlc-workspace)
plugins/os-sdlc/lib/os_sdlc/workspace_cli.rb—OsSdlc::WorkspaceCLI < Thor, mirroringplugins/os-backlog/lib/backlog/cli.rb(exit_on_failure?,self.startoverride with rescue).require_relative "../lib/os_sdlc", thor-guarded require of the CLI,OsSdlc::WorkspaceCLI.start(ARGV).TextIssueSourceintolib/os_sdlc/text_issue_source.rb.load_or_bootstrap_project(command, target)into the CLI as a private method; keep the bootstrap-only-on-setupguard exactly.setup <ticket>:method_option :ticket_text,:ticket_file,:target(env fallbackOS_SDLC_TARGET); preserve the if/elsif issue_source chain,Workspace.for,puts workspace.setup.teardown <ticket>:method_option :force, type: :boolean, default: false;tear_down_without_checkvstear_downper--force.rescue OsSdlc::HarnessError→ warnos-sdlc-workspace: <msg>+ exit 3 around both command bodies (or inself.start).tests/os_sdlc_workspace_bin_test.rb(+tests/support/multi_target_repo_scenario.rbfixture). Dirty-worktree refusal test is the sharpest regression signal.WorkspaceCLI: setup with--ticket-text/--ticket-file/neither/--target; teardown ±--force; HarnessError→exit-3 in-process.2. os-sdlc-runner (plugins/os-sdlc/bin/os-sdlc-runner)
plugins/os-sdlc/lib/os_sdlc/runner/cli.rb—OsSdlc::Runner::CLI < Thor, top-level StandardError rescue inself.startpreserving the exactos-sdlc-runner: harness error: <class>: <msg>+ exit 3 format.build_engine,resolved_target,emit,extract_flag, thevalidated_*helpers,recover_actioninto the CLI as private methods.intake <ticket>: options:map,:target(explicit flag wins overOS_SDLC_TARGETenv — precedence tested bycli_target_argument_test.rb; resolve in body, not option default).resume <ticket>::actionrequired (retry|retry-with-guidance|skip|abort, dash→underscore symbol conversion preserved),:note,:target.recover(no positional): booleans:rollback,:reemit, plus:target; preserverecover_actionselection behavior exactly.--action— keep explicitwarn+exit 3in command bodies; never let Thor remap to 1.$LOAD_PATH.unshift+require "os_sdlc"; must remainload-able (no__FILE__ == $0guard) foradapter_wiring_test.rb's in-processload BINdriver.emitkeepsputs JSON.generate(result); exit 0.tests/runner/cli_map_argument_test.rb,cli_target_argument_test.rb,cli_resume_argument_test.rb,cli_recover_argument_test.rb,adapter_wiring_test.rb.EngineFactory, bad--action, missing ticket id,--targetvs env precedence, StandardError→exit-3.recover's boolean options don't gain conflicting--no-*negations vs test expectations.3. adr-find (plugins/os-adr/bin/adr-find)
plugins/os-adr/lib/adr/cli.rb—Adr::CLI < Thorwith afindcommand.:root(defaultDir.pwd),:paths,:components— keep comma-string + manual.split(',')(NOT Thortype: :array, which is space-split) so the invocation inskills/find/SKILL.mdstays byte-identical;:"all-statuses"boolean.Repository/Finder/JSON.pretty_generatebody verbatim;Adr::Finder/Adr::Repositoryuntouched.{"candidates": [...]}via pretty_generate) — it feeds the/os-adr:findskill's judgment layer directly. Verify no Thor banner leakage.adr-new/adr-init/adr-migrate/adr-detectare grandfathered (ADR-0025; ADR-0123 affected-paths names adr-find singular).--paths,--components,--all-statuses, empty-results shape, registry-driven help. Register intests/all.rbif not auto-globbed.tests/finder_test.rbpasses unmodified; manually confirm output byte-compat with the documented skill invocation.4. wakeup-poll (plugins/os-backlog/bin/wakeup-poll)
Backlog::WakeupCLIinlib/backlog/wakeup_cli.rb) — NOT folded intoBacklog::CLI(ADR-0025 exemption, ADR-0034/35/36 pilot; converting internals is in scope, merging is not).list_issues_cmd,label_names,normalize_labels,normalize_issue_row,list_issues, eligibility/spawn loop) into the CLI or aBacklog::WakeupRunorchestration class;Backlog::Wakeup(pure, tested) stays untouched and Thor-unaware.pollcommand;method_option :spawn, type: :boolean, default: false.Open3.capture3tea/gh shell-out (parallel toBacklog::CLI.runner) — required for unit tests.bin/os-backlogpattern.note(...)output (dispatch-only policy); document the deviation inline.State#record+ summary append gated on--spawnonly. Dry-run must print "would run"/"would append" and touch nothing. Test both sides.--spawnparsing/default, dry-run no-mutation, spawn-mode state write + summary append, first-scan baselining without waking, degrade-on-failed-listing, gh-vs-tea row normalization.tests/wakeup_test.rbpasses unchanged.ARGV.include?("--spawn")parsing (header says no cron until ADR-0035 Accepted — likely none).plugins/os-backlog/CLAUDE.mdif wording goes stale; one-row History entry indocs/implementation-status/os-backlog.md.5. os-sdlc (plugins/os-sdlc/bin/os-sdlc) — largest, last
plugins/os-sdlc/lib/os_sdlc/cli.rb—OsSdlc::CLI < Thor.$LOAD_PATHsetup,require "os_sdlc", thor-guarded CLI require,CLI.start(ARGV).lib/os_sdlc/(GateCommand,AcLintCommand, etc., following #422'sInspectCommandsplit); CLI methods stay thin.--target: Thorclass_option :target(decision locked);load_project/SELECTED_TARGETmove from load-time module side effect into command execution. Confirm nothing reads a pre-set top-levelSELECTED_TARGET.desc+mapper theBacklog::CLIconvention):intake,red-assert,green-assert(two Thor commands sharing a private gate helper),ac-lint,verdict-assert,record-verdict,lint,lint-init,project-config(bare--showbecomes an option),preflight,review-packet,shard-plan,workspace-housekeeping,autocorrect,agent-budget.shard-plan/workspace-housekeepingpass raw ARGV to lib classes that self-parse — verify and likely keep passthrough shape rather than Thor options; explicit check, not assumption.*_OPTION_SPECS/*_DEFAULTSOptionParser pair tomethod_optiondeclarations; verify--no-lintboolean negation behaves under Thor's--no-convention.intake <ticket>,lint <run_id>,project-config <name>,preflight <ticket_path>, with existing fail_soft-style validation.rescue StandardError→os-sdlc: harness error: ...+ exit 3 inself.start. Representative unit test per code.agent-budget/autocorrectkeep JSON,lint/preflight/review-packet/project-config --showkeep plain text.tests/cli_test.rb,cli_intake_test.rb,hardening_preflight_review_packet_test.rb,lint_test.rb,lint_worklist_target_test.rb,agent_budget_test.rb,autocorrect_prepass_test.rb,cops/bin_script_load_in_test_test.rb(check it doesn't assert the old case/when shape), viatests/support/cli_shared_helpers.rb'sBINharness.ac_lint,record_verdict,project_config,verdict_assert) and the chosen--targetmechanism.Work starting on branch worktree-ticket-423-os-sdlc-corrections (os-sdlc target only this session; other targets deferred to a follow-up session).
ticket 423 escalated from lint
Session 1 progress (os-sdlc target only, per plan):
DONE — all three os-sdlc bins converted to Thor entrypoint + lib CLI (ADR-0123 pattern): bin/os-sdlc → OsSdlc::CLI (lib/os_sdlc/cli.rb), bin/os-sdlc-runner → OsSdlc::Runner::CLI (lib/os_sdlc/runner/cli.rb), bin/os-sdlc-workspace → OsSdlc::WorkspaceCLI (lib/os_sdlc/workspace_cli.rb). New static test tests/bin_thor_migration_test.rb. Suite green: 963 runs, 2366 assertions, 0 failures. Commit
752d384on worktree-ticket-423-os-sdlc-corrections.SCOPE NOTE — adr-find (os-adr) and wakeup-poll (os-backlog) are NOT case/when dispatchers and both carry explicit do-not-drive-by grandfather clauses in their plugins' CLAUDE.md; they stay out of this ticket's os-sdlc portion. Remaining targets deferred to next session.
REMAINING — lint gate + review stage of the pipeline, then merge via /os:worktree-finish.
Session 2 resumed on branch worktree-ticket-423-os-sdlc-corrections. Thor conversion committed as
752d384(963 runs / 0 failures). Now running the lint wave (bins autocorrected; workspace_cli/runner cli/cli line-length via lint-fixer rounds), then reviewer stage. Note: adr-find and wakeup-poll carry explicit CLAUDE.md grandfather clauses and are not case/when dispatchers — proposing they be descoped from this ticket.ticket 423 escalated from lint
ticket 423 escalated from lint
Resolution
Done: os-sdlc, os-sdlc-runner, os-sdlc-workspace converted to thin Thor entrypoints with dispatch in OsSdlc::CLI / OsSdlc::Runner::CLI / OsSdlc::WorkspaceCLI (ADR-0123). adr-find and wakeup-poll DESCOPED: both carry explicit CLAUDE.md grandfather clauses and are not case/when dispatchers — the ticket premise did not apply to them.
Evidence: Commits
752d384,c7f875f,d808556on branch worktree-ticket-423-os-sdlc-corrections; suite 963 runs / 2366 assertions / 0 failures; os-sdlc reviewer APPROVE verdict (round 2) in .sdlc/tickets/423/verdict.md; pipeline report briefs/011-review-to-done.md; migration test asserts each CLI class is a Thor subclass.Follow-ups: #424 (decompose OsSdlc::CLI structural lint debt, ticket-skeptic CREATE 2026-08-17); adr-find/wakeup-poll conversion explicitly dropped (grandfathered per CLAUDE.md clauses).