Migrate os-sdlc git calls to OsGit and remove the second worktree owner #1592

Closed
opened 2026-11-06 16:53:32 +00:00 by jared · 3 comments
Owner

Problem

OsSdlc::Git::Repo is the closest wrapper to the target, with about 17 callers, but three files run git on their own (lint/autocorrection/changed_files.rb, crc/sweep/files.rb, implementation/mutation_check.rb). Two files hardcode the base branch main (gate/unchanged_check/base_checkout.rb, report/merged_diff.rb). base_checkout.rb is a second owner of worktree add and remove.

Design: docs/os-git/use-cases-and-domain.md (design) and docs/os-git/call-site-checklist.md (call sites). Settled decisions are in design section 1 (docs/os-git/use-cases-and-domain.md): the gem lives in plugins/os-git with a path: source; git-context and os:worktree merge into os-git; old wrappers go only after all references are updated; design order is use-cases, objects, interfaces; os-context and os-vault become Ruby; ticket tracking stays out of os-git.

Reproduce

n/a: migration of existing call sites; behavior stays the same unless a criterion says otherwise.

Approach

Groups A-03 (G-013 to G-034) and A-04 (G-035 to G-037). Replace each site with the message from the design Replaced-by table:

  1. UC-01 root and top-level (project/root_search, project/setup, lint/autocorrection/changed_files): Repository.find, Repository#at_top_level?.
  2. UC-04 working-tree changes (project/changes, implementation/commit, lint/autocorrection/changed_files with a *.rb glob): Repository#changes(against:, glob:). The lint hook runs on every edit: use RepoRoot and keep the number of git processes at or below today's count.
  3. UC-05, UC-09, UC-11 (fast_gate/changed_files): Repository#changes_between(Span.since_fork(base, branch), renames: true), #read_at, #merge_base.
  4. UC-17 (gate/unchanged_check/base_checkout): OsGit::Worktree.temporary(repository, ref). The base is an argument, not main (design Q6): read a project setting if one exists, otherwise pass "main" as this project's value, not as a library constant.
  5. UC-13 (report/merged_diff): History#matching(text, on: base), with the same Q6 rule.
  6. UC-12, UC-07, UC-14 (crc/*, design/base_check, tracker_lookup): Repository#resolve, #exists?, #files(kind), #remote.
  7. UC-22 (implementation/commit): Repository#commit(Commit::Plan).
  8. implementation/mutation_check.rb runs git apply --include=PATH -. No use-case covers it. Decide: add Repository#apply_patch to the design, or mark the row out of scope with a reason. Record the decision in the design doc.
    The wiring-only rows (G-025, G-031 to G-034) build Git::Repo and pass it on: replace the constructor with OsGit::Repository.open.

Mark every row in the checklist from the Done column as it is replaced. Mark the matching test rows in the same change. Run bin/refresh-plugins after changes to SKILL.md, hooks, or CLIs.

Required work: none beyond the acceptance criteria.

Acceptance criteria

  1. Given a project with tracked, modified, and untracked files, when the gate lists changed files, then the list equals the list before the migration.
  2. Given the lint hook runs after an edit, when it finds changed *.rb files, then it returns the same files and starts no more git processes than before.
  3. Given a base branch other than main, when the unchanged check and the merged-diff report run, then they use that base, and the string "main" is not a library constant.
  4. Given a git failure in a gate, when it runs, then it raises and the gate reports the failure, as git! did before.
  5. Given the D-02 tests (rows G-144 to G-171), when they run, then they use OsGit::Testing::ScratchRepo and the os-sdlc suite passes.
  6. Given plugins/os-sdlc/lib, when it is searched for Git::Repo and Git::Diff, then every reference is updated (the classes themselves are deleted by the retirement ticket #1607).
  7. Given rows G-013 to G-037 and the D-02 test rows G-144 to G-171, when the ticket closes, then each row is marked - [x] with Replaced by filled in, or marked out of scope with a reason, in docs/os-git/call-site-checklist.md.
  8. Given the touched code, when it is searched for Open3, system(, %x, backticks, and git argv, then no git call remains outside OsGit adapters, except sites marked out of scope with a reason.

Non-goals

Deleting OsSdlc::Git::Repo and Diff (the retirement ticket). The ticket-takedown eval harness (its own ticket).

Blocking edges

Blocked by the core ticket (#1588). Also needs the worktree ticket (#1589) for OsGit::Worktree.temporary.

Origin

  • Trigger: User request in session 1a5b2630-09c9-46cb-aee4-a5a98e2e7d24 on 2026-11-06: one os-git gem for all local git work in cc-os and the user's projects.
  • Improvised this session: none
  • Chain: about 17 files reference one wrapper and three run git themselves ← wrapper designed before the shared domain ← DESIGN (docs/os-git/call-site-checklist.md groups A-03, A-04)
  • Root candidate: none found; searched open issues for git, worktree, octokit, ruby port, python, statusline, reset --hard. Related but not a match: #845, #853, #857, #1527, #1554.
  • Where: plugins/os-sdlc/lib/os_sdlc/git/, plugins/os-sdlc/lib/os_sdlc/ (rows G-013 to G-037)
  • Session: 1a5b2630-09c9-46cb-aee4-a5a98e2e7d24
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/1a5b2630-09c9-46cb-aee4-a5a98e2e7d24.jsonl
## Problem `OsSdlc::Git::Repo` is the closest wrapper to the target, with about 17 callers, but three files run git on their own (`lint/autocorrection/changed_files.rb`, `crc/sweep/files.rb`, `implementation/mutation_check.rb`). Two files hardcode the base branch `main` (`gate/unchanged_check/base_checkout.rb`, `report/merged_diff.rb`). `base_checkout.rb` is a second owner of worktree add and remove. Design: `docs/os-git/use-cases-and-domain.md` (design) and `docs/os-git/call-site-checklist.md` (call sites). Settled decisions are in design section 1 (`docs/os-git/use-cases-and-domain.md`): the gem lives in `plugins/os-git` with a `path:` source; `git-context` and `os:worktree` merge into `os-git`; old wrappers go only after all references are updated; design order is use-cases, objects, interfaces; `os-context` and `os-vault` become Ruby; ticket tracking stays out of `os-git`. ## Reproduce n/a: migration of existing call sites; behavior stays the same unless a criterion says otherwise. ## Approach Groups A-03 (G-013 to G-034) and A-04 (G-035 to G-037). Replace each site with the message from the design Replaced-by table: 1. UC-01 root and top-level (`project/root_search`, `project/setup`, `lint/autocorrection/changed_files`): `Repository.find`, `Repository#at_top_level?`. 2. UC-04 working-tree changes (`project/changes`, `implementation/commit`, `lint/autocorrection/changed_files` with a `*.rb` glob): `Repository#changes(against:, glob:)`. The lint hook runs on every edit: use `RepoRoot` and keep the number of git processes at or below today's count. 3. UC-05, UC-09, UC-11 (`fast_gate/changed_files`): `Repository#changes_between(Span.since_fork(base, branch), renames: true)`, `#read_at`, `#merge_base`. 4. UC-17 (`gate/unchanged_check/base_checkout`): `OsGit::Worktree.temporary(repository, ref)`. The base is an argument, not `main` (design Q6): read a project setting if one exists, otherwise pass `"main"` as this project's value, not as a library constant. 5. UC-13 (`report/merged_diff`): `History#matching(text, on: base)`, with the same Q6 rule. 6. UC-12, UC-07, UC-14 (`crc/*`, `design/base_check`, `tracker_lookup`): `Repository#resolve`, `#exists?`, `#files(kind)`, `#remote`. 7. UC-22 (`implementation/commit`): `Repository#commit(Commit::Plan)`. 8. `implementation/mutation_check.rb` runs `git apply --include=PATH -`. No use-case covers it. Decide: add `Repository#apply_patch` to the design, or mark the row out of scope with a reason. Record the decision in the design doc. The wiring-only rows (G-025, G-031 to G-034) build `Git::Repo` and pass it on: replace the constructor with `OsGit::Repository.open`. Mark every row in the checklist from the `Done` column as it is replaced. Mark the matching test rows in the same change. Run `bin/refresh-plugins` after changes to `SKILL.md`, hooks, or CLIs. Required work: none beyond the acceptance criteria. ## Acceptance criteria 1. Given a project with tracked, modified, and untracked files, when the gate lists changed files, then the list equals the list before the migration. 2. Given the lint hook runs after an edit, when it finds changed `*.rb` files, then it returns the same files and starts no more git processes than before. 3. Given a base branch other than `main`, when the unchanged check and the merged-diff report run, then they use that base, and the string `"main"` is not a library constant. 4. Given a git failure in a gate, when it runs, then it raises and the gate reports the failure, as `git!` did before. 5. Given the D-02 tests (rows G-144 to G-171), when they run, then they use `OsGit::Testing::ScratchRepo` and the os-sdlc suite passes. 6. Given `plugins/os-sdlc/lib`, when it is searched for `Git::Repo` and `Git::Diff`, then every reference is updated (the classes themselves are deleted by the retirement ticket #1607). 7. Given rows G-013 to G-037 and the D-02 test rows G-144 to G-171, when the ticket closes, then each row is marked `- [x]` with `Replaced by` filled in, or marked out of scope with a reason, in `docs/os-git/call-site-checklist.md`. 8. Given the touched code, when it is searched for `Open3`, `system(`, `%x`, backticks, and `git` argv, then no git call remains outside `OsGit` adapters, except sites marked out of scope with a reason. ## Non-goals Deleting `OsSdlc::Git::Repo` and `Diff` (the retirement ticket). The ticket-takedown eval harness (its own ticket). ## Blocking edges Blocked by the core ticket (#1588). Also needs the worktree ticket (#1589) for `OsGit::Worktree.temporary`. ## Origin - Trigger: User request in session 1a5b2630-09c9-46cb-aee4-a5a98e2e7d24 on 2026-11-06: one os-git gem for all local git work in cc-os and the user's projects. - Improvised this session: none - Chain: about 17 files reference one wrapper and three run git themselves ← wrapper designed before the shared domain ← DESIGN (docs/os-git/call-site-checklist.md groups A-03, A-04) - Root candidate: none found; searched open issues for git, worktree, octokit, ruby port, python, statusline, reset --hard. Related but not a match: #845, #853, #857, #1527, #1554. - Where: plugins/os-sdlc/lib/os_sdlc/git/, plugins/os-sdlc/lib/os_sdlc/ (rows G-013 to G-037) - Session: 1a5b2630-09c9-46cb-aee4-a5a98e2e7d24 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/1a5b2630-09c9-46cb-aee4-a5a98e2e7d24.jsonl
Author
Owner

From #1588, for this migration: (1) commit_changes returns nothing for a merge commit. (2) log(rev:) returns [] for a range such as a..b. (3) A non-UTF-8 path or subject is scrubbed, so the original bytes are lost. Fix each in OsGit if a migrated call site needs it. Also: Commit is a class holding a backend, not a Data value, because paths and changes load lazily. Repository#primary_root uses the backend; RepoRoot.primary_root stays for the process-free hook path.

From #1588, for this migration: (1) commit_changes returns nothing for a merge commit. (2) log(rev:) returns [] for a range such as a..b. (3) A non-UTF-8 path or subject is scrubbed, so the original bytes are lost. Fix each in OsGit if a migrated call site needs it. Also: Commit is a class holding a backend, not a Data value, because paths and changes load lazily. Repository#primary_root uses the backend; RepoRoot.primary_root stays for the process-free hook path.
Author
Owner

From #1589: OsGit::Worktree.temporary never forces removal, so a dirtied temporary checkout raises NotClean. Check callers that dirty the checkout. Rulings rows from #1589 are numbered 56, 57, 58 in docs/decisions/rulings.tsv; the #1589 body says row 39.

From #1589: `OsGit::Worktree.temporary` never forces removal, so a dirtied temporary checkout raises NotClean. Check callers that dirty the checkout. Rulings rows from #1589 are numbered 56, 57, 58 in docs/decisions/rulings.tsv; the #1589 body says row 39.
Author
Owner

Resolution

Done: os-sdlc lib and bin call OsGit. Test setup uses ScratchRepo. os-sdlc loads OsGit by a sibling-first path source. Added apply_patch and history messages. Base branch reads optional base_branch, else primary_branch.

Evidence: main 89140417. os-sdlc 3508 runs, os-git 255 runs, 0 failures; rubocop clean on os-git; two stranger reviews; AC 2 measured 3 git processes old, 2 new; rows G-013 to G-037 and G-144 to G-171 marked (G-144 out of scope). Cached os-sdlc boots.

Follow-ups: #1627 census and ledger raise on git failure; #1628 settings.rb InwardDependency offenses (both ticket-skeptic verdicts). Fallback edge: a project with no base_branch that runs a feature branch in its primary checkout gets a vacuous unchanged check; report in #1610 summary.

## Resolution **Done:** os-sdlc lib and bin call OsGit. Test setup uses ScratchRepo. os-sdlc loads OsGit by a sibling-first path source. Added apply_patch and history messages. Base branch reads optional base_branch, else primary_branch. **Evidence:** main 89140417. os-sdlc 3508 runs, os-git 255 runs, 0 failures; rubocop clean on os-git; two stranger reviews; AC 2 measured 3 git processes old, 2 new; rows G-013 to G-037 and G-144 to G-171 marked (G-144 out of scope). Cached os-sdlc boots. **Follow-ups:** #1627 census and ledger raise on git failure; #1628 settings.rb InwardDependency offenses (both ticket-skeptic verdicts). Fallback edge: a project with no base_branch that runs a feature branch in its primary checkout gets a vacuous unchanged check; report in #1610 summary.
jared closed this issue 2026-11-06 21:13:00 +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#1592
No description provided.