Fix diff-test gate path mapping for lib namespace directories not mirrored under tests/ #532

Closed
opened 2026-09-04 16:50:17 +00:00 by jared · 1 comment
Owner

Context

The first live /os-sdlc:implement run on the os-sdlc target (#449) stalled in a programmer-repair loop. The diff-test gate (ADR-0160) derives a test path from each changed lib file. For the os-sdlc target the derived path does not exist. The gate fails before any test runs.

Observed

The programmer changed plugins/os-sdlc/lib/os_sdlc/runner/tea_tracker.rb. The gate mapped it to plugins/os-sdlc/tests/os_sdlc/runner/tea_tracker_test.rb. The existing test lives at plugins/os-sdlc/tests/runner/tea_tracker_test.rb. The os_sdlc/ namespace directory under lib/ is not mirrored under tests/. Every gate run for dispatch 9 recorded the same LoadError (correction_rounds rows 1-4 in .sdlc/pipeline.db). Two programmer-repair rounds reproduced it.

os-sdlc-runner gate 9
-e:1:in 'Kernel#require_relative': cannot load such file -- /home/jared/dev/cc-os/.claude/worktrees/ticket-449/plugins/os-sdlc/tests/os_sdlc/runner/tea_tracker_test.rb (LoadError)
	from -e:1:in '<main>'

Reproduce

# in a worktree with any uncommitted change under plugins/os-sdlc/lib/os_sdlc/
os-sdlc-runner implementation-open <session> 449 ~/.claude/plugins/os-sdlc/maps/poodr-implementation.yaml --target os-sdlc
# drive to the programmer node, then:
os-sdlc-runner gate <programmer-dispatch-id>

Expected

def test_lib_namespace_directory_is_not_mirrored_under_tests
  project = stub_project(code_path: "plugins/os-sdlc/lib", test_path: "plugins/os-sdlc/tests")
  paths = DiffedTestPaths.new(project_root, project)
  assert_equal ["plugins/os-sdlc/tests/runner/tea_tracker_test.rb"],
               paths.for(["plugins/os-sdlc/lib/os_sdlc/runner/tea_tracker.rb"])
end

The mapping must resolve to a test file that exists for this repository's layout. If no mapped file exists, the gate must say so in plain words instead of failing with a LoadError inside ruby -e.

Illustration

# illustration, not spec
def test_path_for(file)
  rel = file.delete_prefix(code_prefix)
  candidates = [rel, rel.sub(%r{\A[^/]+/}, "")].map { |r| "#{test_prefix}#{r.sub(/\.rb\z/, '_test.rb')}" }
  candidates.find { |c| File.exist?(File.join(project_root, c)) } || candidates.first
end

Origin

  • Trigger: /os-sdlc:implement 449 on branch ticket-449, dispatch 9 (programmer) and two programmer-repair rounds.
  • Improvised this session: none. The pipeline was stopped and #449 marked waiting.
  • Chain: programmer-repair loop ← diff-test gate LoadError ← DiffedTestPaths#test_path_for mirrors the full lib-relative path under tests/ (plugins/os-sdlc/lib/os_sdlc/runner/diffed_test_paths.rb:33) ← DESIGN: ADR-0160 diff-scoped gates, path convention never checked against the os-sdlc target layout.
  • Root candidate: this ticket.
  • Where: OsSdlc::Runner::DiffedTestPaths#test_path_for, plugins/os-sdlc/lib/os_sdlc/runner/diffed_test_paths.rb
  • Session: 6db006b2-14fa-4785-8ce1-ed011e529bb7
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/6db006b2-14fa-4785-8ce1-ed011e529bb7.jsonl

Related observation, not root-caused here: the two programmer-repair re-dispatches created no rows in dispatches; both repair agents reported against dispatch 9.

Skeptic

VERDICT: CREATE
REASON: Complete in-policy run hit a genuine root-cause bug (diff-test gate LoadError from unmirrored lib namespace), with reproduction, tests, and Origin chain ending in a design gap; blocks every os-sdlc change.

## Context The first live `/os-sdlc:implement` run on the os-sdlc target (#449) stalled in a programmer-repair loop. The diff-test gate (ADR-0160) derives a test path from each changed lib file. For the os-sdlc target the derived path does not exist. The gate fails before any test runs. ## Observed The programmer changed `plugins/os-sdlc/lib/os_sdlc/runner/tea_tracker.rb`. The gate mapped it to `plugins/os-sdlc/tests/os_sdlc/runner/tea_tracker_test.rb`. The existing test lives at `plugins/os-sdlc/tests/runner/tea_tracker_test.rb`. The `os_sdlc/` namespace directory under `lib/` is not mirrored under `tests/`. Every gate run for dispatch 9 recorded the same LoadError (correction_rounds rows 1-4 in `.sdlc/pipeline.db`). Two programmer-repair rounds reproduced it. ``` os-sdlc-runner gate 9 ``` ``` -e:1:in 'Kernel#require_relative': cannot load such file -- /home/jared/dev/cc-os/.claude/worktrees/ticket-449/plugins/os-sdlc/tests/os_sdlc/runner/tea_tracker_test.rb (LoadError) from -e:1:in '<main>' ``` ## Reproduce ``` # in a worktree with any uncommitted change under plugins/os-sdlc/lib/os_sdlc/ os-sdlc-runner implementation-open <session> 449 ~/.claude/plugins/os-sdlc/maps/poodr-implementation.yaml --target os-sdlc # drive to the programmer node, then: os-sdlc-runner gate <programmer-dispatch-id> ``` ## Expected ```ruby def test_lib_namespace_directory_is_not_mirrored_under_tests project = stub_project(code_path: "plugins/os-sdlc/lib", test_path: "plugins/os-sdlc/tests") paths = DiffedTestPaths.new(project_root, project) assert_equal ["plugins/os-sdlc/tests/runner/tea_tracker_test.rb"], paths.for(["plugins/os-sdlc/lib/os_sdlc/runner/tea_tracker.rb"]) end ``` The mapping must resolve to a test file that exists for this repository's layout. If no mapped file exists, the gate must say so in plain words instead of failing with a LoadError inside `ruby -e`. ## Illustration ```ruby # illustration, not spec def test_path_for(file) rel = file.delete_prefix(code_prefix) candidates = [rel, rel.sub(%r{\A[^/]+/}, "")].map { |r| "#{test_prefix}#{r.sub(/\.rb\z/, '_test.rb')}" } candidates.find { |c| File.exist?(File.join(project_root, c)) } || candidates.first end ``` ## Origin - Trigger: `/os-sdlc:implement 449` on branch ticket-449, dispatch 9 (programmer) and two programmer-repair rounds. - Improvised this session: none. The pipeline was stopped and #449 marked waiting. - Chain: programmer-repair loop ← diff-test gate LoadError ← `DiffedTestPaths#test_path_for` mirrors the full lib-relative path under tests/ (`plugins/os-sdlc/lib/os_sdlc/runner/diffed_test_paths.rb:33`) ← DESIGN: ADR-0160 diff-scoped gates, path convention never checked against the os-sdlc target layout. - Root candidate: this ticket. - Where: `OsSdlc::Runner::DiffedTestPaths#test_path_for`, `plugins/os-sdlc/lib/os_sdlc/runner/diffed_test_paths.rb` - Session: 6db006b2-14fa-4785-8ce1-ed011e529bb7 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/6db006b2-14fa-4785-8ce1-ed011e529bb7.jsonl Related observation, not root-caused here: the two programmer-repair re-dispatches created no rows in `dispatches`; both repair agents reported against dispatch 9. ## Skeptic VERDICT: CREATE REASON: Complete in-policy run hit a genuine root-cause bug (diff-test gate LoadError from unmirrored lib namespace), with reproduction, tests, and Origin chain ending in a design gap; blocks every os-sdlc change.
Author
Owner

Resolution

Done: DiffedTestPaths#test_path_for now tries the mirrored path, then the path with the first lib segment dropped, picks the one on disk, and raises HarnessError naming both when neither exists. A single candidate keeps the old unconditional contract so a TDD round can point at a test it is about to create.

Evidence: Commit 356f102 on main; tests in plugins/os-sdlc/tests/runner/diffed_test_paths_test.rb (namespaced, mirrored, no-candidate cases); suite 1020 runs 0 failures; live check: os-sdlc-runner gate 9 for #449 went from LoadError to running tests/runner/tea_tracker_test.rb and then green.

Follow-ups: none

## Resolution **Done:** DiffedTestPaths#test_path_for now tries the mirrored path, then the path with the first lib segment dropped, picks the one on disk, and raises HarnessError naming both when neither exists. A single candidate keeps the old unconditional contract so a TDD round can point at a test it is about to create. **Evidence:** Commit 356f102 on main; tests in plugins/os-sdlc/tests/runner/diffed_test_paths_test.rb (namespaced, mirrored, no-candidate cases); suite 1020 runs 0 failures; live check: os-sdlc-runner gate 9 for #449 went from LoadError to running tests/runner/tea_tracker_test.rb and then green. **Follow-ups:** none
jared closed this issue 2026-09-04 17:41:26 +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#532
No description provided.