<div align="center">

# ⚡ ghls

### Git, but with Stacked PR Superpowers

**Stop shipping 2,000-line PRs that nobody wants to review.**

[![npm version](https://img.shields.io/npm/v/ghls-stack-pr?color=%23FF6B6B&style=flat-square)](https://www.npmjs.com/package/ghls-stack-pr)
[![license](https://img.shields.io/npm/l/ghls-stack-pr?color=%234ECDC4&style=flat-square)](./LICENSE)
[![node](https://img.shields.io/node/v/ghls-stack-pr?color=%23556270&style=flat-square)](https://nodejs.org/)

<br />

```
  ┌─────────────────────────────────┐
  │  ⚡ G H L S                     │
  │  Git + Stacked PRs, Supercharged│
  └─────────────────────────────────┘
```

<br />

[Quick Start](#-quick-start) · [Commands](#-commands) · [How It Works](#-how-it-works) · [Configuration](#%EF%B8%8F-configuration) · [GitHub Actions](#-github-actions)

</div>

---

## What is this?

`ghls` is a **drop-in replacement for `git`** that adds stacked PR superpowers on top. Every command you already know (`ghls status`, `ghls commit`, `ghls log`) passes straight through to git. But when you need to ship a stack of small, reviewable PRs — that's where the magic kicks in.

```
You write code normally. ghls does the rest.

  ┌─ #4 fix: edge case in notifications     +12 -3   ✅ CI passing
  ├─ #3 feat: notification preferences       +45 -12  🔄 In review
  ├─ #2 feat: user settings page             +120 -30 ✅ Approved
  └─ #1 feat: settings data model            +80 -5   ✅ Approved
  ▼
  main
```

When PR #1 gets merged, run `ghls land` and #2, #3, #4 automatically rebase onto the new `main`. No manual branch juggling. No merge conflicts from stale bases. Just vibes.

---

## Why Stacked PRs?

| Mega PR | Stacked PRs with ghls |
|---|---|
| 1,500 lines in one shot | 3 focused PRs of ~200 lines each |
| "LGTM" after 2 days of procrastination | Meaningful review in minutes |
| Merge conflicts from hell | Auto-rebase keeps everything fresh |
| Blocks the whole team | Land PRs independently as they're approved |
| Reviewer: 😩 | Reviewer: 😎 |

---

## 🚀 Quick Start

### Prerequisites

Before installing ghls, make sure you have:

| Dependency | Version | How to check | Install |
|---|---|---|---|
| **Node.js** | >= 18 | `node --version` | [nodejs.org](https://nodejs.org/) |
| **GitHub CLI** | any | `gh --version` | [cli.github.com](https://cli.github.com/) |
| **Git** | any | `git --version` | You probably have this already |

> **Important:** Make sure you're authenticated with GitHub CLI: run `gh auth status` to check. If not, run `gh auth login`.

### Install

```bash
npm install -g ghls-stack-pr
```

### Your first stack in 60 seconds

```bash
# 1. Navigate to any GitHub repo
cd your-repo

# 2. Initialize ghls (one-time setup, takes 5 seconds)
ghls init

# 3. Write code and make commits as usual
git add .
git commit -m "feat: add settings data model"
git commit -m "feat: add settings UI"

# 4. Ship it as stacked PRs
ghls submit

# 5. See your beautiful stack
ghls stack

# 6. After PR #1 is approved and merged on GitHub...
ghls land     # rebases the rest onto main automatically
```

That's it. You're stacking. 🎉

### Pro tip: alias git to ghls

Since ghls passes unknown commands to git, you can replace git entirely:

```bash
# Add to your ~/.zshrc or ~/.bashrc
alias git=ghls
```

Now `git commit`, `git push`, `git log` all work normally, but you also get `git submit`, `git stack`, `git land` for free.

---

## 📖 Commands

### Stack Commands

#### `ghls init`

One-time repo setup. Creates a `.ghls.yml` config file, detects your GitHub remote, and verifies `gh` is installed.

```bash
ghls init
```

---

#### `ghls submit` (alias: `up`)

Creates or updates stacked PRs from your commits. The heart of ghls.

```bash
ghls submit                          # interactive mode
ghls submit --one-click              # fully automated, no prompts
ghls submit --draft                  # create as draft PRs
ghls submit --reviewer alice,bob     # assign reviewers
ghls submit --dry-run                # see the plan without executing
ghls submit --force                  # bypass size limits
ghls submit --group semantic         # choose grouping strategy
```

| Flag | Short | Description |
|---|---|---|
| `--one-click` | | Non-interactive mode — auto-groups, skips prompts, uses defaults |
| `--draft` | `-d` | Create PRs as drafts |
| `--reviewer <names>` | `-r` | Comma-separated list of GitHub usernames to request review from |
| `--yes` | `-y` | Skip confirmation prompts |
| `--force` | | Bypass size validation limits |
| `--dry-run` | | Show what would happen without doing it |
| `--group <strategy>` | `-g` | Grouping strategy (see [Grouping Strategies](#grouping-strategies)) |

---

#### `ghls land` (alias: `merge`)

Merges the bottom PR(s) and rebases the rest of the stack.

```bash
ghls land                            # merge bottom PR (interactive)
ghls land --one-click                # merge all, no prompts
ghls land --all                      # merge entire stack
ghls land --count 2                  # merge bottom 2 PRs
ghls land --dry-run                  # preview the plan
ghls land --conflict theirs          # auto-resolve conflicts
```

| Flag | Short | Description |
|---|---|---|
| `--one-click` | | Equivalent to `--all --yes` — merges everything non-interactively |
| `--all` | | Land all PRs in the stack (bottom-up) |
| `--count <n>` | `-n` | Number of PRs to land from the bottom |
| `--yes` | `-y` | Skip confirmation |
| `--dry-run` | | Show the merge plan without executing |
| `--conflict <strategy>` | | Conflict resolution: `manual`, `ours`, `theirs`, `abort` |

---

#### `ghls sync`

Rebases your entire stack onto the latest base branch. Run this when `main` has been updated.

```bash
ghls sync                            # rebase stack onto latest main
ghls sync --push                     # also force-push branches after sync
ghls sync --conflict ours            # auto-resolve with local changes
ghls sync --continue                 # continue after manual conflict resolution
ghls sync --abort                    # abort an in-progress rebase
```

| Flag | Description |
|---|---|
| `--push` | Force-push branches after rebase |
| `--force` | Overwrite remote branches even if they're ahead |
| `--conflict <strategy>` | `manual` (default), `ours`, `theirs`, `abort` |
| `--continue` | Resume after you've resolved conflicts manually |
| `--abort` | Cancel the in-progress rebase and rollback |

---

#### `ghls stack` (alias: `sk`)

Visualize your current stack. Read-only, never modifies anything.

```bash
ghls stack                           # show stack overview
ghls stack --size                    # include detailed size breakdown
ghls stack --no-remote               # skip fetching PR info from GitHub
```

Example output:

```
  Stack: 3 PRs on main

  ┌─ #103  feat: notification preferences   +45 -12   🔄 Review pending
  ├─ #102  feat: user settings page          +120 -30  ✅ Approved
  └─ #101  feat: settings data model         +80 -5    ✅ Approved, CI passing
  ▼
  main (2 commits behind)
```

---

#### `ghls abandon`

Close all PRs in the stack, delete remote branches, and clean up metadata.

```bash
ghls abandon                         # interactive confirmation
ghls abandon --keep-local            # close PRs but keep local branches/metadata
```

---

### Utility Commands

#### `ghls config`

View or update configuration.

```bash
ghls config --show                   # display all merged config
ghls config mergeMethod              # get a single value
ghls config mergeMethod rebase       # set a value
ghls config maxPrSize 400 --global   # set in user-level config
```

| Flag | Description |
|---|---|
| `--show` | Display the fully merged configuration |
| `--global` | Read/write from user-level config (`~/.config/ghls/config.yml`) |

---

#### `ghls stats`

Your personal stacked PR analytics dashboard.

```bash
ghls stats
```

Shows: total commands run, stacks created, PRs created & landed, sync count, conflicts resolved, average PR size trends, weekly size charts, and estimated review time savings.

---

#### `ghls history`

View the operation log — every submit, land, sync, and abandon is recorded with timestamps and backup references.

```bash
ghls history                         # show recent operations
```

---

#### `ghls undo`

Revert the last ghls operation by restoring from its automatic backup.

```bash
ghls undo                            # restore previous state
```

---

#### `ghls cleanup`

Remove old backup branches (older than 7 days).

```bash
ghls cleanup
```

---

### Command Aliases Cheat Sheet

```
ghls submit  →  ghls up
ghls stack   →  ghls sk
ghls land    →  ghls merge
```

---

## 🔧 How It Works

### The Core Idea

Each commit on your working branch becomes its own PR. PRs are chained so that each one's base is the previous PR's branch:

```
Your commits:          The PRs ghls creates:

  commit C             PR #3 (base: PR #2's branch)
  commit B             PR #2 (base: PR #1's branch)
  commit A             PR #1 (base: main)
```

This means reviewers see **small, focused diffs** — not the entire feature at once.

### Metadata: How ghls Tracks the Stack

ghls stamps git trailers on your commits to track everything:

```
feat: add settings data model

ghls-id: a1b2c3d4-5678-9abc-def0-1234567890ab
ghls-stack: e5f6a7b8
ghls-order: 1
```

| Trailer | Purpose |
|---|---|
| `ghls-id` | Unique ID per commit (UUID) |
| `ghls-stack` | Shared identifier for the entire stack (8-char UUID prefix) |
| `ghls-order` | Position in the stack (1 = bottom, first to merge) |

These trailers are added automatically during `ghls submit` and are used for stack detection, ordering, and integrity validation.

### Branch Naming

ghls creates branches using a configurable template:

```
Default: ghls/{user}-{n}

Example for user "alice" with a 3-PR stack:
  ghls/alice-1  →  PR #1
  ghls/alice-2  →  PR #2
  ghls/alice-3  →  PR #3
```

| Placeholder | Value |
|---|---|
| `{user}` | Your GitHub username |
| `{stack}` | Stack ID (8-char UUID) |
| `{n}` | Position in stack (1-based) |

### Git Passthrough

Any command ghls doesn't recognize is forwarded directly to git:

```bash
ghls status      →  git status
ghls commit -m   →  git commit -m
ghls log --oneline  →  git log --oneline
ghls diff        →  git diff
ghls push        →  git push  (with a helpful hint if you're in a stack)
```

ghls intercepts `push` on stack branches to suggest using `ghls submit` instead, but still runs the push if you want it.

---

## ⚙️ Configuration

### Config File

`ghls init` creates `.ghls.yml` in your repo root:

```yaml
baseBranch: main
branchTemplate: "ghls/{user}-{n}"
mergeMethod: squash              # squash | merge | rebase
maxPrSize: 500                   # hard block above this
warnPrSize: 300                  # warning above this
maxStackEntries: 30              # max PRs in one stack
excludePatterns:
  - "*.lock"
  - "*.generated.*"
  - "package-lock.json"
  - "yarn.lock"
  - "pnpm-lock.yaml"
groupingStrategy: single         # single | marker | semantic | file | interactive | auto
autoSync: false                  # auto-sync before submit
analytics: true                  # local usage analytics
remote: origin                   # GitHub remote name
```

### Config Hierarchy

Configuration is merged in this order (later wins):

```
  ┌─────────────────────────────────────────────────────┐
  │  CLI flags                              (highest)   │
  │  .ghls.yml (repo)                                   │
  │  ~/.config/ghls/config.yml (user)                   │
  │  Built-in defaults                      (lowest)    │
  └─────────────────────────────────────────────────────┘
```

This means you can set personal defaults globally and override per-repo.

### All Config Options

| Option | Default | Description |
|---|---|---|
| `baseBranch` | `main` | The branch your stack targets |
| `branchTemplate` | `ghls/{user}-{n}` | Branch naming pattern |
| `mergeMethod` | `squash` | How PRs are merged: `squash`, `merge`, or `rebase` |
| `maxPrSize` | `500` | Hard block — PRs above this line count are rejected |
| `warnPrSize` | `300` | Warning threshold — you'll get a heads-up |
| `maxStackEntries` | `30` | Maximum number of PRs in a single stack |
| `excludePatterns` | `*.lock`, `*.generated.*`, etc. | Glob patterns excluded from size counting |
| `groupingStrategy` | `single` | How commits are grouped into PRs |
| `autoSync` | `false` | Automatically rebase onto base before submit |
| `analytics` | `true` | Collect local usage analytics |
| `remote` | `origin` | Name of the GitHub remote |

---

## 📏 Size Validation

ghls keeps your PRs small by default. Not all lines are created equal, so different file types are weighted differently:

```
  File Type        Weight    Example
  ─────────        ──────    ───────
  Code             1.0x      src/api/users.ts
  Tests            0.5x      tests/users.test.ts
  Migration        0.7x      migrations/001_add_users.sql
  Config           0.3x      tsconfig.json
  Docs             0.2x      docs/api.md
  Generated        0.0x      package-lock.json (excluded)
```

### How It Works

1. ghls calculates the **weighted line count** for each PR
2. Lines in `excludePatterns` files are ignored entirely
3. The weighted total determines the verdict:

```
  Weighted lines < 300  →  ✅ Good to go
  Weighted lines < 500  →  ⚠️  Warning (still submits)
  Weighted lines > 500  →  🚫 Blocked (use --force to override)
```

4. Review time estimate: ~200 weighted lines per hour

Use `ghls stack --size` for a full breakdown by category.

---

## 🔀 Grouping Strategies

When you have 2+ new commits, `ghls submit` shows an interactive picker:

```
  How should these new commits become PRs?
    1  Each commit = 1 PR (single)
    2  Group all into 1 PR
    3  Use suggested grouping above        ← only shown if ghls found a smarter split
    4  Group by [stack] markers
    5  Group by type (feat/fix/refactor)
    6  Group by file scope (directory)
    7  Interactive — manually assign commits to PRs
```

| # | Strategy | Behavior |
|---|---|---|
| 1 | `single` | Each commit becomes its own PR (default) |
| 2 | `all-in-one` | Every commit is squashed into a single PR — you pick the title |
| 3 | `auto` (suggested) | ghls analyzes marker/semantic/file strategies and suggests the best split. Only appears when a non-trivial grouping exists |
| 4 | `marker` | Groups commits that share a `[stack:name]` or `[group:name]` tag in their message |
| 5 | `semantic` | Groups consecutive commits with the same conventional commit type (`feat`, `fix`, `refactor`, etc.) |
| 6 | `file` | Groups commits by top-level directory scope — commits touching the same area go together |
| 7 | `interactive` | Shows all commits numbered, you type ranges (e.g. `1,2,3` or `1-3`) to assign them to PRs manually |

With `--one-click`, ghls skips this prompt and uses the `auto` strategy automatically.

Set a default in `.ghls.yml` or override per-submit:

```bash
ghls submit --group semantic         # skip the picker, use semantic
ghls submit --group file             # skip the picker, group by directory
ghls submit --one-click              # auto strategy, no prompts at all
```

---

## 🔥 Conflict Resolution

When a rebase produces conflicts, ghls gives you options:

| Strategy | What happens |
|---|---|
| `manual` | Drops you into your editor to resolve, then `ghls sync --continue` |
| `ours` | Keeps your local changes, discards incoming |
| `theirs` | Accepts incoming changes, discards yours |
| `abort` | Cancels the rebase entirely and rolls back |

ghls includes an interactive conflict wizard that analyzes the conflicts, shows their difficulty, and lets you pick a strategy. It handles multiple conflict rounds in a loop until everything's resolved.

```bash
# Automatic resolution
ghls sync --conflict theirs

# Manual resolution workflow
ghls sync                    # hits a conflict
# ... fix the files ...
git add .
ghls sync --continue         # resume
```

---

## 🤖 GitHub Actions

ghls ships 4 optional workflows. Copy them from the `actions/` directory into your repo's `.github/workflows/`:

```bash
cp node_modules/ghls-stack-pr/actions/*.yml .github/workflows/
```

### auto-rebase.yml

**Trigger:** PR merged

Automatically detects when a ghls-managed PR is merged, finds dependent PRs in the stack, updates their base branches, and attempts an auto-rebase. Comments on the PR with success/failure status.

### stack-validate.yml

**Trigger:** PR opened or updated

Validates stack integrity — checks that base branch chaining is correct and the stack metadata is consistent. Posts a validation comment on the PR.

### ci-optimize.yml

**Trigger:** After auto-rebase completes

Cancels redundant CI runs when branches get force-pushed. Groups workflow runs by branch, keeps the latest, cancels everything older. Saves your CI minutes.

### review-preserve.yml

**Trigger:** PR force-pushed (sync event)

Detects "cosmetic" rebases (same tree hash, different commit hash) and re-requests reviews from previous approvers. This way, approvals aren't lost just because ghls rebased the stack.

---

## 🛡️ Safety

ghls is paranoid about your code. Here's how it keeps you safe:

| Feature | Description |
|---|---|
| **Automatic backups** | Every destructive op (`submit`, `land`, `sync`, `abandon`) creates a backup branch first |
| **Undo** | `ghls undo` restores the state from the last backup |
| **Abort** | `ghls sync --abort` cancels an in-progress rebase |
| **Dry run** | `--dry-run` on `submit` and `land` shows the plan without doing anything |
| **Scoped force-push** | Only force-pushes ghls-managed stack branches, never your main branch |
| **Non-destructive** | Works alongside regular `git push` — only touches its own PRs and branches |
| **Operation log** | Every operation is logged in `~/.config/ghls/history/operations.json` |
| **Backup cleanup** | `ghls cleanup` removes backups older than 7 days (max 50 entries) |

Backup branches are stored as `ghls-backup/{branch}/{operation}-{timestamp}`.

---

## 📊 Analytics

ghls collects **local-only** usage analytics (nothing leaves your machine). Run `ghls stats` to see:

- Total commands run, stacks created, PRs shipped & landed
- Sync count and conflicts resolved
- Average & median PR sizes over time
- Weekly size trend charts
- Estimated review time savings

Disable with `ghls config analytics false` or set `analytics: false` in your config.

---

## 🧑‍💻 Full Workflow Example

Here's a real-world scenario: you're building a notifications feature.

```bash
# Start from main
git checkout main && git pull

# Create a feature branch
git checkout -b notifications

# Write the data layer
# ... code code code ...
git add . && git commit -m "feat: add notification data model"

# Write the API
# ... code code code ...
git add . && git commit -m "feat: add notification API endpoints"

# Write the UI
# ... code code code ...
git add . && git commit -m "feat: add notification bell component"

# Ship it as 3 stacked PRs
ghls submit
#  → Creates PR #1: notification data model (base: main)
#  → Creates PR #2: notification API (base: PR #1)
#  → Creates PR #3: notification bell (base: PR #2)

# Check your stack
ghls stack
#  ┌─ #103  feat: add notification bell component    +95 -10
#  ├─ #102  feat: add notification API endpoints      +140 -25
#  └─ #101  feat: add notification data model         +60 -3
#  ▼
#  main

# PR #1 gets approved and merged on GitHub...

# Land it and rebase the rest
ghls land
#  → Merges #101
#  → Rebases #102 onto main, updates base
#  → Rebases #103 onto #102, updates base
#  → Force-pushes updated branches

# PR #2 gets approved...
ghls land
# ...and so on until the stack is empty 🎉
```

---

## 🛠️ Development

```bash
git clone <repo>
cd ghls-stack
npm install
npm run build
npm link            # makes `ghls` available globally
npm run dev         # watch mode (tsc --watch)
npm test            # vitest
npm run test:watch  # vitest in watch mode
npm run lint        # eslint
```

### Project Structure

```
ghls-stack/
├── bin/ghls.ts              # CLI entrypoint & git passthrough
├── src/
│   ├── commands/            # Command implementations
│   │   ├── init.ts          # ghls init
│   │   ├── submit.ts        # ghls submit
│   │   ├── land.ts          # ghls land
│   │   ├── sync.ts          # ghls sync
│   │   ├── status.ts        # ghls stack
│   │   ├── abandon.ts       # ghls abandon
│   │   ├── config.ts        # ghls config
│   │   ├── history.ts       # ghls history, undo, cleanup
│   │   └── stats.ts         # ghls stats
│   ├── core/                # Core logic
│   │   ├── stack.ts         # Stack detection & ordering
│   │   ├── git.ts           # Git operations
│   │   ├── github.ts        # GitHub API (via gh CLI)
│   │   ├── conflicts.ts     # Conflict resolution wizard
│   │   ├── backup.ts        # Backup/restore system
│   │   └── metadata.ts      # Commit trailer management
│   ├── validation/          # PR validation
│   │   ├── size.ts          # Size analysis & weighting
│   │   ├── rules.ts         # Pre-submit/pre-land checks
│   │   └── grouping.ts      # Commit grouping strategies
│   ├── config/              # Configuration
│   │   ├── schema.ts        # Zod config schema
│   │   └── loader.ts        # Config hierarchy merger
│   ├── ui/                  # Terminal UI
│   │   ├── tree.ts          # ASCII stack visualization
│   │   ├── prompts.ts       # Interactive prompts
│   │   ├── stages.ts        # Progress stages
│   │   └── spinner.ts       # Loading spinners
│   ├── analytics/           # Analytics
│   │   ├── collector.ts     # Metrics tracking
│   │   └── storage.ts       # PR history & trends
│   └── utils/               # Utilities
│       ├── errors.ts        # Custom error types
│       ├── exec.ts          # Process execution
│       └── logger.ts        # Logging
├── actions/                 # GitHub Actions workflows
├── tests/                   # Unit & integration tests
└── .ghls.yml                # Repo config (created by init)
```

---

## FAQ

**Q: Does ghls modify my existing branches?**
No. ghls only creates and manages its own branches (prefixed with `ghls/`). Your working branch is untouched.

**Q: Can I use ghls with an existing PR?**
ghls manages its own stack. If you have a regular PR, keep it. Start fresh with `ghls submit` for new work.

**Q: What if I make more commits after submitting?**
Run `ghls submit` again. It updates existing PRs or creates new ones as needed.

**Q: Does it work with GitLab/Bitbucket?**
Not yet — ghls depends on the GitHub CLI (`gh`). GitHub only for now.

**Q: What merge methods are supported?**
`squash` (default), `merge`, and `rebase`. Set via `mergeMethod` in your config.

**Q: Can I have multiple stacks at once?**
Each branch gets its own stack. Switch branches to work on different stacks.

---

## License

MIT — go build cool things.
