diff --git a/AGENTS.md b/AGENTS.md index a328e24..2381ae0 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 8c5586d..c65d685 100644 --- a/mise.toml +++ b/mise.toml @@ -151,6 +151,55 @@ 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. 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 = """ +#!/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 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 + 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 ##### ##################