Make the survivor loop a mutation-testing step: packet subcommand, brief, and process text #1668

Closed
opened 2026-11-10 13:31:56 +00:00 by jared · 4 comments
Owner

Problem

Survivor tickets #1655-#1664 need the same work as #1654: find each surviving mutant, write a test that kills it, and rule on the rest. On #1654 a loop did this cheaply, but its scripts and briefs exist only as files in a research record. The next ticket must copy them by hand.

Design

/home/jared/dev/cc-os/docs/takedown/1668-20261110-104618/final-design.json

Approach

Add one MutineerInvocation owner of the shared mutineer flags, make Pass::Command, MutineerRunner::Command and the new Pass::Packet::Command thin callers of it, and extend mutation-pass with packet and brief owned by Pass::Packet.

Required work:

  1. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/execution.rb
  2. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet.rb
  3. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet/brief.md
  4. Write plugins/os-sdlc/lib/os_sdlc/boot/inputs.rb
  5. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/mutineer_invocation.rb
  6. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/mutineer_runner/command.rb
  7. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/command.rb
  8. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet/command.rb
  9. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet/brief.rb
  10. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/run.rb
  11. Write plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet/document.rb
  12. Write test plugins/os-sdlc/test/mutation_test/mutineer_invocation_test.rb
  13. Write test plugins/os-sdlc/test/boot_mutation_pass_packet_test.rb
  14. Write test plugins/os-sdlc/test/boot_mutation_pass_brief_test.rb

Acceptance criteria

  • packet: sources and tests, mutineer ok, survivors: packet printed, exit 0
  • some survivors ruled: ruled ones omitted
  • no survivors: heading and Sources/Tests lines only, exit 0
  • no --source or no --test (packet or brief): Loud::Error, exit 3
  • no .mutineer.yml (packet): Loud::Error, exit 3
  • a mutineer run is live: exit 3
  • mutineer exits non-zero: exit 3 with the stderr tail
  • mutineer wrote no report: exit 3
  • brief with sources and tests: brief printed, exit 0, no mutineer run
  • extra positional args: usage, exit 2
  • gate and pass commands: byte-identical strings to base

Owner sketch

Responsibility Owner
Shape of every mutineer command line (run, sources, --test flags, strategy, format, output) MutationTest::MutineerInvocation (new)
Gate flags: --since, --no-matrix MutineerRunner::Command, now a caller
Pass flags: --no-since, matrix, cache dir, operators, and test globbing Pass::Command, now a caller
Packet flags: --no-since --no-matrix, refuse empty sources or tests Pass::Packet::Command
Parse packet and brief argv (--source, --test, repeatable) Boot::Inputs.mutation_pass
Dispatch packet and brief Pass::Run
Run mutineer, refuse a live run, fail loudly on bad exit Pass::Execution#run, shared with #call
Clear the stale report, read it, drop ruled survivors, render Pass::Packet
Render the survivor packet Pass::Packet::Document
Hold the brief text and fill placeholders; unknown placeholder raises Pass::Packet::Brief and brief.md
Parse survivors / ruled keys MutineerRunner::Report, Ledger#ruled_keys, reused

plugins/os-sdlc/test/mutation_test/mutineer_invocation_test.rb

# frozen_string_literal: true

require_relative "../test_helper"

