# Aep Git Ref

> Canonical AEP git reference: base resolution, worktree lifecycle, naming, task commits, PRs, recovery. Use when a git convention is needed.

- Skill: `memorysaver/aep-git-ref` (Agent Skill)
- Install (CLI): `npx skillmds@latest add memorysaver/aep-git-ref`
- Raw SKILL.md: https://api.skillmd.com/api/skills/memorysaver/aep-git-ref/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: memorysaver (https://skillmd.com/u/memorysaver)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/memorysaver/aep-git-ref

---


# Git + Worktree Reference (AEP)

`/aep-launch`, `/aep-build`, and `/aep-wrap` operate on a plain git repository plus `git worktree` for parallel agent isolation — when these skills say "commit", they mean `git commit`. This skill is the **canonical home** for AEP's git conventions: every other skill resolves `$BASE`, creates/removes worktrees, and opens PRs by pointing here, never by re-inlining these blocks. Rationale for plain git (agent training data, universal tooling): [docs/decisions/migrate-from-jj-to-git.md](https://github.com/memorysaver/agentic-engineering-patterns/blob/main/docs/decisions/migrate-from-jj-to-git.md).

---

## Integration Branch

Every AEP git operation targets the **integration branch** — the branch feature worktrees are
created from, rebased onto, PR'd into, merged into, and where control-plane commits (`/aep-dispatch`,
`/aep-design`, the `/aep-wrap` archive) land. Throughout this skill and the bash blocks in `/aep-launch`,
`/aep-build`, `/aep-wrap`, and the product-context skills, this branch is referred to as `$BASE`.

AEP **auto-detects** one of two modes — no configuration required for the common cases:

| Mode                        | Condition           | Integration (`$BASE`) | Production            |
| --------------------------- | ------------------- | --------------------- | --------------------- |
| **single-branch** (default) | no `develop` branch | `main`                | `main` (same branch)  |
| **two-branch**              | `develop` exists    | `develop` (staging)   | `main` (promote-only) |

A project grows from single- to two-branch mode simply by creating a `develop` branch — no
reconfiguration. In two-branch mode AEP **never touches the production branch** (`main`): promotion
from integration to production (`develop` → `main`) is the user's concern, exactly like deployment,
which is already outside AEP's scope.

### Resolving `$BASE`

Every git-touching skill resolves the integration branch at the top of its first bash block:

```bash
# Resolve AEP integration branch ($BASE): override → auto-detect develop → main
BASE=$(git config --get aep.integration-branch 2>/dev/null || true)
if [ -z "$BASE" ]; then
  if git show-ref --verify --quiet refs/heads/develop \
     || git show-ref --verify --quiet refs/remotes/origin/develop; then
    BASE=develop
  else
    BASE=main
  fi
fi
```

Resolution order:

1. **Explicit override** — `git config --get aep.integration-branch`. This is an **override only**,
   for a non-standard integration branch name (e.g. `staging`, `integration`). You do **not** set it
   for the standard `main`/`develop` cases — those are auto-detected. `/aep-onboard` reports the detected
   mode and only writes this key when you use a non-standard name
   (`git config aep.integration-branch <name>`; unset with `git config --unset aep.integration-branch`).
2. **Auto-detect** — `develop` if it exists locally or on `origin`.
3. **Default** — `main` (single-branch mode).

Because the standard cases are auto-detected (not pinned), a project **grows from single- to
two-branch mode with no reconfiguration**: create `develop` and every skill resolves `$BASE` to it
on the next run. (If you had pinned `aep.integration-branch=main`, that override would win and
suppress the upgrade — which is why setup does not pin the default.)

`git config --get aep.integration-branch` is repo-local and shared across all worktrees via the
common `.git/config`, so `$BASE` resolves identically in the main session and inside any
`.feature-workspaces/<name>` worktree.

---

## Worktree Lifecycle

### Create

```bash
# Resolve $BASE first — see "Resolving $BASE" above
mkdir -p .feature-workspaces
git worktree add -b feat/<name> .feature-workspaces/<name> "$BASE"
```

- Path is **always** under `.feature-workspaces/<name>` (kept gitignored).
- Branch is **always** `feat/<name>` — corresponding to the OpenSpec change name or story id.
- Base is **always** the integration branch `$BASE`.

