feat(workspace): gate stale worktree creation + opt-in rebase (closes #52)#54
Merged
Conversation
Closes #52 Builds on #53's non-breaking staleness signal. `worktree add` now refuses to materialize a worktree that would be more than `datamachine_worktree_stale_threshold` commits (default 50) behind its upstream, tearing the half-cooked checkout down and returning a `worktree_stale` WP_Error with remediation options. Pass `--allow-stale` to opt in, or `--rebase-base` to auto-rebase onto the upstream tip before returning. On rebase conflicts the rebase is aborted — `--rebase-base` is not a silent bypass. ## Behavior change Previously: `worktree add` happily cooked on top of any local branch, no matter how stale. Agents discovered multi-hundred-commit drift at PR-review time when GitHub flagged conflicts. Now: by default, a worktree that would land >50 commits behind upstream is rolled back at create-time with a WP_Error pointing at four concrete remediation paths: Worktree base is 51 commits behind origin/main (threshold: 50). Options: - workspace git-pull data-machine-code --allow-primary-mutation - worktree add … --from=origin/main - worktree add … --rebase-base - worktree add … --allow-stale Threshold is filterable per-site/per-repo via `datamachine_worktree_stale_threshold`. ## Rebase semantics `--rebase-base` picks the right target per path: - Existing-local-branch: rebase onto `@{upstream}` - New-branch-off-local-base: rebase onto `origin/<base>` Success clears the behind-count (worktree passes the gate cleanly). Failure aborts the rebase, the worktree stays at its pre-rebase HEAD, and `rebase_succeeded: false` + `rebase_error: <tail>` are surfaced. Critically, the staleness gate STILL fires after a failed rebase — `--rebase-base` alone on a conflicting rebase isn't a silent `--allow-stale` bypass. ## Changes - `Workspace::worktree_add()` — new `$allow_stale` + `$rebase_base` params. Rebase runs before the gate (success nullifies gate). Gate runs after staleness probe, computes filterable threshold, tears the worktree down + returns WP_Error on violation. - `effective_behind_count()` helper — picks the one behind-count that matters for gating (existing-branch or new-branch-off-local-base, whichever is present). - `try_rebase_worktree()` helper — selects upstream target, runs rebase, aborts on failure, zeroes the relevant behind-count on success. - `WorkspaceAbilities` — input schema gains `allow_stale` + `rebase_base` (both default false). Output schema gains `gate_threshold`, `rebase_attempted`, `rebase_target`, `rebase_succeeded`, `rebase_error`. - `WorkspaceCommand` — new `--allow-stale` + `--rebase-base` flags with full help blocks + examples. `render_worktree_freshness()` gains a rebase block that renders BEFORE staleness (success: "rebased onto <target>", failure: "⚠ rebase onto <target> failed" + error tail). - `tests/smoke-worktree-staleness.php` — 7 new real-git-fixture assertions for rebase success (branch 2 behind → 0 behind after rebase, consumer commit preserved) and rebase conflict handling (non-zero exit, abort restores pre-rebase SHA, behind-count preserved).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds on #53's non-breaking staleness signal.
worktree addnow refuses to materialize a worktree that would be more thandatamachine_worktree_stale_thresholdcommits (default 50) behind upstream, tearing the half-cooked checkout down and returning aworktree_staleWP_Errorwith four concrete remediation options. Pass--allow-staleto opt in, or--rebase-baseto auto-rebase onto the upstream tip before returning.Closes #52 (this is option 2 from the issue — the behavior-change half).
Behavior change
Before:
worktree addhappily cooked on top of any local branch, no matter how stale. Agents discovered multi-hundred-commit drift at PR-review time when GitHub flagged merge conflicts.After: by default, a worktree that would land >50 commits behind upstream is rolled back at create-time:
Threshold is filterable per-site / per-repo via
datamachine_worktree_stale_threshold(default 50).Rebase semantics
--rebase-basepicks the right target per path:@{upstream}origin/<base>Success clears the behind-count (worktree passes the gate cleanly). On conflict, the rebase is aborted — worktree stays at its pre-rebase HEAD, and
rebase_succeeded: false+rebase_error: <tail>are surfaced. Critically, the gate still fires after a failed rebase.--rebase-basealone on a conflicting rebase is NOT a silent--allow-stalebypass; the agent has to acknowledge the conflict explicitly.Fresh branches (no staleness to rebase) are a clean no-op — no
rebase_attemptedfield, no noise in the output.Changes
Workspace::worktree_add()— signature gains$allow_stale = falseand$rebase_base = false. Rebase runs before the gate (success nullifies gate). Gate runs after the staleness probe, computes the filterable threshold, tears the worktree down withworktree remove --forceand returns aworktree_staleWP_Erroron violation.effective_behind_count()helper — picks the one behind-count that matters for gating (existing-branchstale_commits_behindor new-branch-off-local-basebase_stale_commits_behind, whichever is present).try_rebase_worktree()helper — selects upstream target, runs the rebase, aborts on failure, zeroes the relevant behind-count on success.WorkspaceAbilities— input schema gainsallow_stale+rebase_base(both defaultfalse, both optional). Output schema gainsgate_threshold,rebase_attempted,rebase_target,rebase_succeeded,rebase_error.WorkspaceCommand— new--allow-stale+--rebase-baseflags with full help blocks + examples.render_worktree_freshness()gains a rebase block that renders BEFORE staleness (success:Freshness: rebased onto <target>, failure:⚠ rebase onto <target> failed — worktree stayed at pre-rebase HEAD+ error tail).tests/smoke-worktree-staleness.php— 7 new real-git-fixture assertions: branch 2 behind → 0 behind after rebase, consumer commit preserved on HEAD, conflicting rebase exits non-zero,rebase --abortrestores pre-rebase SHA, behind-count preserved after abort.Testing
Smoke suite: 92/92 passing
Live CLI tests on
intelligence-chubes4Studio siteFixture: local branch
test/stale-branch-with-upstreamtrackingorigin/main, 51 commits behind.WP_Errorwith guidance, worktree torn downlsconfirms no dir left behind--allow-stalebypasses gate--allow-stale⚠ 51 commits behind origin/mainwarning--rebase-baseon stale branch--rebase-baseFreshness: rebased onto @{upstream}--rebase-baseon fresh branch--rebase-base --from=origin/mainConflict path validated via smoke fixture (scenario B in
smoke-worktree-staleness.php) — real git conflict,git rebase --abort, pre-rebase SHA + behind-count both restored.Not in this PR
--rebase-merges,--autosquashetc.). Keep the rebase shape simple until someone needs more.AI assistance
Workspace, ability schema additions, CLI flags + rendering, 7 new smoke assertions). Live-tested all four CLI scenarios against a synthetic 51-commit-stale local branch ondata-machine-codefrom this Studio site. Chris steered scope (PR 1 non-breaking additive, PR 2 gating behavior change) and caught the original kimaki session-spawn mistake on PR 1.