module OsSdlc
  class MutineerInvocationTest < Minitest::Test
    def test_builds_sources_one_test_flag_each_lead_strategy_extra_format_and_output_in_order
      flags = MutationTest::MutineerInvocation::Flags.new(lead: %w[--no-since --no-matrix], extra: %w[--operators a,b])
      command = MutationTest::MutineerInvocation.new(sources: %w[lib/a.rb lib/b],
                                                     tests: %w[test/a_test.rb test/b_test.rb],
                                                     output: ".mutineer/x.json", flags: flags)

      assert_equal "bundle exec mutineer run lib/a.rb lib/b --test test/a_test.rb --test test/b_test.rb --no-since " \
                   "--no-matrix --strategy redefine --operators a,b --format json --output .mutineer/x.json",
                   command.to_s
    end

    def test_extra_defaults_to_nothing
      flags = MutationTest::MutineerInvocation::Flags.new(lead: %w[--since HEAD])
      command = MutationTest::MutineerInvocation.new(sources: %w[lib], tests: %w[test/a_test.rb],
                                                     output: "o.json", flags: flags)

      assert_equal "bundle exec mutineer run lib --test test/a_test.rb --since HEAD --strategy redefine " \
                   "--format json --output o.json", command.to_s
    end
  end
end

plugins/os-sdlc/test/boot_mutation_pass_packet_test.rb

# frozen_string_literal: true

require_relative "test_helper"
require_relative "mutation_test/pass/pass_fixture"

