project.rb batch 4/2: RootSearch and Setup ask Git::Repo for the repo boundary #584

Closed
opened 2026-09-14 20:03:42 +00:00 by jared · 2 comments
Owner

Map: #586

Batch 4 of the project.rb responsibility refactor, ticket 2 of 3. Follows ticket 1.
Audit: tuning/responsibility-audit/runs/16-project-rb-batch3-reaudit/report.md.
Capability: repository-boundary-detection. Owner: OsSdlc::Git::Repo (existing, repo? at git/repo.rb:38, confidence 0.85).
Map: maps/poodr-ticket-implementation.yaml.

Cases (one red round, all listed)

  1. Project::RootSearch.call(start) where the repo boundary directory holds a .git file (a worktree or submodule) stops at that directory and returns nil when no project.yaml lies between. Today it walks past it (root_search.rb:32 tests File.directory?).
  2. Project::RootSearch.call(start) with a .git directory boundary behaves as today (tests/project/root_search_test.rb:32 already covers this).
  3. Project::Setup#upsert on a directory that holds a .sdlc/project.yaml but is not inside a git repo raises HarnessError "not inside a git repo". Today the existing yaml skips the guard (setup.rb:13). User decision 2026-09-14: git is always required; worktrees depend on it.

Owner sketch

# root_search.rb
def boundary?
  Git::Repo.new(@dir).repo_root? || @dir == File.expand_path(Dir.home)
end

Git::Repo gains one predicate that answers "is this directory itself the top of a repo or worktree" (rev-parse --show-toplevel equals the dir). repo? alone is not enough: it is true for every subdirectory. Delegate to the question, not to the .git path.

# setup.rb
def upsert(name:, **attrs)
  raise_not_a_git_repo! unless @repo.repo?
  @yaml.merge_keys(attrs.merge(name: name))
end

Callers that delegate

  • lib/os_sdlc/project/root_search.rb:32 boundary?
  • lib/os_sdlc/project/setup.rb:13 upsert

Tests that touch the old behavior (planned, not discovered)

  • tests/project/setup_test.rb:11-30
  • tests/project_test.rb:107,129,234-289 (upsert scenarios; check any that create a yaml without git init)
  • tests/support/project_scenario.rb:90

ADR

Amends ADR-0185 (Setup precondition now unconditional) and ADR-0174 (Git::Repo gains the toplevel predicate). Create the amending ADR when the ticket lands.

Structural follow-through

  • rg '\.git"' plugins/os-sdlc/lib/os_sdlc/project returns nothing.
Map: #586 Batch 4 of the project.rb responsibility refactor, ticket 2 of 3. Follows ticket 1. Audit: tuning/responsibility-audit/runs/16-project-rb-batch3-reaudit/report.md. Capability: repository-boundary-detection. Owner: `OsSdlc::Git::Repo` (existing, `repo?` at git/repo.rb:38, confidence 0.85). Map: maps/poodr-ticket-implementation.yaml. ## Cases (one red round, all listed) 1. `Project::RootSearch.call(start)` where the repo boundary directory holds a `.git` **file** (a worktree or submodule) stops at that directory and returns nil when no project.yaml lies between. Today it walks past it (root_search.rb:32 tests `File.directory?`). 2. `Project::RootSearch.call(start)` with a `.git` directory boundary behaves as today (tests/project/root_search_test.rb:32 already covers this). 3. `Project::Setup#upsert` on a directory that holds a `.sdlc/project.yaml` but is not inside a git repo raises `HarnessError` "not inside a git repo". Today the existing yaml skips the guard (setup.rb:13). User decision 2026-09-14: git is always required; worktrees depend on it. ## Owner sketch ```ruby # root_search.rb def boundary? Git::Repo.new(@dir).repo_root? || @dir == File.expand_path(Dir.home) end ``` `Git::Repo` gains one predicate that answers "is this directory itself the top of a repo or worktree" (`rev-parse --show-toplevel` equals the dir). `repo?` alone is not enough: it is true for every subdirectory. Delegate to the question, not to the `.git` path. ```ruby # setup.rb def upsert(name:, **attrs) raise_not_a_git_repo! unless @repo.repo? @yaml.merge_keys(attrs.merge(name: name)) end ``` ## Callers that delegate - lib/os_sdlc/project/root_search.rb:32 `boundary?` - lib/os_sdlc/project/setup.rb:13 `upsert` ## Tests that touch the old behavior (planned, not discovered) - tests/project/setup_test.rb:11-30 - tests/project_test.rb:107,129,234-289 (upsert scenarios; check any that create a yaml without `git init`) - tests/support/project_scenario.rb:90 ## ADR Amends ADR-0185 (Setup precondition now unconditional) and ADR-0174 (Git::Repo gains the toplevel predicate). Create the amending ADR when the ticket lands. ## Structural follow-through - `rg '\.git"' plugins/os-sdlc/lib/os_sdlc/project` returns nothing.
Author
Owner

