diff --git a/.agents/commands/upstream-sync.md b/.agents/commands/upstream-sync.md new file mode 100644 index 000000000..472808604 --- /dev/null +++ b/.agents/commands/upstream-sync.md @@ -0,0 +1,303 @@ +--- +description: Refresh the Superset upstream mirror and open a safe sync PR into main when needed +allowed-tools: Bash +argumentHint: "[--mirror-only]" +--- + +# Upstream Sync + +Refresh `origin/upstream` from `superset-sh/superset:main`, then open a proxy-branch PR into `origin/main` only when O3 Code is actually behind upstream. + +This command is shared from `.agents/commands/`. `.claude/commands` and `.cursor/commands` are symlinks to that directory, so do not duplicate this file elsewhere. + +## Arguments + +- `--mirror-only` — fast-forward `origin/upstream`, then stop before creating a PR. + +## Repository Model + +- `origin/main` is the O3 Code product branch. +- `superset/main` is the upstream Superset source branch. +- `origin/upstream` is a pure mirror of `superset/main`. +- Conflict resolution happens only on a proxy branch based on `origin/main`. + +`origin/upstream` must stay pure: never commit to it, never resolve conflicts on it, never rebase it, and never force-push it. + +## Hard Rules + +- Do not push directly to `main`. +- Do not push to the `superset` remote. +- Do not use `--force` or `--force-with-lease`. +- Do not squash or rebase the sync PR. Merge it with a merge commit so `origin/upstream` remains in `main` history. +- Do not include agent attribution in branch names, commit messages, PR titles, PR bodies, or comments. +- Start from a clean worktree. If there are local changes, stop and report them before fetching, switching branches, or merging. +- Follow the repository rules in `AGENTS.md`; do not duplicate those rules in this command. + +## Workflow + +### 1. Parse Arguments + +Read `$ARGUMENTS` and set: + +```bash +MIRROR_ONLY=0 +for arg in $ARGUMENTS; do + case "$arg" in + --mirror-only) MIRROR_ONLY=1 ;; + "") ;; + *) + echo "Unknown argument: $arg" + exit 1 + ;; + esac +done +``` + +### 2. Preflight + +Run: + +```bash +git status --short --branch +origin_url="$(git remote get-url origin)" +superset_url="$(git remote get-url superset 2>/dev/null || true)" +if [ -z "$superset_url" ]; then + git remote add superset https://github.com/superset-sh/superset.git + superset_url="$(git remote get-url superset)" +fi +gh auth status +repo_name="$(gh repo view --json nameWithOwner --jq .nameWithOwner)" +default_branch="$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name)" +printf 'origin=%s\nsuperset=%s\nrepo=%s\ndefault_branch=%s\n' \ + "$origin_url" "$superset_url" "$repo_name" "$default_branch" +if [ "$repo_name" != "o3dotdev/o3-code" ] || [ "$default_branch" != "main" ]; then + echo "Unexpected repository context; refusing upstream sync." + exit 1 +fi +``` + +Then enforce a clean worktree before any mutating sync step: + +```bash +if [ -n "$(git status --porcelain)" ]; then + echo "Working tree is dirty. Commit, stash, or discard local changes before upstream sync." + git status --short + exit 1 +fi +``` + +Expected remotes: + +- `origin`: `https://github.com/o3dotdev/o3-code.git` +- `superset`: `https://github.com/superset-sh/superset.git` + +### 3. Fetch And Compare + +Run: + +```bash +git fetch --no-tags origin main upstream +git fetch --no-tags superset main +git rev-list --left-right --count origin/main...origin/upstream +git rev-list --left-right --count origin/upstream...superset/main +git log --oneline origin/upstream..superset/main +git log --oneline origin/main..origin/upstream +git show --no-patch --format='origin/main %H %s' origin/main +git show --no-patch --format='origin/upstream %H %s' origin/upstream +git show --no-patch --format='superset/main %H %s' superset/main +origin_main_sha="$(git rev-parse origin/main)" +origin_upstream_before="$(git rev-parse origin/upstream)" +superset_main_sha="$(git rev-parse superset/main)" +``` + +Record: + +- current `origin/main` SHA +- current `origin/upstream` SHA +- current `superset/main` SHA +- upstream range being mirrored, if any +- whether `origin/upstream` already matches `superset/main` +- how many commits `origin/main` is missing from the refreshed mirror + +### 4. Refresh The Mirror + +Before pushing the mirror, verify fast-forward ancestry: + +```bash +git merge-base --is-ancestor origin/upstream superset/main +``` + +If this fails, stop. `origin/upstream` has diverged from Superset and needs human review. + +Run: + +```bash +git push origin refs/remotes/superset/main:refs/heads/upstream +git fetch --no-tags origin upstream main +git show --no-patch --format='origin/upstream %H %s' origin/upstream +origin_upstream_after="$(git rev-parse origin/upstream)" +test "$origin_upstream_after" = "$(git rev-parse superset/main)" +``` + +If the push is rejected, fetch again and rerun the ancestry check. Retry only if `origin/upstream` is still an ancestor of `superset/main`. + +### 5. Decide Whether A PR Is Needed + +After refreshing the mirror, run: + +```bash +behind="$(git rev-list --count origin/main..origin/upstream)" +echo "$behind" +``` + +If `behind` is `0`, stop. `main` already contains the refreshed upstream mirror; do not create an empty PR. + +If `--mirror-only` was passed, stop after reporting `behind`. + +Check for an existing open sync PR: + +```bash +gh pr list --base main --state open --json number,title,headRefName,url \ + --jq '.[] | select(.headRefName == "upstream" or (.headRefName | startswith("sync/upstream-")))' +``` + +If one exists, do not open a duplicate. If its head branch is `upstream`, stop: the mirror branch must stay pure, so that PR should be replaced by a proxy-branch sync PR. If its head branch starts with `sync/upstream-`, update that branch: + +```bash +existing_sync_branch="$(gh pr list --base main --state open --json headRefName \ + --jq '[.[] | select(.headRefName == "upstream" or (.headRefName | startswith("sync/upstream-")))][0].headRefName // ""')" +if [ -n "$existing_sync_branch" ]; then + if [ "$existing_sync_branch" = "upstream" ]; then + echo "Open sync PR uses mirror branch directly. Do not commit to upstream; replace it with a proxy branch." + exit 1 + fi + git fetch origin "$existing_sync_branch" + git switch "$existing_sync_branch" 2>/dev/null || \ + git switch -c "$existing_sync_branch" --track "origin/$existing_sync_branch" + sync_branch="$existing_sync_branch" + git merge --no-ff --no-edit origin/upstream +fi +``` + +### 6. Create The Proxy Branch + +If `sync_branch` is already set from an existing proxy sync PR, skip branch creation and continue with the conflict/fork-delta review below. Otherwise, create the PR branch from `origin/main`. Use today's UTC date in `YYYYMMDD` format; if the branch exists locally or remotely, append `-2`, `-3`, etc. + +```bash +git switch main +git pull --ff-only origin main +sync_date="$(date -u +%Y%m%d)" +sync_branch="sync/upstream-$sync_date" +suffix=2 +while git show-ref --verify --quiet "refs/heads/$sync_branch" || \ + git ls-remote --exit-code --heads origin "$sync_branch" >/dev/null 2>&1; do + sync_branch="sync/upstream-$sync_date-$suffix" + suffix=$((suffix + 1)) +done +git switch -c "$sync_branch" +git merge --no-ff --no-edit origin/upstream +``` + +Resolve conflicts only on the proxy branch. Keep upstream fixes, but preserve O3-specific choices. If the merge reports conflicts, complete the merge after resolving them: + +```bash +git diff --name-only --diff-filter=U +git status --short +git add +git commit --no-edit +``` + +Before resolving conflicts and before treating a conflict-free merge as ready, read the canonical fork-delta references: + +- `AGENTS.md` for repo rules, including command placement, git/GitHub attribution, database migration handling, and Next.js request interception naming. +- `docs/internal/fork-deltas/README.md` for when fork-delta entries are required. +- `docs/internal/fork-deltas/registry.md` for O3-specific behavior that must be preserved when porting upstream changes. + +Treat those files as the source of truth. If a sync exposes a new intentional O3-vs-upstream behavior difference, update `docs/internal/fork-deltas/registry.md` in the sync PR instead of adding a one-off checklist here. + +Useful conflict checks: + +```bash +git status --short +git diff --check +git diff --name-only --diff-filter=U +git diff origin/main...HEAD --stat +``` + +### 7. Validate + +For an upstream content sync, run: + +```bash +bun run lint:fix +bun run lint +bun run typecheck +bun test +``` + +Run targeted tests for any conflict area. Run `bun run build` when upstream touches packaging, Next apps, Electron, shared config, or release workflows. + +If validation fails from an unrelated existing issue, capture the exact command and failure in the PR body. + +### 8. Open The PR + +Push the proxy branch: + +```bash +git push -u origin "$sync_branch" +``` + +Create the PR body before opening the PR: + +```bash +cat >/tmp/upstream-sync-pr.md <}\` +- New mirror: \`${origin_upstream_after:-$(git rev-parse origin/upstream)}\` +- Superset range: \`${origin_upstream_before:-}..${origin_upstream_after:-$(git rev-parse origin/upstream)}\` + +## Conflict And Fork-Delta Notes +- Conflicts: +- Fork-delta registry updates: +- Merge method required: merge commit, not squash/rebase. + +## Validation +- + +## Follow-Ups +- +EOF +``` + +Replace any placeholder values before creating the PR if the SHA variables were not preserved in the shell session. + +Create a PR into `main`: + +```bash +gh pr create --base main --head "$sync_branch" \ + --title "Sync upstream Superset changes" \ + --body-file /tmp/upstream-sync-pr.md +``` + +The PR body must include: + +- Upstream range, for example `superset-sh/superset ..`. +- Whether `origin/upstream` was fast-forwarded. +- Conflict areas and fork-delta decisions. +- Validation commands and results. +- Deferred follow-up work, if any. + +### 9. Post-Merge Check + +After the PR merges: + +```bash +git fetch --no-tags origin main upstream +git merge-base --is-ancestor origin/upstream origin/main +``` + +The command must exit `0`. If it does not, the sync PR was merged incorrectly or the mirror advanced after the PR branch was created.