Move os:worktree and handoff git calls into os-git Worktree and Integration #1589

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

Problem

plugins/os/lib/worktree/* is the second-largest git wrapper (Worktree::Git plus about ten policy classes). Handoff::Repository has its own private git helper that returns "" on every failure. plugins/os-sdlc/lib/os_sdlc/gate/unchanged_check/base_checkout.rb is a second owner of worktree add and worktree remove. The worktree rules (refuse a dirty primary, fast-forward then merge commit, safe branch -d, conflict exit code) must live in os-git objects, behind one interface.

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: structural move. The CLI contract (exit codes 0 to 6 and the output lines) is in plugins/os/skills/worktree/SKILL.md, ADR-0151, and ADR-0253.

Approach

Build OsGit::Worktree, OsGit::Integration, OsGit::Worktree::Start, OsGit::Worktree::Finish, and OsGit::Worktree::Links from design sections 3 and 4.3, on top of the core objects (#1588). Worktree::Links holds the cc-os policy (.cc-os/config key worktree-symlinks, .sdlc/pipeline.db); the core never names cc-os paths. bin/worktree stays the Thor entry. It maps Integration#refusal and errors to exit codes. Keep the contract byte-for-byte: exit codes and output lines.

Apply the ADR ruling on Q5 (#1585). If ADR-0240 is superseded, do not port the worktree-before-remove feature. Keep exit code 6 as "primary checkout dirty".

Replace the handoff git helper with Repository, History, and ChangeSet. Keep the Handoff::Worktree record value (it is a handoff type, not git). Replace the use of Worktree.temporary in gate/unchanged_check/base_checkout.rb with OsGit::Worktree.temporary in the os-sdlc migration ticket (#1592), not here.

Open worktree tickets may be fixed or made obsolete by this port: #849, #1154, #1297, #1306, #982. For each, comment whether the port fixed it. Do not widen the scope to fix them.

Rows to mark in docs/os-git/call-site-checklist.md: G-001 to G-012 (A-01 and A-02), and the D-01 test rows G-121 to G-143 (replace setup with OsGit::Testing::ScratchRepo).

Required work:

  1. OsGit::Worktree and OsGit::Worktree.temporary — check: present temporary in plugins/os-git/lib/os_git/worktree.rb
  2. OsGit::Integration with refusal, merge, verify_merged!, bring_up, align — check: present bring_up in plugins/os-git/lib/os_git/integration.rb
  3. OsGit::Worktree::Start, Finish, Links — check: present Links in plugins/os-git/lib/os_git/worktree/links.rb
  4. plugins/os-git/bin/worktree with the exit-code contract — check: present exit in plugins/os-git/bin/worktree
  5. Slim plugins/os/lib/handoff/repository.rb to call OsGit — check: absent Open3 in plugins/os/lib/handoff/repository.rb
  6. Copy plugins/os/skills/worktree/SKILL.md to plugins/os-git/skills/worktree/SKILL.md as the /os-git:worktree skill (noun-first per ADR-0106). The old skill stays until #1605 updates the skills that name it — check: present worktree start in plugins/os-git/skills/worktree/SKILL.md
  7. Add a gemspec, so a plugin with a Gemfile can use a path: source at ~/.claude/plugins/os-git; a plugin with no Gemfile requires lib/os_git from that symlink (rulings row 39) — check: present os_git in plugins/os-git/os_git.gemspec
  8. Make plugins/os/bin/worktree delegate to the new CLI, so the command on PATH runs OsGit::Worktree::Start and Finish; the classes in plugins/os/lib/worktree/ stop being called and #1607 deletes them — check: present os-git in plugins/os/bin/worktree
  9. Move the D-01 test setup to OsGit::Testing::ScratchRepo (AC 8).

Orchestrator work: mark rows G-001 to G-012 and G-121 to G-143 in the checklist (AC 9), and comment on #849, #1154, #1297, #1306, and #982 whether the port fixed each.

Acceptance criteria

  1. Given a branch with a worktree and a clean primary checkout, when worktree finish runs, then it merges, removes the worktree, deletes the branch with -d, and exits 0 with the same output lines as before.
  2. Given a primary checkout with tracked changes, when worktree finish runs, then it refuses and exits 6.
  3. Given a conflicting merge, when worktree finish runs, then it exits 1 and lists the conflicted files.
  4. Given a branch that is not an ancestor of the base, when worktree finish runs, then it refuses with the same exit code as before.
  5. Given an existing branch with no worktree, when worktree start runs, then it refuses and exits 1 (ADR-0253).
  6. Given a reused worktree behind the base, when worktree start runs, then it fast-forwards when it can, and otherwise makes a merge commit, and never rebases.
  7. Given the handoff snapshot in a repository where git fails, when it is built, then the failure surfaces as OsGit::Error at the edge, and no helper returns "" for a real failure. Outside a repository and for an unknown commit, available? and commit_known? still answer false (git answered "nothing there"), and pickup_git_limits_test passes.
  8. Given the os plugin tests (D-01 rows), when they run, then they use OsGit::Testing::ScratchRepo and pass.
  9. Given rows G-001 to G-012 and G-121 to G-143, when the ticket closes, then each row is marked - [x] with Replaced by filled in, in docs/os-git/call-site-checklist.md.
  10. Given the worktree command on PATH, when it runs, then it starts no code in plugins/os/lib/worktree/, and every git call it makes goes through OsGit; the search for Open3, system(, and backticks in plugins/os-git/lib/os_git/worktree finds no git call. The non-git system call in after_merge.rb (plugin refresh) moves with the policy code.

Owner sketch

class OsGit::Integration
  def initialize(repository, base: repository.primary_branch); end
  def refusal(branch); end
  def merge(branch); end
  def bring_up(worktree); end
end
class OsGit::Worktree; def self.temporary(repository, ref); end; end

Non-goals

Deleting Worktree::Git or any class in plugins/os/lib/worktree/ (the retirement ticket #1607). Fixing #849, #1154, #1297, #1306, #982 beyond a comment.

Blocking edges

Blocked by the core ticket (#1588) and the ADR ticket (#1585).

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: two worktree owners and a private handoff helper ← worktree logic grew inside plugin os ← DESIGN (docs/os-git/use-cases-and-domain.md sections 2.2, 5, 7.2)
  • 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/lib/worktree/, plugins/os/lib/handoff/repository.rb
  • Session: 1a5b2630-09c9-46cb-aee4-a5a98e2e7d24
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/1a5b2630-09c9-46cb-aee4-a5a98e2e7d24.jsonl
## Problem `plugins/os/lib/worktree/*` is the second-largest git wrapper (`Worktree::Git` plus about ten policy classes). `Handoff::Repository` has its own private git helper that returns `""` on every failure. `plugins/os-sdlc/lib/os_sdlc/gate/unchanged_check/base_checkout.rb` is a second owner of `worktree add` and `worktree remove`. The worktree rules (refuse a dirty primary, fast-forward then merge commit, safe `branch -d`, conflict exit code) must live in `os-git` objects, behind one interface. 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: structural move. The CLI contract (exit codes 0 to 6 and the output lines) is in `plugins/os/skills/worktree/SKILL.md`, ADR-0151, and ADR-0253. ## Approach Build `OsGit::Worktree`, `OsGit::Integration`, `OsGit::Worktree::Start`, `OsGit::Worktree::Finish`, and `OsGit::Worktree::Links` from design sections 3 and 4.3, on top of the core objects (#1588). `Worktree::Links` holds the cc-os policy (`.cc-os/config` key `worktree-symlinks`, `.sdlc/pipeline.db`); the core never names cc-os paths. `bin/worktree` stays the Thor entry. It maps `Integration#refusal` and errors to exit codes. Keep the contract byte-for-byte: exit codes and output lines. Apply the ADR ruling on Q5 (#1585). If ADR-0240 is superseded, do not port the `worktree-before-remove` feature. Keep exit code 6 as "primary checkout dirty". Replace the handoff git helper with `Repository`, `History`, and `ChangeSet`. Keep the `Handoff::Worktree` record value (it is a handoff type, not git). Replace the use of `Worktree.temporary` in `gate/unchanged_check/base_checkout.rb` with `OsGit::Worktree.temporary` in the os-sdlc migration ticket (#1592), not here. Open worktree tickets may be fixed or made obsolete by this port: #849, #1154, #1297, #1306, #982. For each, comment whether the port fixed it. Do not widen the scope to fix them. Rows to mark in `docs/os-git/call-site-checklist.md`: G-001 to G-012 (A-01 and A-02), and the D-01 test rows G-121 to G-143 (replace setup with `OsGit::Testing::ScratchRepo`). Required work: 1. `OsGit::Worktree` and `OsGit::Worktree.temporary` — check: present `temporary` in `plugins/os-git/lib/os_git/worktree.rb` 2. `OsGit::Integration` with refusal, merge, verify_merged!, bring_up, align — check: present `bring_up` in `plugins/os-git/lib/os_git/integration.rb` 3. `OsGit::Worktree::Start`, `Finish`, `Links` — check: present `Links` in `plugins/os-git/lib/os_git/worktree/links.rb` 4. `plugins/os-git/bin/worktree` with the exit-code contract — check: present `exit` in `plugins/os-git/bin/worktree` 5. Slim `plugins/os/lib/handoff/repository.rb` to call `OsGit` — check: absent `Open3` in `plugins/os/lib/handoff/repository.rb` 6. Copy `plugins/os/skills/worktree/SKILL.md` to `plugins/os-git/skills/worktree/SKILL.md` as the `/os-git:worktree` skill (noun-first per ADR-0106). The old skill stays until #1605 updates the skills that name it — check: present `worktree start` in `plugins/os-git/skills/worktree/SKILL.md` 7. Add a gemspec, so a plugin with a Gemfile can use a `path:` source at `~/.claude/plugins/os-git`; a plugin with no Gemfile requires `lib/os_git` from that symlink (rulings row 39) — check: present `os_git` in `plugins/os-git/os_git.gemspec` 8. Make `plugins/os/bin/worktree` delegate to the new CLI, so the command on PATH runs `OsGit::Worktree::Start` and `Finish`; the classes in `plugins/os/lib/worktree/` stop being called and #1607 deletes them — check: present `os-git` in `plugins/os/bin/worktree` 9. Move the D-01 test setup to `OsGit::Testing::ScratchRepo` (AC 8). Orchestrator work: mark rows G-001 to G-012 and G-121 to G-143 in the checklist (AC 9), and comment on #849, #1154, #1297, #1306, and #982 whether the port fixed each. ## Acceptance criteria 1. Given a branch with a worktree and a clean primary checkout, when `worktree finish` runs, then it merges, removes the worktree, deletes the branch with `-d`, and exits 0 with the same output lines as before. 2. Given a primary checkout with tracked changes, when `worktree finish` runs, then it refuses and exits 6. 3. Given a conflicting merge, when `worktree finish` runs, then it exits 1 and lists the conflicted files. 4. Given a branch that is not an ancestor of the base, when `worktree finish` runs, then it refuses with the same exit code as before. 5. Given an existing branch with no worktree, when `worktree start` runs, then it refuses and exits 1 (ADR-0253). 6. Given a reused worktree behind the base, when `worktree start` runs, then it fast-forwards when it can, and otherwise makes a merge commit, and never rebases. 7. Given the handoff snapshot in a repository where git fails, when it is built, then the failure surfaces as `OsGit::Error` at the edge, and no helper returns `""` for a real failure. Outside a repository and for an unknown commit, `available?` and `commit_known?` still answer `false` (git answered "nothing there"), and `pickup_git_limits_test` passes. 8. Given the os plugin tests (D-01 rows), when they run, then they use `OsGit::Testing::ScratchRepo` and pass. 9. Given rows G-001 to G-012 and G-121 to G-143, when the ticket closes, then each row is marked `- [x]` with `Replaced by` filled in, in `docs/os-git/call-site-checklist.md`. 10. Given the `worktree` command on PATH, when it runs, then it starts no code in `plugins/os/lib/worktree/`, and every git call it makes goes through `OsGit`; the search for `Open3`, `system(`, and backticks in `plugins/os-git/lib/os_git/worktree` finds no git call. The non-git `system` call in `after_merge.rb` (plugin refresh) moves with the policy code. ## Owner sketch ```ruby class OsGit::Integration def initialize(repository, base: repository.primary_branch); end def refusal(branch); end def merge(branch); end def bring_up(worktree); end end class OsGit::Worktree; def self.temporary(repository, ref); end; end ``` ## Non-goals Deleting `Worktree::Git` or any class in `plugins/os/lib/worktree/` (the retirement ticket #1607). Fixing #849, #1154, #1297, #1306, #982 beyond a comment. ## Blocking edges Blocked by the core ticket (#1588) and the ADR ticket (#1585). ## 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: two worktree owners and a private handoff helper ← worktree logic grew inside plugin os ← DESIGN (docs/os-git/use-cases-and-domain.md sections 2.2, 5, 7.2) - 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/lib/worktree/, plugins/os/lib/handoff/repository.rb - Session: 1a5b2630-09c9-46cb-aee4-a5a98e2e7d24 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/1a5b2630-09c9-46cb-aee4-a5a98e2e7d24.jsonl
Author
Owner

Work started by the #1610 orchestrator. Branch: ticket-1589. Load rule: gemspec in plugins/os-git; path source and plain require both use ~/.claude/plugins/os-git.

Work started by the #1610 orchestrator. Branch: ticket-1589. Load rule: gemspec in plugins/os-git; path source and plain require both use ~/.claude/plugins/os-git.
Author
Owner

Implementation 244 ended implementation_failed at test-reviewer (verdict:error). The 11 os-git tests passed review and are red. The cause is the pipeline: it records test changes only under the target's test path, so the plugins/os/tests edits for AC 8 were invisible. Occurrence recorded on #1109. The work continues by the hand-finish path: programmer agent, both suites, lint, one design audit, then merge.

Implementation 244 ended implementation_failed at test-reviewer (verdict:error). The 11 os-git tests passed review and are red. The cause is the pipeline: it records test changes only under the target's test path, so the plugins/os/tests edits for AC 8 were invisible. Occurrence recorded on #1109. The work continues by the hand-finish path: programmer agent, both suites, lint, one design audit, then merge.
Author
Owner

Resolution

Done: Worktree start and finish, Integration, and the handoff git calls moved into os-git. The worktree command on PATH delegates to plugins/os-git/bin/worktree. Old classes stay until #1607.

Evidence: main 93706787. os-git 212 runs, os 237 runs, 0 failures; rubocop clean; contract tests run the new edge. Hand-finished after run 244 failed at test-reviewer (root #1109). Design audit: no data-loss defect; side-by-side old and new CLI runs matched. Rulings 56 to 58 (the body says row 39; renumbered at merge). Checklist rows G-001 to G-012 and G-121 to G-143 checked.

Follow-ups: #1613 timeout-kill state (ticket-skeptic: no matching ticket). Worktree.temporary NotClean note posted on #1592. #1154, #1297, #1306 remain open and are not fixed by the port (comments posted).

## Resolution **Done:** Worktree start and finish, Integration, and the handoff git calls moved into os-git. The worktree command on PATH delegates to plugins/os-git/bin/worktree. Old classes stay until #1607. **Evidence:** main 93706787. os-git 212 runs, os 237 runs, 0 failures; rubocop clean; contract tests run the new edge. Hand-finished after run 244 failed at test-reviewer (root #1109). Design audit: no data-loss defect; side-by-side old and new CLI runs matched. Rulings 56 to 58 (the body says row 39; renumbered at merge). Checklist rows G-001 to G-012 and G-121 to G-143 checked. **Follow-ups:** #1613 timeout-kill state (ticket-skeptic: no matching ticket). Worktree.temporary NotClean note posted on #1592. #1154, #1297, #1306 remain open and are not fixed by the port (comments posted).
jared closed this issue 2026-11-06 19:50: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#1589
No description provided.