npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ghls-stack-pr

v0.1.5

Published

Stacked PR CLI — make small, reviewable PRs the default workflow

Readme

⚡ ghls

Git, but with Stacked PR Superpowers

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

npm version license node

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

Quick Start · Commands · How It Works · Configuration · GitHub Actions


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 | | GitHub CLI | any | gh --version | 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

npm install -g ghls-stack-pr

Your first stack in 60 seconds

# 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:

# 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.

ghls init

ghls submit (alias: up)

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

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) |


ghls land (alias: merge)

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

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.

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.

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.

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

Utility Commands

ghls config

View or update configuration.

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.

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.

ghls history                         # show recent operations

ghls undo

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

ghls undo                            # restore previous state

ghls cleanup

Remove old backup branches (older than 7 days).

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:

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:

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)
  1. 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:

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.

# 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/:

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.

# 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

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.