From a398cf0521f602ec91c75d06b78616586e789087 Mon Sep 17 00:00:00 2001 From: bgagent Date: Fri, 29 May 2026 13:49:58 -0400 Subject: [PATCH 1/2] feat(dx): add worktree:sync mise task to rebase clean worktrees on origin/main Worktrees branched from origin/main drift silently as other PRs merge, causing unnecessary conflicts at push time and CI runs against stale bases. Issue #197. The new `mise run worktree:sync` task fetches origin/main and rebases every clean linked worktree onto it. Dirty worktrees and main itself are skipped; conflicting rebases are aborted and reported with a non-zero exit so the operator can resolve them explicitly rather than leaving a half-rebased state. Closes #197 --- AGENTS.md | 3 ++- mise.toml | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 02296af0..983d53d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,7 +49,7 @@ Handler entry tests: `cdk/test/handlers/orchestrate-task.test.ts`, `create-task. - **`mise run build`** runs **`//agent:quality`** before CDK — the deployed image bundles **`agent/`**; agent changes belong in that tree. - **`prek install`** fails if Git **`core.hooksPath`** is set — another hook manager owns hooks; see [CONTRIBUTING.md](./CONTRIBUTING.md). - **Editing on `main` directly** — ALWAYS create a worktree with a feature branch for changes, even trivial ones. Main should stay clean; all work flows through worktree → branch → PR → merge. -- **Git worktrees** — Always **`git fetch origin main`** before creating a new worktree to ensure you branch from the latest remote state. `node_modules/` and `agent/.venv/` are per-tree (not shared). Run **`mise run install`** in each new worktree before building. All CDK path references (`__dirname`-relative) and mise `config_roots` resolve correctly without extra setup. +- **Git worktrees** — Always **`git fetch origin main`** before creating a new worktree to ensure you branch from the latest remote state. `node_modules/` and `agent/.venv/` are per-tree (not shared). Run **`mise run install`** in each new worktree before building. All CDK path references (`__dirname`-relative) and mise `config_roots` resolve correctly without extra setup. Run **`mise run worktree:sync`** periodically to rebase clean linked worktrees onto **`origin/main`** so they don't drift behind merged PRs. - **Bumping Cedar engines in isolation** — `cedarpy` (Python, `agent/pyproject.toml`) and `@cedar-policy/cedar-wasm` (TypeScript, `cdk/package.json`) are two language bindings over the same Cedar Rust core. They MUST move together; even patch-version drift between bindings can yield divergent `(decision, matching_rule_ids)` on the same `(policy, input)` — invisible to per-side unit tests, caught (only) by `contracts/cedar-parity/` golden fixtures in CI. If you bump one engine you MUST bump the other to a tested-compatible version AND refresh the parity fixtures in the same commit. Both pins are EXACT (no `^`/`~`). See `docs/design/CEDAR_HITL_GATES.md` §15.6 (decision #23) and the parity-contract banner in `mise.toml`. **DO NOT** accept upstream's "Update branch" or auto-merge suggestions on cedarpy without verifying parity with cedar-wasm. ### Tech stack @@ -117,6 +117,7 @@ Run `mise tasks --all` (with `MISE_EXPERIMENTAL=1`) for the full list. Common co - **`mise run security:gh-actions`** — Static analysis of GitHub Actions under **`.github/`** ([zizmor](https://github.com/zizmorcore/zizmor)). - **`mise run hooks:install`** — Re-install **[prek](https://github.com/j178/prek)** git hooks (also run automatically at the end of **`mise run install`** inside a Git checkout). See [CONTRIBUTING.md](./CONTRIBUTING.md) if `core.hooksPath` blocks install. - **`mise run hooks:run`** — Run the same **pre-commit** and **pre-push** hook stages on all files (local verification). +- **`mise run worktree:sync`** — Fetch **`origin/main`** and rebase every clean linked worktree onto it (issue #197). Dirty worktrees and `main` itself are skipped; conflicting rebases are aborted and reported with a non-zero exit status. Use these instead of running `tsc`, `jest`, or `cdk` directly when possible, so the project's scripts and config stay consistent. diff --git a/mise.toml b/mise.toml index 8c5586d4..f890cb67 100644 --- a/mise.toml +++ b/mise.toml @@ -151,6 +151,47 @@ run = [ "mise run hooks:pre-push:tests", ] +################## +#### WORKTREE #### +################## + +# Issue #197: worktrees branched from origin/main drift silently as other PRs +# merge. This task fetches origin/main and rebases every clean linked worktree +# onto it. Dirty worktrees are skipped (no rebase on uncommitted changes); main +# itself (the primary worktree) is also skipped. Branches that fail to rebase +# are aborted and reported so the next push surfaces the conflict explicitly +# rather than silently leaving a half-rebased state. +[tasks."worktree:sync"] +description = "Fetch origin/main and rebase every clean linked worktree onto it" +run = """ +#!/usr/bin/env bash +set -eu -o pipefail +git fetch origin main +status=0 +# tab-delimited "worktreebranch" pairs, one per linked worktree +pairs="$(git worktree list --porcelain | awk ' + /^worktree / { wt = substr($0, 10) } + /^branch / { print wt "\\t" substr($0, 8); wt = "" } +')" +while IFS=$'\\t' read -r wt branch; do + [ -z "${wt:-}" ] && continue + short_branch="${branch#refs/heads/}" + # Skip the main branch — `git pull` is the right tool for it, not rebase. + case "$short_branch" in main|master) continue ;; esac + if [ -n "$(git -C "$wt" status --porcelain)" ]; then + echo "SKIP $short_branch ($wt) — dirty working tree" + continue + fi + echo "Rebasing $short_branch ($wt) onto origin/main..." + if ! git -C "$wt" rebase origin/main; then + echo "FAIL $short_branch — rebase aborted (resolve conflicts manually)" + git -C "$wt" rebase --abort || true + status=1 + fi +done <<< "$pairs" +exit $status +""" + ################## ##### BUILD ##### ################## From fe30fb77f4fb9892ee97d9a3ea96647d45d14dba Mon Sep 17 00:00:00 2001 From: bgagent Date: Mon, 1 Jun 2026 13:41:23 -0400 Subject: [PATCH 2/2] feat(dx): skip already-merged branches in worktree:sync Address review feedback on PR #218: detect worktrees whose HEAD is already an ancestor of origin/main (i.e. the branch has merged) and skip them with a MERGED message rather than attempting a no-op rebase. The operator can then prune the worktree at their leisure. Suggested by @scottschreckengaust. --- mise.toml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/mise.toml b/mise.toml index f890cb67..c65d6857 100644 --- a/mise.toml +++ b/mise.toml @@ -157,10 +157,11 @@ run = [ # Issue #197: worktrees branched from origin/main drift silently as other PRs # merge. This task fetches origin/main and rebases every clean linked worktree -# onto it. Dirty worktrees are skipped (no rebase on uncommitted changes); main -# itself (the primary worktree) is also skipped. Branches that fail to rebase -# are aborted and reported so the next push surfaces the conflict explicitly -# rather than silently leaving a half-rebased state. +# onto it. Worktrees on already-merged branches are reported and skipped (no +# rebase needed); dirty worktrees are skipped (no rebase on uncommitted +# changes); main itself (the primary worktree) is also skipped. Branches that +# fail to rebase are aborted and reported so the next push surfaces the +# conflict explicitly rather than silently leaving a half-rebased state. [tasks."worktree:sync"] description = "Fetch origin/main and rebase every clean linked worktree onto it" run = """ @@ -178,6 +179,13 @@ while IFS=$'\\t' read -r wt branch; do short_branch="${branch#refs/heads/}" # Skip the main branch — `git pull` is the right tool for it, not rebase. case "$short_branch" in main|master) continue ;; esac + # If the branch tip is already in origin/main, the work has merged and a + # rebase would either no-op or replay merged commits depending on history. + # Report and skip so the operator can prune the worktree at their leisure. + if git -C "$wt" merge-base --is-ancestor HEAD origin/main 2>/dev/null; then + echo "MERGED $short_branch ($wt) — already in origin/main, no rebase needed" + continue + fi if [ -n "$(git -C "$wt" status --porcelain)" ]; then echo "SKIP $short_branch ($wt) — dirty working tree" continue