The worktree shares `.git/objects` with the main repo, so creating it is fast and history is not duplicated. Only the working tree files are duplicated on disk.

### Inspect

```bash
git worktree list                            # all worktrees, branches, paths
git -C .feature-workspaces/<name> status     # status inside a specific worktree
git -C .feature-workspaces/<name> log --oneline "$BASE"..HEAD   # commits unique to the feature branch
```

### Remove (`/aep-wrap` step 6)

```bash
git worktree remove .feature-workspaces/<name>
git branch -d feat/<name>
```

If `git branch -d` warns the branch isn't fully merged (likely because the PR was squash-merged so commit SHAs differ), force with `git branch -D feat/<name>` _after_ confirming via `gh pr view <number> --json state` that the PR is `MERGED`.

### Recover from a corrupt worktree directory

If `.feature-workspaces/<name>/` was deleted manually:

```bash
git worktree prune                           # forget the orphaned registration
git branch -D feat/<name>                    # delete the orphan branch (if needed)
```

If `.git/worktrees/<name>/` still references a missing path (e.g. you moved the parent directory):

```bash
git worktree repair .feature-workspaces/<name>
```

---

## Branch Naming

| Branch            | Pattern              | Created by                    |
| ----------------- | -------------------- | ----------------------------- |
| Feature work      | `feat/<short-name>`  | `/aep-launch` (one per story) |
| Migration / chore | `chore/<short-name>` | manually, when applicable     |
| Hotfix off main   | `fix/<short-name>`   | manually                      |
| Migration project | `migration/<topic>`  | manually                      |

**Rules:**

- Use kebab-case after the slash. No spaces, no underscores.
- Keep names short (≤ 30 chars) — they appear in tab labels, signal files, and PR titles.
- Reuse a deleted branch name once its worktree is fully removed (`git worktree prune` clears the dangling refs).

---

## The One-Commit-per-Task Pattern (Phase 4 of `/aep-build`)

### What

`tasks.md` lists N tasks. The feature branch ends up with N commits — one per task — in the same order. Conventional-commit format. Workspace agents implement linearly, committing after each task, so the commit count matches the task count:

```bash
# Implement task 1
# ... edit files ...
git add -A
git commit -m "feat(auth): extract auth service"

# Implement task 2
# ... edit files ...
git add -A
git commit -m "feat(auth): add token refresh flow"

# ... etc.
```

After each commit, record the short SHA in `.dev-workflow/feature-verification.json` against the matching task entry's `commit_sha` field.

### Why this shape

- The PR's commit list reads like a table of contents matching `tasks.md`.
- Each commit is a self-contained unit that the evaluator (in Phase 5) and reviewers (in Phase 11) can `git show <sha>` independently.
- We squash-merge at PR-merge time, so per-commit hygiene only matters for review readability — the integration branch's history stays clean automatically.
- It removes any need to rewrite history mid-stream, which is where agents most often go off the rails.

### What to do when something goes wrong

