test-writer rewrites Retired tests into absence tests instead of deleting them; behavior-verifier passes anyway #613

Closed
opened 2026-09-18 13:42:53 +00:00 by jared · 2 comments
Owner

Context

A ticket's ## Retired tests section lists entries of two shapes: a bare path retires a whole file, path#method retires one test. Pipeline::RetiredTest splits on #.

On #607 implementation 3 (session 4974deff-0140-4813-8cd1-e96c6356a214) the ticket listed three file entries. The test-writer tried to delete them and backed off. Its handoff: "the diff-test gate require_relatives every changed test path including a removed one, which produced an unconditional LoadError." It rewrote each file into a one-assertion absence test instead. test-reviewer and behavior-verifier passed although the acceptance criterion grep -r lsp plugins/os/lib plugins/os/tests prints nothing was unmet. The main loop deleted the files by hand.

The agent's charter already permits deletion (agents/test-writer.md: "rewrite or delete each listed path#method"). The gate forbade it in practice. Blocked by #614, which lands first because it edits the same diff_test_command.

Change

File entries are retired by the harness at implementation-open, before any dispatch. Method entries stay with the test-writer, and a deterministic gate step refuses to go green while any entry is still present. The charter line loses its condition.

# plugins/os-sdlc/lib/os_sdlc/pipeline/retired_test.rb
class RetiredTest
  def initialize(entry)
    @path, @method_name = entry.split("#", 2)
  end

  def file? = @method_name.nil?

  def exists?(root)
    return false unless root

    full = File.join(root, @path)
    File.exist?(full) && File.read(full).include?(@method_name.to_s)
  end

  def delete!(root)
    File.delete(File.join(root, @path)) if file?
  end
end
# plugins/os-sdlc/lib/os_sdlc/pipeline/open_implementation.rb
def ticket_body
  body = @tracker.fetch_body(@work_order.ticket)
  check_ticket_shape!(body)
  retire_test_files!(body)
  body
end

def retire_test_files!(body)
  return unless ticket_map?

  TicketShape.new(body, root: @root).retired_tests.each { |entry| RetiredTest.new(entry).delete!(@root) }
end
# plugins/os-sdlc/lib/os_sdlc/pipeline/gate_commands.rb
def diff_test_command
  return suite_command if changes.unmapped_code_paths.any?

  test_paths = changes.test_paths.select { |path| File.exist?(path) }
  ...
end

def retired_tests_command
  entries = TicketShape.new(implementation.ticket_body).retired_tests
  return nil if entries.empty?

  "os-sdlc-runner retired-tests-check #{implementation.id}"
end

os-sdlc-runner retired-tests-check IMPLEMENTATION_ID runs RetiredTest#exists? on every entry of that implementation's ticket body. Exit 0 when none remain. Exit 1 with one line per entry still present:

retired test still present: plugins/os/tests/foo_test.rb#test_bar -- remove the method

The step retired-tests joins the test-writer node's gate list in maps/poodr-ticket-implementation.yaml.

Charter, agents/test-writer.md, replaces the retired_tests sentence with: "File entries under retired_tests are already deleted. Remove each listed path#method in this round."

Tasks

  • RetiredTest#file? and #delete!.
  • OpenImplementation deletes file entries after the shape check, ticket map only.
  • diff_test_command filters test_paths to files that exist.
  • retired-tests-check CLI subcommand; GateCommands#retired_tests_command; retired-tests step on the test-writer node gate in the ticket map.
  • Charter sentence replaced in agents/test-writer.md.
  • Run ruby plugins/os-sdlc/tests/all.rb; green.

Acceptance criteria

  • After implementation-open on a ticket map, every file entry under ## Retired tests is gone from the worktree.
  • A deleted test path never appears in the diff-test require list.
  • The test-writer gate is red with the prescriptive line while a path#method entry is still present, green when it is gone.

Owner sketch

module OsSdlc
  module Pipeline
    class RetiredTest
      def file?
      def exists?(root)
      def delete!(root)
    end
    class OpenImplementation
      def call
    end
    class GateCommands
      def diff_test_command
      def retired_tests_command
    end
  end
end