Work started on branch ticket-584 via os-sdlc pipeline (map poodr-ticket-implementation.yaml, batch 4 map #586).

Work started on branch ticket-584 via os-sdlc pipeline (map poodr-ticket-implementation.yaml, batch 4 map #586).
Author
Owner

Resolution

Done: Batch 4/2 merged to main 30b2b93, ADR-0188. Git::Repo#top_level? is the single repo-boundary definition; RootSearch#boundary? delegates to it (a .git file now stops the climb); Setup#upsert requires a git repo unconditionally and refuses a targets-shaped file via Project::Yaml#refuse_targets_shaped!.

Evidence: Suite 1184 runs 0 failures; rubocop clean; rg for .git path tests under lib/os_sdlc/project returns nothing. Pipeline impl 15: 19 dispatches, bound_exhausted at suite-check. Deviations: (1) capture agent filed the ticket with an empty body (only 'Map: #586'); impl 14 failed at change-auditor in 21 s; body written from the scratchpad and impl 15 opened. (2) programmer relayed the reviewer's dispatch id; settled via implementation-report + gate + subagent-stop (haiku). (3) pre-existing test test_load_climbs_out_of_a_worktree_whose_dot_git_is_a_file asserted the removed behavior; sonnet rewrote it and made fixture repos real (git init / git worktree add). (4) sonnet fixer added an unused initial: keyword on RootSearch and a duplicated targets-shaped refusal in Setup; haiku removed both. (5) ADR skill wrote the file into the main checkout from the worktree, twice; moved by hand, stray superseded-by stamps reverted.

Follow-ups: none

## Resolution **Done:** Batch 4/2 merged to main 30b2b93, ADR-0188. Git::Repo#top_level? is the single repo-boundary definition; RootSearch#boundary? delegates to it (a .git file now stops the climb); Setup#upsert requires a git repo unconditionally and refuses a targets-shaped file via Project::Yaml#refuse_targets_shaped!. **Evidence:** Suite 1184 runs 0 failures; rubocop clean; rg for .git path tests under lib/os_sdlc/project returns nothing. Pipeline impl 15: 19 dispatches, bound_exhausted at suite-check. Deviations: (1) capture agent filed the ticket with an empty body (only 'Map: #586'); impl 14 failed at change-auditor in 21 s; body written from the scratchpad and impl 15 opened. (2) programmer relayed the reviewer's dispatch id; settled via implementation-report + gate + subagent-stop (haiku). (3) pre-existing test test_load_climbs_out_of_a_worktree_whose_dot_git_is_a_file asserted the removed behavior; sonnet rewrote it and made fixture repos real (git init / git worktree add). (4) sonnet fixer added an unused initial: keyword on RootSearch and a duplicated targets-shaped refusal in Setup; haiku removed both. (5) ADR skill wrote the file into the main checkout from the worktree, twice; moved by hand, stray superseded-by stamps reverted. **Follow-ups:** none
jared closed this issue 2026-09-14 21:29:08 +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#584
No description provided.