mirror of
https://github.com/github/gh-stack.git
synced 2026-09-14 20:26:28 +08:00
4759126a51
* Add AGENTS.md and copilot-instructions.md for AI agent onboarding Add two complementary instruction files so AI coding agents can work effectively in this repository without re-discovering conventions: - AGENTS.md (7K chars): agent-agnostic open standard with full project structure, build/test commands, coding patterns, testing conventions, error handling, key interfaces, and non-obvious gotchas. - .github/copilot-instructions.md (2K chars): concise Copilot-specific instructions under the 4K code review limit, covering the essentials and referencing AGENTS.md for full details. Closes #132 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: update cmd to generate an executable binary Co-authored-by: Sameen Karim <skarim@github.com> * fix: update cmd to get the build output as an exec binary Co-authored-by: Sameen Karim <skarim@github.com> * Fix build command and errors.As usage per review feedback - go build ./... compiles but does not produce a binary. Changed to go build -o gh-stack . which actually outputs the executable. - errors.As(err, &ExitError{}) panics at runtime because the value type ExitError does not satisfy the error interface (only *ExitError does). Updated to the correct two-line pattern matching cmd/root.go. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Sameen Karim <skarim@github.com>
6.9 KiB
6.9 KiB
gh-stack: Agent Instructions
A GitHub CLI (gh) extension for managing stacked branches and pull requests. Written in Go, it automates creating branches, keeping them rebased, setting PR base branches, and navigating between stack layers.
Build, test, and validate
go mod download # install dependencies
go build -o gh-stack . # build (produces ./gh-stack binary)
go vet ./... # static analysis. Run before tests.
go test -race -count=1 ./... # all tests with race detection
Always run go vet before go test. CI runs both on every push/PR across ubuntu, windows, and macOS (test.yml).
There is no Makefile, linter config, or code generation step. The standard Go toolchain is all that's needed.
Install locally as a gh extension
go build -o gh-stack .
gh extension remove stack 2>/dev/null
gh extension install .
Project structure
main.go # entrypoint. Calls cmd.Execute().
cmd/ # Cobra commands (one file per command + tests)
root.go # registers all subcommands in four groups
utils.go # shared helpers, ExitError types, exit codes
internal/
git/ # git.Ops interface + defaultOps (exec-based)
gitops.go # Ops interface (52 methods)
mock_ops.go # MockOps. Each method has a corresponding *Fn field.
github/ # github.ClientOps interface + real Client
client_interface.go # ClientOps interface (11 methods)
mock_client.go # MockClient. Uses function-pointer fields for testing.
stack/ # stack file (.git/gh-stack) management, JSON schema, locking
schema.json # JSON Schema for the stack file format
config/ # Config struct (I/O, colors, test overrides)
testing.go # NewTestConfig(). Returns *Config + stdout/stderr pipes.
branch/ # branch naming (Slugify, DateSlug, NextNumberedName)
modify/ # interactive stack modification state machine
pr/ # PR template discovery
tui/ # bubbletea/bubbles/lipgloss terminal UI
stackview/ # interactive stack visualization
modifyview/ # interactive modify session UI
shared/ # shared TUI types
docs/ # Astro + Starlight documentation site
skills/ # AI agent skill definition (SKILL.md)
Command groups (registered in cmd/root.go)
| Group | Commands |
|---|---|
| Stack management | init, add, view, checkout, modify, unstack |
| Remote operations | submit, sync, rebase, push, link |
| Navigation | switch, up, down, top, bottom, trunk |
| Utilities | alias, feedback |
Coding patterns
Command structure
Each command lives in its own file (cmd/<name>.go) and follows this pattern:
- Define an
<name>Optionsstruct for flags/args. - Export a
<Name>Cmd(cfg *config.Config) *cobra.Commandconstructor. - Implement logic in a private
run<Name>(cfg, opts, args)function. - The
RunEfield on the command callsrun<Name>.
Error handling
Use typed exit codes defined in cmd/utils.go:
| Code | Sentinel | Meaning |
|---|---|---|
| 1 | ErrSilent |
Error already printed |
| 2 | ErrNotInStack |
Branch/stack not found |
| 3 | ErrConflict |
Rebase conflict |
| 4 | ErrAPIFailure |
GitHub API error |
| 5 | ErrInvalidArgs |
Invalid arguments or flags |
| 6 | ErrDisambiguate |
Multiple stacks/remotes, can't auto-select |
| 7 | ErrRebaseActive |
Rebase already in progress |
| 8 | ErrLockFailed |
Stack file lock contention |
| 9 | ErrStacksUnavailable |
Stacked PRs not enabled for repository |
| 10 | ErrModifyRecovery |
Modify session interrupted |
Return these from RunE. Never call os.Exit() directly from commands. Check with:
var exitErr *ExitError
if errors.As(err, &exitErr) { ... }
Testing patterns
- Framework:
stretchr/testify(assert,require) for assertions. - Table-driven tests are the norm. See
cmd/utils_test.gofor examples. - Config: Use
config.NewTestConfig()which returns(*Config, stdoutReader, stderrReader)with captured I/O and no-op color functions. - Git mocking: Call
git.SetOps(&git.MockOps{...}). It returns a restore function. Alwaysdefer restore()to prevent test pollution. - GitHub mocking: Set
cfg.GitHubClientOverride = &github.MockClient{...}. - Prompt mocking: Set
cfg.SelectFn,cfg.ConfirmFn, orcfg.InputFnon the config to simulate interactive input. - Stack file setup: Use
stack.Load(dir)after writing a stack file to get correct checksums forSave.
Key interfaces
git.Ops(internal/git/gitops.go): 52 methods wrapping git CLI calls. The production implementation usescli/go-gh'sclient.Command()viarun()andrunSilent()helpers. Package-level functions (e.g.,git.CurrentBranch()) delegate to a swappable package-levelopsvariable.github.ClientOps(internal/github/client_interface.go): 11 methods for GitHub API (PRs, stacks). Injected viacfg.GitHubClientOverridein tests.config.Config(internal/config/config.go): Central configuration passed to all commands. Holds I/O streams, color functions, and test hook fields (SelectFn,ConfirmFn,InputFn,TokenForHostFn,RepoOverride).
Stack file
- Location:
.git/gh-stack(JSON format, schema version 1). - Schema:
internal/stack/schema.json. - Locking: Exclusive file lock at
.git/gh-stack.lockwith 5-second timeout. Errors surface asLockError. - Staleness: Concurrent modifications detected via
StaleError.
CI workflows (.github/workflows/)
| Workflow | Trigger | What it does |
|---|---|---|
test.yml |
push to main, PRs | go vet + go test -race -count=1 ./... on 3 OS matrix |
release.yml |
v* tags |
Cross-platform precompiled binaries via cli/gh-extension-precompile |
docs.yml |
push to main (docs/**) | Builds Astro/Starlight docs, deploys to GitHub Pages |
Non-obvious things
- The
Queuedfield onBranchRefis transient (populated from GitHub API, never persisted to the stack JSON file). git.SetOps()replaces the package-level ops variable. Forgettingdefer restore()in a test will break every subsequent test in the package.- Interrupt detection: Ctrl+C is caught as
terminal.InterruptErr, wrapped into anerrInterruptsentinel, and printed with a friendly message before a silent exit. - Rerere: on first rebase conflict, the user is prompted to enable
git rerere. If declined, a flag file prevents future prompts.tryAutoResolveRebase()loops up to 1000 times auto-continuing when rerere resolves conflicts. - The
.gitignoreignores/gh-stackand/gh-stack.exe(the built binary).