Cases

  1. Given a retired-tests entry tests/a_test.rb, when RetiredTest#delete!(root) runs, then the file is gone; given tests/a_test.rb#test_x, delete! leaves the file in place.
  2. Given a ticket map and a body listing one file entry that exists, when OpenImplementation#call runs, then the file is deleted before the open payload returns.
  3. Given a diff that deletes a test file, when diff_test_command is built, then the deleted path is not in the require list.
  4. Given an implementation whose ticket lists tests/a_test.rb#test_x and the method still exists, when retired-tests-check runs, then it exits 1 and prints retired test still present: tests/a_test.rb#test_x -- remove the method; when the method is gone, it exits 0.
  5. Given the ticket map, when the test-writer node's gate list is read, then it contains the retired-tests step.

Origin

  • Trigger: /os-sdlc:implement #607, implementation 3, test-writer dispatch 20
  • Session: 4974deff-0140-4813-8cd1-e96c6356a214
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/4974deff-0140-4813-8cd1-e96c6356a214.jsonl
  • Where: plugins/os-sdlc/lib/os_sdlc/pipeline/retired_test.rb, open_implementation.rb, gate_commands.rb, agents/test-writer.md
  • Design approved by user 2026-09-18; body rewritten with code sketch.
## Context A ticket's `## Retired tests` section lists entries of two shapes: a bare path retires a whole file, `path#method` retires one test. `Pipeline::RetiredTest` splits on `#`. On #607 implementation 3 (session 4974deff-0140-4813-8cd1-e96c6356a214) the ticket listed three file entries. The test-writer tried to delete them and backed off. Its handoff: "the diff-test gate require_relatives every changed test path including a removed one, which produced an unconditional LoadError." It rewrote each file into a one-assertion absence test instead. test-reviewer and behavior-verifier passed although the acceptance criterion `grep -r lsp plugins/os/lib plugins/os/tests` prints nothing was unmet. The main loop deleted the files by hand. The agent's charter already permits deletion (`agents/test-writer.md`: "rewrite or delete each listed path#method"). The gate forbade it in practice. Blocked by #614, which lands first because it edits the same `diff_test_command`. ## Change File entries are retired by the harness at `implementation-open`, before any dispatch. Method entries stay with the test-writer, and a deterministic gate step refuses to go green while any entry is still present. The charter line loses its condition. ```ruby # plugins/os-sdlc/lib/os_sdlc/pipeline/retired_test.rb class RetiredTest def initialize(entry) @path, @method_name = entry.split("#", 2) end def file? = @method_name.nil? def exists?(root) return false unless root full = File.join(root, @path) File.exist?(full) && File.read(full).include?(@method_name.to_s) end def delete!(root) File.delete(File.join(root, @path)) if file? end end ``` ```ruby # plugins/os-sdlc/lib/os_sdlc/pipeline/open_implementation.rb def ticket_body body = @tracker.fetch_body(@work_order.ticket) check_ticket_shape!(body) retire_test_files!(body) body end def retire_test_files!(body) return unless ticket_map? TicketShape.new(body, root: @root).retired_tests.each { |entry| RetiredTest.new(entry).delete!(@root) } end ``` ```ruby # plugins/os-sdlc/lib/os_sdlc/pipeline/gate_commands.rb def diff_test_command return suite_command if changes.unmapped_code_paths.any? test_paths = changes.test_paths.select { |path| File.exist?(path) } ... end def retired_tests_command entries = TicketShape.new(implementation.ticket_body).retired_tests return nil if entries.empty? "os-sdlc-runner retired-tests-check #{implementation.id}" end ``` `os-sdlc-runner retired-tests-check IMPLEMENTATION_ID` runs `RetiredTest#exists?` on every entry of that implementation's ticket body. Exit 0 when none remain. Exit 1 with one line per entry still present: ``` retired test still present: plugins/os/tests/foo_test.rb#test_bar -- remove the method ``` The step `retired-tests` joins the test-writer node's gate list in `maps/poodr-ticket-implementation.yaml`. Charter, `agents/test-writer.md`, replaces the retired_tests sentence with: "File entries under retired_tests are already deleted. Remove each listed `path#method` in this round." ## Tasks - [ ] `RetiredTest#file?` and `#delete!`. - [ ] `OpenImplementation` deletes file entries after the shape check, ticket map only. - [ ] `diff_test_command` filters `test_paths` to files that exist. - [ ] `retired-tests-check` CLI subcommand; `GateCommands#retired_tests_command`; `retired-tests` step on the test-writer node gate in the ticket map. - [ ] Charter sentence replaced in `agents/test-writer.md`. - [ ] Run `ruby plugins/os-sdlc/tests/all.rb`; green. ## Acceptance criteria - [ ] After `implementation-open` on a ticket map, every file entry under `## Retired tests` is gone from the worktree. - [ ] A deleted test path never appears in the diff-test require list. - [ ] The test-writer gate is red with the prescriptive line while a `path#method` entry is still present, green when it is gone. ## Owner sketch ```ruby module OsSdlc module Pipeline class RetiredTest def file? def exists?(root) def delete!(root) end class OpenImplementation def call end class GateCommands def diff_test_command def retired_tests_command end end end ``` ## Cases 1. Given a retired-tests entry `tests/a_test.rb`, when `RetiredTest#delete!(root)` runs, then the file is gone; given `tests/a_test.rb#test_x`, `delete!` leaves the file in place. 2. Given a ticket map and a body listing one file entry that exists, when `OpenImplementation#call` runs, then the file is deleted before the open payload returns. 3. Given a diff that deletes a test file, when `diff_test_command` is built, then the deleted path is not in the require list. 4. Given an implementation whose ticket lists `tests/a_test.rb#test_x` and the method still exists, when `retired-tests-check` runs, then it exits 1 and prints `retired test still present: tests/a_test.rb#test_x -- remove the method`; when the method is gone, it exits 0. 5. Given the ticket map, when the test-writer node's gate list is read, then it contains the `retired-tests` step. ## Origin - Trigger: /os-sdlc:implement #607, implementation 3, test-writer dispatch 20 - Session: 4974deff-0140-4813-8cd1-e96c6356a214 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/4974deff-0140-4813-8cd1-e96c6356a214.jsonl - Where: plugins/os-sdlc/lib/os_sdlc/pipeline/retired_test.rb, open_implementation.rb, gate_commands.rb, agents/test-writer.md - Design approved by user 2026-09-18; body rewritten with code sketch.
Author
Owner