module OsSdlc
  class BootMutationPassPacketTest < TmpdirTestCase
    include PassFixture

    REPORT_PATH = ".mutineer/packet.json"

    def setup
      super
      scaffold_target(@dir)
    end

    def test_packet_prints_the_survivors_grouped_by_file_and_sorted_by_line
      out, err, status = with_mutineer(report: two_survivors) do
        run_pass("packet", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb")
      end

      assert_equal 0, status.exitstatus, err
      assert_equal ["# 2 survivors of 10 (score 80.0, timeouts 1, errors 2)", "",
                    "Sources: lib/order.rb", "Tests: test/order_test.rb", "", "## lib/order.rb",
                    "- [m2] line 1 comparison in Order#total: `a > b` => `a >= b`",
                    "- [m1] line 5 arithmetic in Order#total: `1 + 2` => `1 - 2`"], out.lines(chomp: true)
    end

    def test_packet_runs_the_targeted_command_in_the_target_root
      with_mutineer(report: report) do
        run_pass("packet", @dir, "--source", "lib/order.rb", "--source", "lib/order", "--test", "test/order_test.rb")
      end

      assert_equal "bundle exec mutineer run lib/order.rb lib/order --test test/order_test.rb --no-since --no-matrix " \
                   "--strategy redefine --format json --output #{REPORT_PATH}", @mutineer[:command]
      assert_equal File.expand_path(@dir), @mutineer[:chdir]
    end

    def test_packet_drops_survivors_ruled_in_the_ledger
      write(@dir, ".sdlc/mutation-rulings.tsv",
            "kind\tkey\tverdict\treason\tdate\truled_by\n" \
            "survivor\tm1\tequivalent\tno input differs\t2026-11-10\ttester\n")

      out, = with_mutineer(report: two_survivors) do
        run_pass("packet", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb")
      end

      refute_includes out, "[m1]"
      assert_includes out, "[m2]"
      assert_includes out, "# 1 survivors of 10"
    end

    def test_packet_without_a_source_or_test_exits_three
      _out, err, status = with_mutineer(report: report) { run_pass("packet", @dir, "--test", "test/order_test.rb") }

      assert_equal 3, status.exitstatus
      assert_equal "os-sdlc-mutation-pass: packet needs at least one --source and one --test\n", err
      assert_empty @mutineer
    end

    def test_packet_exits_three_when_mutineer_writes_no_report
      write(@dir, REPORT_PATH, report(survivor("stale", 1, "a", "b")))

      out, err, status = with_mutineer(report: nil) do
        run_pass("packet", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb")
      end

      assert_equal 3, status.exitstatus
      assert_equal "os-sdlc-mutation-pass: mutineer wrote no report at #{File.expand_path(@dir)}/#{REPORT_PATH}\n", err
      refute_includes out, "stale"
    end

    def test_packet_exits_three_when_mutineer_fails
      _out, err, status = with_mutineer(report: nil, exitstatus: 1, stderr: "boom") do
        run_pass("packet", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb")
      end

      assert_equal 3, status.exitstatus
      assert_equal "os-sdlc-mutation-pass: mutineer exited 1: boom\n", err
    end

    private

    def survivor(id, line, was, now)
      { "id" => id, "file" => "lib/order.rb", "line" => line, "subject" => "Order#total",
        "operator" => was.include?(">") ? "comparison" : "arithmetic",
        "diff" => "--- a/lib/order.rb\n+++ b/lib/order.rb\n@@ -1 +1 @@\n-  #{was}\n+  #{now}\n" }
    end

    def two_survivors
      report(survivor("m1", 5, "1 + 2", "1 - 2"), survivor("m2", 1, "a > b", "a >= b"))
    end

    def report(*survivors)
      JSON.generate("summary" => { "attempted" => 10, "killed" => 8, "score" => 80.0, "timeout" => 1, "errored" => 2 },
                    "survivors" => survivors)
    end

    def run_pass(*argv)
      InProcessDispatchRunner.call(@dir, "mutation-pass", *argv, project_shell: @shell)
    end

    PacketShell = Data.define(:seen, :report, :exitstatus, :stderr) do
      def capture3(command, chdir:)
        return ["", "", InProcessDispatchRunner::Status.new(1)] if command.start_with?("pgrep")

        seen.merge!(command: command, chdir: chdir)
        write_report(File.join(chdir, REPORT_PATH)) if report
        ["", stderr, InProcessDispatchRunner::Status.new(exitstatus)]
      end

      def write_report(path)
        FileUtils.mkdir_p(File.dirname(path))
        File.write(path, report)
      end
    end

    def with_mutineer(report:, exitstatus: 0, stderr: "")
      @mutineer = {}
      @shell = PacketShell.new(seen: @mutineer, report: report, exitstatus: exitstatus, stderr: stderr)
      yield
    end
  end
end

plugins/os-sdlc/test/boot_mutation_pass_brief_test.rb

# frozen_string_literal: true

require_relative "test_helper"
require_relative "mutation_test/pass/pass_fixture"

module OsSdlc
  class BootMutationPassBriefTest < TmpdirTestCase
    include PassFixture

    def setup
      super
      scaffold_target(@dir)
    end

    def test_brief_names_the_packet_command_the_tests_and_the_root
      out, err, status = run_pass("brief", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb")
      packet = "os-sdlc-mutation-pass packet #{File.expand_path(@dir)} --source lib/order.rb --test test/order_test.rb"

      assert_equal 0, status.exitstatus, err
      assert_includes out, packet
      assert_includes out, "bundle exec ruby -Itest test/order_test.rb"
      assert_includes out, "Work only in this directory:\n#{File.expand_path(@dir)}"
      refute_includes out, "{{"
    end

    def test_brief_asks_for_a_json_only_reply
      out, = run_pass("brief", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb")

      assert_includes out, "Reply with ONLY this JSON"
      assert_includes out, "equivalent|dead_code|hard"
    end

    def test_brief_without_a_source_or_test_exits_three
      _out, err, status = run_pass("brief", @dir, "--source", "lib/order.rb")

      assert_equal 3, status.exitstatus
      assert_equal "os-sdlc-mutation-pass: packet needs at least one --source and one --test\n", err
    end

    private

    def run_pass(*argv)
      InProcessDispatchRunner.call(@dir, "mutation-pass", *argv, project_shell: Object.new)
    end
  end
end
## Problem Survivor tickets #1655-#1664 need the same work as #1654: find each surviving mutant, write a test that kills it, and rule on the rest. On #1654 a loop did this cheaply, but its scripts and briefs exist only as files in a research record. The next ticket must copy them by hand. ## Design /home/jared/dev/cc-os/docs/takedown/1668-20261110-104618/final-design.json ## Approach Add one MutineerInvocation owner of the shared mutineer flags, make Pass::Command, MutineerRunner::Command and the new Pass::Packet::Command thin callers of it, and extend mutation-pass with `packet` and `brief` owned by Pass::Packet. Required work: 1. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/execution.rb` 2. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet.rb` 3. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet/brief.md` 4. Write `plugins/os-sdlc/lib/os_sdlc/boot/inputs.rb` 5. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/mutineer_invocation.rb` 6. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/mutineer_runner/command.rb` 7. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/command.rb` 8. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet/command.rb` 9. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet/brief.rb` 10. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/run.rb` 11. Write `plugins/os-sdlc/lib/os_sdlc/mutation_test/pass/packet/document.rb` 12. Write test `plugins/os-sdlc/test/mutation_test/mutineer_invocation_test.rb` 13. Write test `plugins/os-sdlc/test/boot_mutation_pass_packet_test.rb` 14. Write test `plugins/os-sdlc/test/boot_mutation_pass_brief_test.rb` ## Acceptance criteria - `packet`: sources and tests, mutineer ok, survivors: packet printed, exit 0 - some survivors ruled: ruled ones omitted - no survivors: heading and Sources/Tests lines only, exit 0 - no `--source` or no `--test` (packet or brief): `Loud::Error`, exit 3 - no `.mutineer.yml` (packet): `Loud::Error`, exit 3 - a mutineer run is live: exit 3 - mutineer exits non-zero: exit 3 with the stderr tail - mutineer wrote no report: exit 3 - `brief` with sources and tests: brief printed, exit 0, no mutineer run - extra positional args: usage, exit 2 - gate and pass commands: byte-identical strings to base ## Owner sketch | Responsibility | Owner | |---|---| | Shape of every mutineer command line (run, sources, `--test` flags, strategy, format, output) | `MutationTest::MutineerInvocation` (new) | | Gate flags: `--since`, `--no-matrix` | `MutineerRunner::Command`, now a caller | | Pass flags: `--no-since`, matrix, cache dir, operators, and test globbing | `Pass::Command`, now a caller | | Packet flags: `--no-since --no-matrix`, refuse empty sources or tests | `Pass::Packet::Command` | | Parse `packet` and `brief` argv (`--source`, `--test`, repeatable) | `Boot::Inputs.mutation_pass` | | Dispatch `packet` and `brief` | `Pass::Run` | | Run mutineer, refuse a live run, fail loudly on bad exit | `Pass::Execution#run`, shared with `#call` | | Clear the stale report, read it, drop ruled survivors, render | `Pass::Packet` | | Render the survivor packet | `Pass::Packet::Document` | | Hold the brief text and fill placeholders; unknown placeholder raises | `Pass::Packet::Brief` and `brief.md` | | Parse survivors / ruled keys | `MutineerRunner::Report`, `Ledger#ruled_keys`, reused | `plugins/os-sdlc/test/mutation_test/mutineer_invocation_test.rb` ```ruby # frozen_string_literal: true require_relative "../test_helper" module OsSdlc class MutineerInvocationTest < Minitest::Test def test_builds_sources_one_test_flag_each_lead_strategy_extra_format_and_output_in_order flags = MutationTest::MutineerInvocation::Flags.new(lead: %w[--no-since --no-matrix], extra: %w[--operators a,b]) command = MutationTest::MutineerInvocation.new(sources: %w[lib/a.rb lib/b], tests: %w[test/a_test.rb test/b_test.rb], output: ".mutineer/x.json", flags: flags) assert_equal "bundle exec mutineer run lib/a.rb lib/b --test test/a_test.rb --test test/b_test.rb --no-since " \ "--no-matrix --strategy redefine --operators a,b --format json --output .mutineer/x.json", command.to_s end def test_extra_defaults_to_nothing flags = MutationTest::MutineerInvocation::Flags.new(lead: %w[--since HEAD]) command = MutationTest::MutineerInvocation.new(sources: %w[lib], tests: %w[test/a_test.rb], output: "o.json", flags: flags) assert_equal "bundle exec mutineer run lib --test test/a_test.rb --since HEAD --strategy redefine " \ "--format json --output o.json", command.to_s end end end ``` `plugins/os-sdlc/test/boot_mutation_pass_packet_test.rb` ```ruby # frozen_string_literal: true require_relative "test_helper" require_relative "mutation_test/pass/pass_fixture" module OsSdlc class BootMutationPassPacketTest < TmpdirTestCase include PassFixture REPORT_PATH = ".mutineer/packet.json" def setup super scaffold_target(@dir) end def test_packet_prints_the_survivors_grouped_by_file_and_sorted_by_line out, err, status = with_mutineer(report: two_survivors) do run_pass("packet", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb") end assert_equal 0, status.exitstatus, err assert_equal ["# 2 survivors of 10 (score 80.0, timeouts 1, errors 2)", "", "Sources: lib/order.rb", "Tests: test/order_test.rb", "", "## lib/order.rb", "- [m2] line 1 comparison in Order#total: `a > b` => `a >= b`", "- [m1] line 5 arithmetic in Order#total: `1 + 2` => `1 - 2`"], out.lines(chomp: true) end def test_packet_runs_the_targeted_command_in_the_target_root with_mutineer(report: report) do run_pass("packet", @dir, "--source", "lib/order.rb", "--source", "lib/order", "--test", "test/order_test.rb") end assert_equal "bundle exec mutineer run lib/order.rb lib/order --test test/order_test.rb --no-since --no-matrix " \ "--strategy redefine --format json --output #{REPORT_PATH}", @mutineer[:command] assert_equal File.expand_path(@dir), @mutineer[:chdir] end def test_packet_drops_survivors_ruled_in_the_ledger write(@dir, ".sdlc/mutation-rulings.tsv", "kind\tkey\tverdict\treason\tdate\truled_by\n" \ "survivor\tm1\tequivalent\tno input differs\t2026-11-10\ttester\n") out, = with_mutineer(report: two_survivors) do run_pass("packet", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb") end refute_includes out, "[m1]" assert_includes out, "[m2]" assert_includes out, "# 1 survivors of 10" end def test_packet_without_a_source_or_test_exits_three _out, err, status = with_mutineer(report: report) { run_pass("packet", @dir, "--test", "test/order_test.rb") } assert_equal 3, status.exitstatus assert_equal "os-sdlc-mutation-pass: packet needs at least one --source and one --test\n", err assert_empty @mutineer end def test_packet_exits_three_when_mutineer_writes_no_report write(@dir, REPORT_PATH, report(survivor("stale", 1, "a", "b"))) out, err, status = with_mutineer(report: nil) do run_pass("packet", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb") end assert_equal 3, status.exitstatus assert_equal "os-sdlc-mutation-pass: mutineer wrote no report at #{File.expand_path(@dir)}/#{REPORT_PATH}\n", err refute_includes out, "stale" end def test_packet_exits_three_when_mutineer_fails _out, err, status = with_mutineer(report: nil, exitstatus: 1, stderr: "boom") do run_pass("packet", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb") end assert_equal 3, status.exitstatus assert_equal "os-sdlc-mutation-pass: mutineer exited 1: boom\n", err end private def survivor(id, line, was, now) { "id" => id, "file" => "lib/order.rb", "line" => line, "subject" => "Order#total", "operator" => was.include?(">") ? "comparison" : "arithmetic", "diff" => "--- a/lib/order.rb\n+++ b/lib/order.rb\n@@ -1 +1 @@\n- #{was}\n+ #{now}\n" } end def two_survivors report(survivor("m1", 5, "1 + 2", "1 - 2"), survivor("m2", 1, "a > b", "a >= b")) end def report(*survivors) JSON.generate("summary" => { "attempted" => 10, "killed" => 8, "score" => 80.0, "timeout" => 1, "errored" => 2 }, "survivors" => survivors) end def run_pass(*argv) InProcessDispatchRunner.call(@dir, "mutation-pass", *argv, project_shell: @shell) end PacketShell = Data.define(:seen, :report, :exitstatus, :stderr) do def capture3(command, chdir:) return ["", "", InProcessDispatchRunner::Status.new(1)] if command.start_with?("pgrep") seen.merge!(command: command, chdir: chdir) write_report(File.join(chdir, REPORT_PATH)) if report ["", stderr, InProcessDispatchRunner::Status.new(exitstatus)] end def write_report(path) FileUtils.mkdir_p(File.dirname(path)) File.write(path, report) end end def with_mutineer(report:, exitstatus: 0, stderr: "") @mutineer = {} @shell = PacketShell.new(seen: @mutineer, report: report, exitstatus: exitstatus, stderr: stderr) yield end end end ``` `plugins/os-sdlc/test/boot_mutation_pass_brief_test.rb` ```ruby # frozen_string_literal: true require_relative "test_helper" require_relative "mutation_test/pass/pass_fixture" module OsSdlc class BootMutationPassBriefTest < TmpdirTestCase include PassFixture def setup super scaffold_target(@dir) end def test_brief_names_the_packet_command_the_tests_and_the_root out, err, status = run_pass("brief", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb") packet = "os-sdlc-mutation-pass packet #{File.expand_path(@dir)} --source lib/order.rb --test test/order_test.rb" assert_equal 0, status.exitstatus, err assert_includes out, packet assert_includes out, "bundle exec ruby -Itest test/order_test.rb" assert_includes out, "Work only in this directory:\n#{File.expand_path(@dir)}" refute_includes out, "{{" end def test_brief_asks_for_a_json_only_reply out, = run_pass("brief", @dir, "--source", "lib/order.rb", "--test", "test/order_test.rb") assert_includes out, "Reply with ONLY this JSON" assert_includes out, "equivalent|dead_code|hard" end def test_brief_without_a_source_or_test_exits_three _out, err, status = run_pass("brief", @dir, "--source", "lib/order.rb") assert_equal 3, status.exitstatus assert_equal "os-sdlc-mutation-pass: packet needs at least one --source and one --test\n", err end private def run_pass(*argv) InProcessDispatchRunner.call(@dir, "mutation-pass", *argv, project_shell: Object.new) end end end ```
Author
Owner

Decompose routing (ticket-decompose, 2026-11-10): routed to /os-sdlc:ticket-takedown 1668.

a. One change: a survivors subcommand on MutationTest::Pass::Run (dispatch is public_send(subcommand), pass/run.rb:11) that runs a targeted mutineer run <sources> --test <tests> --no-matrix and writes one survivor packet per source group.

b. Owner: no existing object renders a packet. New object: a packet that turns mutineer survivor rows (file, line, id, operator, subject, diff) into the markdown the brief reads, with the test file each survivor belongs in. Command building stays with Pass::Command; argv stays with Boot::Inputs.mutation_pass (MUTATION_PASS_ARITY and MUTATION_PASS_USAGE need a row).

Facts for the takedown:

  • MutationTest::Overlap::OwnerTest#for (overlap/owner_test.rb:20) maps lib/os_sdlc/x.rb to test/x_test.rb. It drops the namespace directory. Ruling 100 puts the paired test at test/os_sdlc/<path>_test.rb, and ruling 105 puts cop tests at test/cops/<cop>_test.rb. The source-to-test mapping must follow the rulings; OwnerTest does not today.
  • Mutant has kind; mutineer rows carry operator.
  • A top-level survivors command already exists (boot/commands.rb:70, bin/os-sdlc-survivors, MutationTest::Check). The new subcommand name is mutation-pass survivors; the takedown should confirm the two names do not confuse.
  • No os-sdlc skill or reference describes the pass today; required work item 3 creates the first one.
  • Research scripts: docs/research/mutation-testing/2026-11-10-survivor-loop/ (cop_packet.rb is the packet template).
Decompose routing (ticket-decompose, 2026-11-10): routed to `/os-sdlc:ticket-takedown 1668`. a. One change: a `survivors` subcommand on `MutationTest::Pass::Run` (dispatch is `public_send(subcommand)`, `pass/run.rb:11`) that runs a targeted `mutineer run <sources> --test <tests> --no-matrix` and writes one survivor packet per source group. b. Owner: no existing object renders a packet. New object: a packet that turns mutineer survivor rows (`file`, `line`, `id`, `operator`, `subject`, `diff`) into the markdown the brief reads, with the test file each survivor belongs in. Command building stays with `Pass::Command`; argv stays with `Boot::Inputs.mutation_pass` (`MUTATION_PASS_ARITY` and `MUTATION_PASS_USAGE` need a row). Facts for the takedown: - `MutationTest::Overlap::OwnerTest#for` (`overlap/owner_test.rb:20`) maps `lib/os_sdlc/x.rb` to `test/x_test.rb`. It drops the namespace directory. Ruling 100 puts the paired test at `test/os_sdlc/<path>_test.rb`, and ruling 105 puts cop tests at `test/cops/<cop>_test.rb`. The source-to-test mapping must follow the rulings; OwnerTest does not today. - `Mutant` has `kind`; mutineer rows carry `operator`. - A top-level `survivors` command already exists (`boot/commands.rb:70`, `bin/os-sdlc-survivors`, `MutationTest::Check`). The new subcommand name is `mutation-pass survivors`; the takedown should confirm the two names do not confuse. - No os-sdlc skill or reference describes the pass today; required work item 3 creates the first one. - Research scripts: `docs/research/mutation-testing/2026-11-10-survivor-loop/` (`cop_packet.rb` is the packet template).
Author
Owner

Takedown run docs/takedown/1668-20261110-093202 failed at green-suite: (1) the final design leaves Sdlc/Structural/ParameterThreadedThroughPrivateMethods at mutation_test/pass/run.rb:54 and Metrics/PerceivedComplexity at boot/inputs.rb:94; (2) main itself fails the green-suite command (no bundler, system rubocop 1.92.2) with 12 cop tests from #1654. Blocked by #1669. Rerun the takedown after #1669.

Takedown run docs/takedown/1668-20261110-093202 failed at green-suite: (1) the final design leaves Sdlc/Structural/ParameterThreadedThroughPrivateMethods at mutation_test/pass/run.rb:54 and Metrics/PerceivedComplexity at boot/inputs.rb:94; (2) main itself fails the green-suite command (no bundler, system rubocop 1.92.2) with 12 cop tests from #1654. Blocked by #1669. Rerun the takedown after #1669.
Author
Owner

Work started on branch ticket-1668 (os-sdlc implement pipeline, map takedown-implement, design docs/takedown/1668-20261110-104618).

Work started on branch ticket-1668 (os-sdlc implement pipeline, map takedown-implement, design docs/takedown/1668-20261110-104618).
Author
Owner

Resolution

Done: mutation-pass gains packet and brief; MutineerInvocation owns the mutineer command line for gate, pass, and packet; references/survivor-loop.md holds the loop steps and the ruling 107 delete rule

Evidence: implementation 276 implementation_complete (8 dispatches, 4 min 52 s); commits 5ad52f7f7, 0363e9575; merge of main effa99be2; suite 3977 runs, 0 failures

Follow-ups: none

## Resolution **Done:** mutation-pass gains packet and brief; MutineerInvocation owns the mutineer command line for gate, pass, and packet; references/survivor-loop.md holds the loop steps and the ruling 107 delete rule **Evidence:** implementation 276 implementation_complete (8 dispatches, 4 min 52 s); commits 5ad52f7f7, 0363e9575; merge of main effa99be2; suite 3977 runs, 0 failures **Follow-ups:** none <!-- os-backlog kind: resolution -->
jared closed this issue 2026-11-10 20:52:35 +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#1668
No description provided.