| Situation                                                 | Action                                                                                                                                                            |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Forgot a file in the just-committed task                  | `git add <file> && git commit --amend --no-edit` (only safe **before** the next task's commit)                                                                    |
| Realized the previous task is broken, several commits ago | Add a new commit: `fix(<scope>): correct <issue from task N>` — the history stays append-only                                                                     |
| Review feedback or eval-loop FAIL                         | Add a follow-up commit: `fix(<scope>): address review on <topic>`                                                                                                 |
| Need to update against new origin/`$BASE`                 | `git fetch origin && git rebase origin/"$BASE" && git push --force-with-lease origin feat/<name>`                                                                 |
| Conflicts during rebase                                   | Resolve in working tree, `git add <files> && git rebase --continue`. If hopelessly tangled, `git rebase --abort` and surface to the orchestrator via signal file. |

Push a rewritten branch with `--force-with-lease` rather than a bare `--force`: the lease variant fails safely when someone else has pushed since your last fetch.

---

## Publishing & PR Conventions

### Push the feature branch

```bash
# First push (after Phase 9 cleanup):
git push -u origin feat/<name>

# Subsequent pushes (review fixes, rebases):
git push origin feat/<name>
# or after a rebase:
git push --force-with-lease origin feat/<name>
```

### Open the PR — always set base explicitly

```bash
# $BASE is the integration branch — resolve it first (see "Integration Branch" above)
gh pr create --base "$BASE" --title "<title>" --body "<body>"
```

The `--base "$BASE"` flag is **mandatory**: without it `gh` infers the base from local branch state (a dispatch commit can look like a recent base), the PR merges into a non-integration branch, and the code never lands on `$BASE`. In two-branch mode an inferred base could be production `main`, which AEP never merges feature work into.

### Merge the PR

```bash
gh pr merge <number> --squash --delete-branch
```

We always squash-merge. The feature branch's per-task commits collapse into one commit on the integration branch (`$BASE`), which keeps its log readable while preserving the per-task review trail in the PR's "Commits" tab.

---

## Control-Plane Commits (on the integration branch)

`/aep-design`, `/aep-dispatch`, `/aep-envision`, `/aep-map`, `/aep-calibrate`, `/aep-validate`, `/aep-reflect`, and the `/aep-wrap` archive step all commit directly to the integration branch (`$BASE`). The pattern is identical:

```bash
# Resolve $BASE first — see "Resolving $BASE" above
git pull --ff-only origin "$BASE"       # fail-fast if the integration branch has diverged
git add <specific-files>                # never -A on the integration branch; be explicit
git commit -m "<conventional message>"
git push origin "$BASE"
```

The `--ff-only` is intentional — it refuses a non-fast-forward pull, which means concurrent pushes get caught instead of silently merged. If the pull fails because someone else pushed first, fetch, rebase your work, and try again.

---

## Recovery

### Lost a commit (committed to wrong branch, or amend'd away)

```bash
git reflog                              # find the orphaned commit's SHA
git cherry-pick <sha>                   # bring it onto the current branch
# or
git reset --hard <sha>                  # if you want to rewind to that point
```

### Lost an entire branch (deleted with -D)

```bash
git reflog                              # the deleted branch's tip is in here
git branch <name> <sha>                 # recreate the branch at that tip
```

### Lost work in a worktree directory you removed

If you ran `rm -rf .feature-workspaces/<name>` without committing first, the work is gone — `git worktree` only tracks committed history. `.dev-workflow/` files (signals, lessons, progress) are also gone since they're gitignored. Use `git worktree remove` instead of `rm -rf` next time; it refuses if the worktree has uncommitted changes.

### OpenSpec files missing after rebase

If the dispatch commit's OpenSpec files disappeared during a rebase or merge:

```bash
git log --oneline -n 30                 # find the dispatch commit SHA
git restore --source=<sha> -- openspec/
```

This restores `openspec/` to the state of the dispatch commit without touching anything else.

---

## Disk Budget

Each worktree adds approximately one full working-tree copy on disk. `.git/objects` is shared across all worktrees, so commit history isn't duplicated. For a typical AEP repo (~100 MB working tree, multi-GB `.git/objects` after years of history), three concurrent agents adds ~300 MB on top of one shared `.git/`.

Monitor with:

```bash
du -sh .feature-workspaces/             # working-tree footprint
du -sh .git/objects                     # shared history
```

If disk pressure hits before agents finish, prefer pausing new `/aep-launch` invocations over forcibly removing in-flight worktrees.

---

## Cheat Sheet

| You want to…                 | Run                                                                     |
| ---------------------------- | ----------------------------------------------------------------------- |
| Create a feature worktree    | `git worktree add -b feat/<n> .feature-workspaces/<n> "$BASE"`          |
| List all worktrees           | `git worktree list`                                                     |
| See feature-branch commits   | `git log --oneline "$BASE"..HEAD`                                       |
| Sync against latest `$BASE`  | `git fetch origin && git rebase origin/"$BASE"`                         |
| Push after rebase            | `git push --force-with-lease origin feat/<n>`                           |
| Open PR                      | `gh pr create --base "$BASE"`                                           |
| Merge PR                     | `gh pr merge <#> --squash --delete-branch`                              |
| Remove worktree post-merge   | `git worktree remove .feature-workspaces/<n> && git branch -d feat/<n>` |
| Recover deleted commit       | `git reflog` then `git cherry-pick <sha>`                               |
| Restore lost openspec/ files | `git restore --source=<sha> -- openspec/`                               |