Work started via os-sdlc pipeline on branch ticket-613 (session 4974deff). #614 merged first at 558f389c.

Work started via os-sdlc pipeline on branch ticket-613 (session 4974deff). #614 merged first at 558f389c.
Author
Owner

Resolution

Done: File entries under Retired tests are deleted at implementation-open (ticket map only); diff_test_command drops deleted test paths; retired-tests-check verb and retired-tests gate step on the test-writer node; charter sentence replaced.

Evidence: Branch ticket-613 commit 301c191f; pipeline implementation 5 dispatches 40-44 pass, programmer gate green; behavior-verifier 45 returned verdict:error on a truncated brief so the run ended implementation_failed; main loop verified the full diff by hand, ruby plugins/os-sdlc/tests/all.rb 1354 runs 0 failures on rerun (first run had 2 lsp-probe socket flakes), rubocop clean.

Follow-ups: #618 truncated code_changes brief fact and verdict:error terminal route.

## Resolution **Done:** File entries under Retired tests are deleted at implementation-open (ticket map only); diff_test_command drops deleted test paths; retired-tests-check verb and retired-tests gate step on the test-writer node; charter sentence replaced. **Evidence:** Branch ticket-613 commit 301c191f; pipeline implementation 5 dispatches 40-44 pass, programmer gate green; behavior-verifier 45 returned verdict:error on a truncated brief so the run ended implementation_failed; main loop verified the full diff by hand, ruby plugins/os-sdlc/tests/all.rb 1354 runs 0 failures on rerun (first run had 2 lsp-probe socket flakes), rubocop clean. **Follow-ups:** #618 truncated code_changes brief fact and verdict:error terminal route.
jared closed this issue 2026-09-18 15:29:50 +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#613
No description provided.