| name | aep-git-ref |
| description | Canonical AEP git and worktree reference covering base resolution, lifecycle, naming, task commits, PRs, and recovery. Use when another skill needs a git convention or the user asks how AEP uses git. |
Git + Worktree Reference (AEP)
/aep-launch, /aep-build, and /aep-wrap operate on a plain git repository plus git worktree for parallel agent isolation. There is no separate VCS, no colocated mode, no special wrapper — 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.
AEP migrated from Jujutsu to pure git in 2026-04 (agent training data, universal tooling); rationale in 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:
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:
- 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).
- Auto-detect —
develop if it exists locally or on origin.
- 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
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 — never another feature branch.
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
git worktree list
git -C .feature-workspaces/<name> status
git -C .feature-workspaces/<name> log --oneline "$BASE"..HEAD
Remove (/aep-wrap step 6)
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:
git worktree prune
git branch -D feat/<name>
If .git/worktrees/<name>/ still references a missing path (e.g. you moved the parent directory):
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.
- Don't reuse a branch name after deletion until the corresponding worktree is fully removed (
git worktree prune cleans up dangling refs).
The One-Commit-per-Task Pattern (Phase 4 of /aep-build)
This is the largest AEP-specific convention.
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, never bundling and never splitting:
git add -A
git commit -m "feat(auth): extract auth service"
git add -A
git commit -m "feat(auth): add token refresh flow"
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> — do not rebase |
| 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. |
Never use git push --force (without --force-with-lease). The lease variant fails safely when someone else has pushed since your last fetch.
Publishing & PR Conventions
Push the feature branch
git push -u origin feat/<name>
git push origin feat/<name>
git push --force-with-lease origin feat/<name>
Open the PR — always set base explicitly
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 and can target the wrong branch (especially when a dispatch commit looked like a recent base). PRs targeting the wrong base merge into a non-integration branch, and the code never lands on the integration branch even after a successful merge. In two-branch mode this is doubly important — an inferred base could be production main, which AEP must never merge feature work into directly.
Merge the PR
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:
git pull --ff-only origin "$BASE"
git add <specific-files>
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)
git reflog
git cherry-pick <sha>
git reset --hard <sha>
Lost an entire branch (deleted with -D)
git reflog
git branch <name> <sha>
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:
git log --oneline -n 30
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:
du -sh .feature-workspaces/
du -sh .git/objects
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/ |