Skip to content

Commit 4759126

Browse files
francisfuzzCopilotskarim
authored
Add AGENTS.md and copilot-instructions.md for AI agent onboarding (#133)
* 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>
1 parent 84d160b commit 4759126

2 files changed

Lines changed: 172 additions & 0 deletions

File tree

.github/copilot-instructions.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# gh-stack: Copilot Instructions
2+
3+
A Go CLI extension (`gh stack`) for managing stacked branches and pull requests. Uses Cobra for commands, bubbletea/lipgloss for TUI, and `stretchr/testify` for tests.
4+
5+
## Build and validate
6+
7+
```sh
8+
go mod download # install deps
9+
go build -o gh-stack . # build
10+
go vet ./... # static analysis. Always run before tests.
11+
go test -race -count=1 ./... # tests with race detection
12+
```
13+
14+
No Makefile, no code generation, no external linter config. Standard Go toolchain only.
15+
16+
## Project layout
17+
18+
- `cmd/`: One Cobra command per file. Each exports `<Name>Cmd(cfg *config.Config)` with logic in `run<Name>()`.
19+
- `internal/git/`: `Ops` interface (52 methods) wrapping git CLI. `MockOps` for tests. Package-level functions delegate to swappable `ops` variable.
20+
- `internal/github/`: `ClientOps` interface (11 methods) for GitHub API. `MockClient` for tests.
21+
- `internal/config/`: `Config` struct passed to all commands. Holds I/O, colors, and test hooks (`SelectFn`, `ConfirmFn`, `InputFn`, `GitHubClientOverride`).
22+
- `internal/stack/`: Stack file (`.git/gh-stack`, JSON) management with file locking.
23+
- `internal/tui/`: bubbletea views (`stackview`, `modifyview`).
24+
25+
## Coding conventions
26+
27+
- Return typed `ExitError` sentinels (codes 1-10 in `cmd/utils.go`) from `RunE`. Never call `os.Exit()` directly.
28+
- Check errors with `var exitErr *ExitError; errors.As(err, &exitErr)`.
29+
- Table-driven tests with `t.Run()` subtests.
30+
- Use `config.NewTestConfig()` for test configs with captured I/O.
31+
- Mock git: `restore := git.SetOps(&git.MockOps{...}); defer restore()`. Always defer restore.
32+
- Mock GitHub: `cfg.GitHubClientOverride = &github.MockClient{...}`.
33+
- Mock prompts: set `cfg.SelectFn`, `cfg.ConfirmFn`, or `cfg.InputFn`.
34+
- Load stack files with `stack.Load(dir)` after writing to get correct checksums.
35+
36+
For full architecture details, see [AGENTS.md](../AGENTS.md) in the repository root.

AGENTS.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# gh-stack: Agent Instructions
2+
3+
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.
4+
5+
## Build, test, and validate
6+
7+
```sh
8+
go mod download # install dependencies
9+
go build -o gh-stack . # build (produces ./gh-stack binary)
10+
go vet ./... # static analysis. Run before tests.
11+
go test -race -count=1 ./... # all tests with race detection
12+
```
13+
14+
Always run `go vet` before `go test`. CI runs both on every push/PR across ubuntu, windows, and macOS (`test.yml`).
15+
16+
There is no Makefile, linter config, or code generation step. The standard Go toolchain is all that's needed.
17+
18+
### Install locally as a `gh` extension
19+
20+
```sh
21+
go build -o gh-stack .
22+
gh extension remove stack 2>/dev/null
23+
gh extension install .
24+
```
25+
26+
## Project structure
27+
28+
```
29+
main.go # entrypoint. Calls cmd.Execute().
30+
cmd/ # Cobra commands (one file per command + tests)
31+
root.go # registers all subcommands in four groups
32+
utils.go # shared helpers, ExitError types, exit codes
33+
internal/
34+
git/ # git.Ops interface + defaultOps (exec-based)
35+
gitops.go # Ops interface (52 methods)
36+
mock_ops.go # MockOps. Each method has a corresponding *Fn field.
37+
github/ # github.ClientOps interface + real Client
38+
client_interface.go # ClientOps interface (11 methods)
39+
mock_client.go # MockClient. Uses function-pointer fields for testing.
40+
stack/ # stack file (.git/gh-stack) management, JSON schema, locking
41+
schema.json # JSON Schema for the stack file format
42+
config/ # Config struct (I/O, colors, test overrides)
43+
testing.go # NewTestConfig(). Returns *Config + stdout/stderr pipes.
44+
branch/ # branch naming (Slugify, DateSlug, NextNumberedName)
45+
modify/ # interactive stack modification state machine
46+
pr/ # PR template discovery
47+
tui/ # bubbletea/bubbles/lipgloss terminal UI
48+
stackview/ # interactive stack visualization
49+
modifyview/ # interactive modify session UI
50+
shared/ # shared TUI types
51+
docs/ # Astro + Starlight documentation site
52+
skills/ # AI agent skill definition (SKILL.md)
53+
```
54+
55+
### Command groups (registered in `cmd/root.go`)
56+
57+
| Group | Commands |
58+
|-------|----------|
59+
| Stack management | `init`, `add`, `view`, `checkout`, `modify`, `unstack` |
60+
| Remote operations | `submit`, `sync`, `rebase`, `push`, `link` |
61+
| Navigation | `switch`, `up`, `down`, `top`, `bottom`, `trunk` |
62+
| Utilities | `alias`, `feedback` |
63+
64+
## Coding patterns
65+
66+
### Command structure
67+
68+
Each command lives in its own file (`cmd/<name>.go`) and follows this pattern:
69+
70+
1. Define an `<name>Options` struct for flags/args.
71+
2. Export a `<Name>Cmd(cfg *config.Config) *cobra.Command` constructor.
72+
3. Implement logic in a private `run<Name>(cfg, opts, args)` function.
73+
4. The `RunE` field on the command calls `run<Name>`.
74+
75+
### Error handling
76+
77+
Use typed exit codes defined in `cmd/utils.go`:
78+
79+
| Code | Sentinel | Meaning |
80+
|------|----------|---------|
81+
| 1 | `ErrSilent` | Error already printed |
82+
| 2 | `ErrNotInStack` | Branch/stack not found |
83+
| 3 | `ErrConflict` | Rebase conflict |
84+
| 4 | `ErrAPIFailure` | GitHub API error |
85+
| 5 | `ErrInvalidArgs` | Invalid arguments or flags |
86+
| 6 | `ErrDisambiguate` | Multiple stacks/remotes, can't auto-select |
87+
| 7 | `ErrRebaseActive` | Rebase already in progress |
88+
| 8 | `ErrLockFailed` | Stack file lock contention |
89+
| 9 | `ErrStacksUnavailable` | Stacked PRs not enabled for repository |
90+
| 10 | `ErrModifyRecovery` | Modify session interrupted |
91+
92+
Return these from `RunE`. Never call `os.Exit()` directly from commands. Check with:
93+
94+
```go
95+
var exitErr *ExitError
96+
if errors.As(err, &exitErr) { ... }
97+
```
98+
99+
### Testing patterns
100+
101+
- **Framework:** `stretchr/testify` (`assert`, `require`) for assertions.
102+
- **Table-driven tests** are the norm. See `cmd/utils_test.go` for examples.
103+
- **Config:** Use `config.NewTestConfig()` which returns `(*Config, stdoutReader, stderrReader)` with captured I/O and no-op color functions.
104+
- **Git mocking:** Call `git.SetOps(&git.MockOps{...})`. It returns a restore function. Always `defer restore()` to prevent test pollution.
105+
- **GitHub mocking:** Set `cfg.GitHubClientOverride = &github.MockClient{...}`.
106+
- **Prompt mocking:** Set `cfg.SelectFn`, `cfg.ConfirmFn`, or `cfg.InputFn` on the config to simulate interactive input.
107+
- **Stack file setup:** Use `stack.Load(dir)` after writing a stack file to get correct checksums for `Save`.
108+
109+
### Key interfaces
110+
111+
- **`git.Ops`** (`internal/git/gitops.go`): 52 methods wrapping git CLI calls. The production implementation uses `cli/go-gh`'s `client.Command()` via `run()` and `runSilent()` helpers. Package-level functions (e.g., `git.CurrentBranch()`) delegate to a swappable package-level `ops` variable.
112+
- **`github.ClientOps`** (`internal/github/client_interface.go`): 11 methods for GitHub API (PRs, stacks). Injected via `cfg.GitHubClientOverride` in tests.
113+
- **`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`).
114+
115+
### Stack file
116+
117+
- **Location:** `.git/gh-stack` (JSON format, schema version 1).
118+
- **Schema:** `internal/stack/schema.json`.
119+
- **Locking:** Exclusive file lock at `.git/gh-stack.lock` with 5-second timeout. Errors surface as `LockError`.
120+
- **Staleness:** Concurrent modifications detected via `StaleError`.
121+
122+
## CI workflows (`.github/workflows/`)
123+
124+
| Workflow | Trigger | What it does |
125+
|----------|---------|-------------|
126+
| `test.yml` | push to main, PRs | `go vet` + `go test -race -count=1 ./...` on 3 OS matrix |
127+
| `release.yml` | `v*` tags | Cross-platform precompiled binaries via `cli/gh-extension-precompile` |
128+
| `docs.yml` | push to main (docs/**) | Builds Astro/Starlight docs, deploys to GitHub Pages |
129+
130+
## Non-obvious things
131+
132+
- The `Queued` field on `BranchRef` is transient (populated from GitHub API, never persisted to the stack JSON file).
133+
- `git.SetOps()` replaces the **package-level** ops variable. Forgetting `defer restore()` in a test will break every subsequent test in the package.
134+
- Interrupt detection: Ctrl+C is caught as `terminal.InterruptErr`, wrapped into an `errInterrupt` sentinel, and printed with a friendly message before a silent exit.
135+
- 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.
136+
- The `.gitignore` ignores `/gh-stack` and `/gh-stack.exe` (the built binary).

0 commit comments

Comments
 (0)