# Merge All Features

> End-to-end multi-repo feature shipping. Use whenever the user invokes "/merge-all-features" or asks to commit, push, and raise PRs across one or many repos at once. Splits a target folder into independent repos, groups uncommitted changes feature-by-feature, runs ESLint + Jest gates before every commit, commits in dependency order with clean messages (NO co-author trailers), pushes, opens PRs with full descriptions (purpose, file changes, work done, use cases, splatter zone / blast radius, new imports, feature flags, required reviewers, test + lint results), then self-reviews and posts the review on the PR with required actions. Works on a single repo, a folder of repos, or the current directory if it is itself a repo. Spawns parallel subagents (one per repo) on cheap/default models so independent repos progress concurrently. If one repo is blocked, surfaces the blocker to the user in **bold** and keeps going on the others.

- Skill: `aravinds-wick/merge-all-features` (Agent Skill, multi-file: 13 files)
- Install (CLI): `npx skillmds@latest add aravinds-wick/merge-all-features`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aravinds-wick/merge-all-features/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: AravindS-Wick (https://skillmd.com/u/aravinds-wick)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aravinds-wick/merge-all-features

---

# /merge-all-features

Ship feature work across one or many repositories with full automation: detect repos, group changes by feature, gate (lint + test), commit cleanly, push, open detailed PRs, self-review, and report. Independent repos run in parallel. Blocked repos do not stall the rest.

This skill is invoked by the slash command `/merge-all-features` or by any request matching its description. The orchestrator (you, Claude, in the main thread) is responsible for coordination, the final summary, and any user-facing decisions. Per-repo work is delegated to subagents using cheap/default models.

---

## The hard rules (non-negotiable)

These exist because they protect the user from the most expensive mistakes — broken main branches, unreviewed merges, attribution mistakes, and silent failures.

1. **No co-author / co-owner trailers.** Commit messages must NOT contain `Co-Authored-By:`, `Co-Authored:`, `Signed-off-by: Claude`, "🤖 Generated with", "Generated by Claude", or any equivalent attribution line. Use `git commit -m "<message>"` with a clean body. If you suspect any template might inject these, run `scripts/strip_trailers.sh <commit-msg-file>` before committing. This rule applies to PR descriptions and review comments too.

2. **Always stash-backup before any commit or merge.** Run `scripts/stash_backup.sh <repo> <feature-name>` as the very first action for each feature, before staging or gating. The script creates a named stash (`backup/<feature>/<timestamp>`), then immediately pops it to restore the working tree — the backup ref stays in the stash list. If the backup fails (e.g. no commits yet), warn the user but continue — do NOT block on stash failure.

3. **Gates are blocking.** ESLint and Jest run before every commit. If either fails, attempt auto-fix once (`eslint --fix`), rerun, and if it still fails, STOP that repo — do NOT commit broken code. Mark the repo as **blocked**, report it, move on.

4. **One feature per commit by default.** Only lump features into a single commit/PR when the user has explicitly said "these can be pushed and lumped together" (or equivalent). Otherwise: one feature → one commit → one PR.

5. **Correct order matters.** Commit in dependency order: config/types → core libs → consumers → tests/docs. If feature B imports from feature A, A commits first.

6. **Blocked ≠ stopped.** When a repo is blocked, alert the user in **bold** with the repo name and reason. Then keep going — other repos in the run continue in parallel. Never abandon the whole run for a single repo's blocker.

7. **Detect, don't assume.** The target may be a single repo (`.git` at root), a folder of repos (multiple subdirs with `.git`), or `.` itself. Detect via `scripts/detect_repos.sh` before doing anything else.

---

## High-level workflow

```
1. RESOLVE TARGET       → which folder, what repos live in it
2. SCOPE PER REPO       → uncommitted changes grouped into features
3. CONFIRM PLAN         → show the user the per-repo feature plan, get OK
4. SPAWN SUBAGENTS      → one per repo, parallel, cheap model
5. EACH SUBAGENT runs:  stash-backup → branch → gate → commit → push → PR → self-review → post
6. AGGREGATE REPORT     → collected status per repo, blockers in bold
```

The orchestrator handles 1–4 and 6. The subagent prompt (defined below) handles 5.

---

## Step 1 — Resolve target

Default target is the user's current working directory unless they specified a path. Run:

```bash
bash scripts/detect_repos.sh "$TARGET_DIR"
```

It prints one absolute repo path per line. Three cases:

- **0 repos** → tell the user, stop. Don't initialise anything without their say-so.
- **1 repo** → single-repo flow; still uses the same subagent path for consistency.
- **N repos** → multi-repo flow; N subagents in parallel.

---

## Step 2 — Scope changes per repo

For each repo, gather:

```bash
cd "$repo" && git status --porcelain=v1 -uall
cd "$repo" && git diff --stat HEAD
cd "$repo" && git log -1 --format=%H 2>/dev/null  # is there even a HEAD?
```

Then group changes into features. A "feature" is a coherent set of file changes that belong together by intent — not by directory. Heuristics (apply in order):

1. **Explicit hints** from the user's message (e.g., "the auth changes and the schema changes").
2. **File path clusters** — files under the same module path (`src/auth/*`, `src/payments/*`) usually go together.
3. **Diff inspection** — read the actual diff hunks. If function `foo` calls a new function `bar` and both are in the diff, they're one feature.
4. **Test pairing** — a source file and its `__tests__` / `.test.ts` / `.spec.ts` belong to the same feature.
5. **Config separation** — `package.json`, `tsconfig.json`, lockfiles, and CI configs get their own feature unless they're clearly part of one feature's setup.

Each feature gets:
- `name`: short kebab-case (e.g., `add-jwt-refresh`)
- `files`: list of changed paths
- `summary`: one sentence
- `order`: integer, dependency order (lower commits first)
- `imports_introduced`: new imports anywhere in the diff
- `flags_introduced`: any feature flags / env vars added
- `splatter_zone`: what else in the repo could be affected (callers of changed functions, anything importing the changed files)

Reference `references/feature-grouping.md` for the grouping rubric in detail.

---

## Step 3 — Confirm the plan with the user

Before spawning any subagents, show the user a compact plan:

```
Repo: web-app (3 features)
  1. add-jwt-refresh        → src/auth/*.ts (5 files)        [order: 1]
  2. fix-payment-rounding   → src/payments/total.ts (1 file) [order: 2]
  3. update-readme          → README.md (1 file)             [order: 3]

Repo: mobile-app (1 feature)
  1. wire-auth-refresh      → src/screens/Login.tsx (2 files) [order: 1]

Lump together? (default: no, one PR per feature)
```

Wait for confirmation. If the user says "lump features 1 and 2 in web-app", merge those into one feature for that repo and re-show. Only proceed once they approve.

---

## Step 4 — Spawn subagents in parallel

For each repo, spawn one subagent in the SAME turn (parallel). Subagents use the cheapest available model — they don't need deep reasoning, just careful execution of the per-repo recipe.

**Subagent prompt template:**

```
You are a per-repo execution agent for /merge-all-features. Use the cheapest available model. Do NOT add co-author trailers, signoffs, or "Generated with" lines to any commit or PR.

Repo: <absolute path>
Base branch: <main / master / detected>
Features (in commit order):
  <JSON list with name, files, summary, order, imports_introduced, flags_introduced, splatter_zone>

For EACH feature, in order, do:
  1. Stash backup FIRST: `bash <skill-path>/scripts/stash_backup.sh "<repo>" "<feature-name>"`
     If it prints STASH_SKIP or warns (no HEAD yet), log the warning and continue — do NOT block.
  2. Stage exactly that feature's files: `git add <files>` (no `git add .`)
  3. Run gates from the orchestrator's skill scripts:
       bash <skill-path>/scripts/run_gates.sh "<repo>" "<files>"
     If exit 0 → continue. If exit non-zero → stop this repo, write blocker reason to <workspace>/<repo-name>/BLOCKED.md, exit 0 from the agent (do NOT crash the orchestrator).
  4. Create branch: feat/<feature-name> off latest base branch (rebase if needed).
  5. Commit: `git commit -m "<type>(<scope>): <summary>"` — clean message, no trailers.
  6. Push: `git push -u origin feat/<feature-name>`
  7. Detect host: orchestrator passes HOST=github|gitlab based on remote URL.
  8. Build PR body from template at <skill-path>/references/pr-description-template.md, filled with feature data.
  9. Open PR:
       - github → `gh pr create --title "<title>" --body-file <body-path> --base <base>`
       - gitlab → `glab mr create --title "<title>" --description "$(cat <body-path>)" --target-branch <base>`
  10. Self-review using <skill-path>/references/pr-review-checklist.md against the diff. Produce two artifacts:
       - REVIEW.md (the review itself)
       - REQUIRED_ACTIONS.md (concrete TODOs)
  11. Post review:
       - github → `gh pr review <pr> --comment --body-file REVIEW.md`
                  then `gh pr comment <pr> --body-file REQUIRED_ACTIONS.md`
       - gitlab → `glab mr note <mr> --message "$(cat REVIEW.md)"` and another for required actions.
  12. Write SUCCESS.md with: PR URL, commit SHA, branch name, stash ref, gate results, review summary.

Output: write all artifacts to <workspace>/<repo-name>/feature-<N>/ as you go. Final line of your response: either "OK <repo-name>" or "BLOCKED <repo-name>: <reason>".
```

Spawn ALL subagents in one turn. Do not await one before launching the next.

---

## Step 5 — Per-repo execution (what the subagent does)

This is what each subagent runs. The orchestrator doesn't run these directly; they're documented here so the subagent prompt above has a reference.

**5a. Stash backup (always first)**

```bash
bash "$SKILL_PATH/scripts/stash_backup.sh" "$repo" "$FEATURE_NAME"
# Output: STASH_OK backup/<feature>/<timestamp>  → log the ref in SUCCESS.md
#         STASH_SKIP                              → log "nothing to stash", continue
#         Any error                               → warn user, continue (not a blocker)
```

To recover from a stash backup later:
```bash
git stash list | grep "backup/<feature-name>"
git stash apply stash@{N}   # N = the matching stash index
```

**5b. Branch setup**

```bash
cd "$repo"
git fetch origin
BASE=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo main)
git checkout -b "feat/$FEATURE_NAME" "origin/$BASE"
```

**5c. Stage exact files for this feature**

```bash
git add -- "${FEATURE_FILES[@]}"
git status --porcelain  # verify nothing extra got staged
```

**5d. Run gates (blocking)**

```bash
bash "$SKILL_PATH/scripts/run_gates.sh" "$repo" "${FEATURE_FILES[@]}"
```

The script runs ESLint (only on staged JS/TS files) and Jest (related tests via `--findRelatedTests`). It exits 0 on green, non-zero on failure. On failure, the subagent attempts `eslint --fix` once, re-runs, and if still red, writes BLOCKED.md with the failing output and exits.

**5e. Commit (clean message, no trailers)**

```bash
COMMIT_MSG="$(printf '%s(%s): %s' "$TYPE" "$SCOPE" "$SUMMARY")"
git commit -m "$COMMIT_MSG"
# Verify no trailers leaked in:
bash "$SKILL_PATH/scripts/strip_trailers.sh" HEAD
```

Conventional commit types: `feat`, `fix`, `chore`, `refactor`, `docs`, `test`, `perf`, `build`, `ci`. The scope is the feature's primary module.

**5f. Push**

```bash
git push -u origin "feat/$FEATURE_NAME"
```

If push is rejected (non-fast-forward, protected branch, etc.) → BLOCKED.

**5g. Build PR body**

Use `references/pr-description-template.md`. Fill placeholders with feature data:

- `{{PURPOSE}}` — what this feature is for, in plain language
- `{{FILE_CHANGES}}` — bulleted list of changed files with a one-line "why" each
- `{{WORK_DONE}}` — what kind of work (refactor / new module / bug fix / config / etc.)
- `{{USE_CASES}}` — when/how someone would use this
- `{{SPLATTER_ZONE}}` — blast radius: callers, importers, downstream effects
- `{{NEW_IMPORTS}}` — any new packages or internal modules pulled in
- `{{FEATURE_FLAGS}}` — flags/env vars introduced to gate this
- `{{REQUIRED_REVIEWERS}}` — codeowners / domain experts; pull from CODEOWNERS if present
- `{{TEST_RESULTS}}` — output of `jest --listTests` + pass/fail summary
- `{{LINT_RESULTS}}` — "clean" or the leftover warnings

**5g. Open PR**

Detect host once per repo:

```bash
REMOTE=$(git remote get-url origin)
case "$REMOTE" in
  *github.com*)  HOST=github ;;
  *gitlab.com*|*gitlab.*)  HOST=gitlab ;;
  *) HOST=unknown ;;
esac
```

If `HOST=unknown`, push the branch and surface a manual-PR-needed note (not a blocker for the rest of the run).

**5h. Self-review and post**

See `references/pr-review-checklist.md` for the checklist. Produce REVIEW.md (findings, severity tagged) and REQUIRED_ACTIONS.md (numbered list of concrete fixes). Post both to the PR.

---

## Step 6 — Aggregate and report

Once all subagents finish, build the final report:

```
✅ web-app
   - add-jwt-refresh:        merged-ready, PR #142, review posted (2 required actions)
   - fix-payment-rounding:   merged-ready, PR #143, review posted (0 required actions)
   - update-readme:          merged-ready, PR #144, review posted (0 required actions)

**🚫 BLOCKED: payments-svc**
   - reason: jest failure in src/totals.test.ts — calculateTax expected 110, got 99
   - 3 features queued; none committed
   - action needed: fix the failing test or update the expected value

✅ mobile-app
   - wire-auth-refresh:      merged-ready, PR #58, review posted (1 required action)
```

Blocked repos are reported in **bold** with the repo name and reason. The orchestrator does NOT abandon the run when one repo blocks — by Step 6, all other repos have already completed in parallel.

If multiple repos were touched and any are blocked, the orchestrator also checks: "are there other repos in this folder that weren't part of this run but have uncommitted changes?" If yes, mention them at the bottom — the user may want a follow-up run.

---

## Edge cases to handle gracefully

- **Detached HEAD or rebase in progress** → BLOCKED, surface the git state to the user.
- **No remote** → push step is skipped, PR step is skipped, report says "local commit only".
- **Protected base branch / push rejected** → BLOCKED with the exact rejection message.
- **No ESLint config in repo** → skip lint gate, note in PR body "no ESLint config detected".
- **No Jest in repo** → skip Jest gate, note in PR body "no Jest detected". (Future: pluggable test runners.)
- **Binary files or generated files staged** → flag in plan step; ask user to confirm before committing.
- **`.env`, secrets, credentials staged** → HARD STOP for that repo, blocker, do not push.
- **Existing PR on the same branch** → update the PR (push to the existing branch) rather than opening a duplicate.
- **No CODEOWNERS** → leave `{{REQUIRED_REVIEWERS}}` as "none auto-detected; add reviewers manually".

---

## Reference files

- `references/pr-description-template.md` — the exact PR body structure
- `references/pr-review-checklist.md` — what to check during self-review
- `references/feature-grouping.md` — the rubric for grouping uncommitted changes

## Scripts

- `scripts/stash_backup.sh` — create named stash backup before any commit; pops immediately to restore working tree; warns but never blocks
- `scripts/detect_repos.sh` — find git repos in a folder, or treat the folder as a repo
- `scripts/run_gates.sh` — run ESLint + Jest on staged files, exit non-zero on failure
- `scripts/strip_trailers.sh` — verify a commit has no co-author / signoff trailers
- `scripts/group_features.py` — heuristic grouping of uncommitted changes into features
- `scripts/make_pr_body.py` — assemble the PR description from template + feature data

## Recovering from a stash backup

If a commit or merge goes wrong, recover with:
```bash
git stash list | grep "backup/"          # find your backup
git stash apply stash@{N}               # restore it (N = index from list)
```

---

## A note on tone in user-facing output

- Blockers in **bold**. Don't bury them.
- One line per PR in the summary — the user wants to scan, not read.
- Don't claim a PR is "ready to merge" unless the gates passed and the self-review found no high-severity issues.
- If a self-review finds something serious (security, broken interface, missing tests for new branching logic), flag it as `severity: high` in REQUIRED_ACTIONS.md and mention it in the summary line for that PR.

