gor-cli 0.2.0

A Rust CLI for GitHub — a 'gh' clone
Documentation
# AGENTS.md — AI Agent Development Guide

Instructions for LLM coding agents working on the `gor` repository.

---

## Project Identity

**gor** — GitHub on Rust. A fast, self-contained GitHub CLI, equivalent to `gh` but written in Rust with no external dependencies on `git` or OpenSSL.

- **Binary:** `gor`
- **Crate:** `gor` (published on crates.io)
- **License:** MIT OR Apache-2.0
- **MSRV:** 1.86
- **Edition:** 2024

---

## Architecture (non-negotiable)

```
src/
  lib.rs          # All business logic — fully unit-testable, no I/O in pure functions
  main.rs         # Thin entry point: parse args, init tracing, dispatch to lib
  cli.rs          # clap derive definitions (Args, Subcommand enums)
  client.rs       # HTTP client, auth, token resolution, URL building
  host.rs         # Multi-host support (github.com + GHES), URL derivation
  repository.rs   # Repo spec parsing (OWNER/REPO), remote URL detection via gix
  output.rs       # JSON, table, and terminal formatting
  error.rs        # Error enum (thiserror), exit codes
  config.rs       # Host config persistence (~/.config/gor/hosts.yml, mode 0600)
  keyring_store.rs # OS keyring integration
  auth/
    mod.rs         # Login/logout/status/token orchestration
    device.rs      # OAuth device flow
    token.rs       # Token verification + stdin reading
  cmd/
    mod.rs
    api.rs         # Arbitrary REST API calls
    repo.rs        # repo view/list/clone/create/fork/delete
    pr.rs          # pr list/view/checkout/create/comment/close/reopen/merge/diff/review
    issue.rs       # issue list/view/create/comment/close/reopen/edit
    release.rs     # release list/view/create/edit/delete/upload/download
    label.rs       # label list/create/edit/delete
    search.rs      # search repos/issues/prs/code/commits
    gist.rs        # gist list/view/create/edit/delete
    workflow.rs    # workflow list/view/run/enable/disable
    run.rs         # run list/view/watch/cancel/rerun/download
    browse.rs      # open in browser
    util.rs        # Shared helpers (pagination, body builders, field selection)
```

**Rules:**
- `src/main.rs` must remain thin — parse args, init logging, call into `lib.rs`. Never put business logic here.
- Every public item in `lib.rs` must have a doc comment with `# Examples`.
- New command groups go in `src/cmd/`. Each file gets a `mod` entry in `src/cmd/mod.rs`.
- Shared logic between commands goes in `src/cmd/util.rs`. Never duplicate pagination, body building, or field selection code.

---

## Development Workflow

All common tasks have `just` recipes. Run `just` with no arguments to list them.

```bash
just build          # Debug build
just release        # Release build (optimized, stripped, LTO)
just test           # Full test suite (nextest + doc tests)
just test-unit      # Unit tests only (src/)
just test-integration # Integration tests only (tests/)
just lint           # Format check + clippy with -D warnings
just lint-fix       # Auto-fix formatting + clippy
just doc            # Build docs and open in browser
just coverage       # Generate lcov coverage report
just audit          # cargo-deny + cargo-audit
just typos          # Spellcheck
just shear          # Unused dependency check
just ci             # Full CI gate (lint + test + audit + typos + shear)
just clean          # Remove target/
just install        # Install gor from source
```

**Always run `just ci` before pushing.** This mirrors what CI will run.

---

## Linting & Formatting

### Policy is centralized in `Cargo.toml` under `[workspace.lints]`

**Deny (hard gates — CI fails):**
- `unsafe_code` — no unsafe in application code
- `unwrap_used` — use `?` or proper error handling
- `dbg_macro` — no dbg!() in committed code
- `todo` — no todo!() in committed code

**Warn (visible, fix over time):**
- `expect_used` — prefer proper error handling over expect()
- `print_stdout`, `print_stderr` — use tracing for logging
- `pedantic` and `nursery` groups (with negative priority so they can be overridden)

**Allow (explicitly suppressed noise):**
- `module_name_repetitions`, `doc_markdown`, `must_use_candidate`, `too_many_lines`

### Formatting
- `style_edition = "2024"`, `max_width = 100`
- Run `just lint-fix` before committing

### Spellcheck
- `typos` runs in CI. Add false positives to `_typos.toml` under `[default.extend-words]`.

---

## Error Handling

- **Library code** (`client.rs`, `repository.rs`, `host.rs`, `config.rs`): Use `thiserror` — define explicit error variants in `error.rs`. Never expose third-party error types in public APIs.
- **Command code** (`cmd/*.rs`): Use `anyhow::Result` with `.context(...)` for human-readable messages.
- **User-facing diagnostics**: Use `miette` for structured errors with source snippets and help text.
- **Exit codes**: 0 (success), 1 (general error), 4 (auth/401/403).

---

## Testing

### Unit tests
- Live in `#[cfg(test)] mod tests` blocks inside source files.
- Test pure functions in `lib.rs`, `repository.rs`, `host.rs`, `output.rs`.
- Use `proptest` for parsers (repo spec, remote URL, field coercion).

### Integration tests
- Live in `tests/` directory.
- Use `assert_cmd` + `assert_fs` for black-box CLI testing.
- Use `insta` for snapshot testing of `--help` output, error messages, and table formatting.
- Use `wiremock` to mock GitHub API responses.

### Doc tests
- Every public function in `lib.rs` gets a runnable `# Examples` section.
- Use `no_run` for examples that need network access.
- Use `should_panic` for error-path examples.

---

## Commit Conventions

Use **Conventional Commits**. This is required — `release-plz` uses commit messages to generate changelogs.

```
feat: add repo create command
fix: handle 429 rate limit responses
docs: document auth flow in README
test: add integration tests for pr checkout
refactor: extract pagination logic to util.rs
chore: update dependencies
ci: add windows to test matrix
```

**Scopes** (optional but encouraged): `auth`, `api`, `repo`, `pr`, `issue`, `release`, `label`, `search`, `gist`, `workflow`, `run`, `client`, `output`, `config`, `ci`, `docs`.

---

## CI Pipeline

CI runs on every push and PR. Eight parallel jobs:

| Job | What it checks |
|-----|---------------|
| `lint` | fmt, clippy, cargo-deny, typos, cargo-shear |
| `doc` | doc generation warnings, doc tests |
| `test` | nextest + doc tests on ubuntu, macos, windows |
| `coverage` | llvm-cov + codecov upload |
| `msrv` | cargo check with Rust 1.86 |
| `security` | cargo-audit via rustsec |
| `trivy` | vuln, secret, and misconfig scanning via Trivy |
| `kingfisher` | secret scanning with live validation via Kingfisher |

**If CI fails, fix it before pushing more commits.** Do not disable lint gates with `#[allow(...)]` without a documented reason.

---

## Key Design Decisions

1. **REST-first, GraphQL later.** REST covers all P0/P1 needs. GraphQL is deferred.
2. **gix over git2.** Pure Rust, no OpenSSL, no `git` binary dependency.
3. **Multi-host from day one.** github.com and GHES use the same code paths.
4. **Sync by default.** `reqwest::blocking`. Async only if I/O concurrency demands it.
5. **Flag-driven, not interactive.** No TUI prompts in v1. All behavior controlled via CLI flags.
6. **`--json` with field selection.** Not all-or-nothing. Support `--json field1,field2` + `--jq`.
7. **Config in `~/.config/gor/`.** YAML hosts file (mode 0600), OS keyring for tokens.

## Architecture Decision Records (ADRs)

Major architectural decisions are recorded as ADRs in `docs/adr/` using the [`adrs`](https://joshrotenberg.com/adrs/) CLI tool. The repo is configured for **NextGen mode** (YAML frontmatter) with **MADR 4.0.0 format**.

### When to create an ADR

Create an ADR anytime a major architectural decision is made — library choices, protocol decisions, concurrency models, cross-cutting patterns, or deprecations. Do NOT create ADRs for routine implementation details or decisions already covered by AGENTS.md conventions.

### Quick reference

```bash
adrs new "Title"                  # Create a new ADR (opens editor)
adrs new --no-edit "Title"        # Create without editor (for scripting)
adrs new --supersedes N "Title"   # Supersede an existing ADR
adrs list                          # List all ADRs
adrs list -l                       # Detailed list with status and date
adrs list --status accepted        # Filter by status
adrs search "query"               # Search all content
adrs edit N                        # Edit ADR by number
adrs status N accepted             # Change status
adrs status N superseded --by M    # Mark as superseded by ADR M
adrs link N "Relates to" M        # Link two ADRs
adrs doctor                        # Check ADR health
adrs generate toc > docs/adr/README.md  # Generate table of contents
```

### Status lifecycle

```
proposed → accepted → deprecated → superseded
            rejected
```

### Agent workflow

1. When a major architectural decision is made, **pause and create an ADR** before committing code.
2. Use `--no-edit` to create the ADR, then fill in content directly in the markdown file.
3. Run `adrs doctor` to validate before committing.
4. Commit the ADR alongside the implementing code.
5. If a decision changes, supersede the old ADR — never edit an accepted ADR's decision.

See `.pi/skills/adrs/SKILL.md` for detailed usage.

---

## User Stories / Issues

User stories and feature issues are tracked as markdown files under `./docs/issues/`.
Each file uses YAML frontmatter for metadata and follows a consistent template.

**Frontmatter fields:**
- `tags` — list of relevant tags (e.g., `[repo, create]`)
- `priority` — P0 (critical), P1 (high), P2 (medium), P3 (low), P4 (nice-to-have)
- `phase` — roadmap phase (0–4, per the implementation roadmap)
- `endpoints` — GitHub REST API endpoints used
- `status``todo`, `in_progress`, `done`
- `blockedBy` — list of story names (without `.md`) that must be completed first
- `blocks` — list of story names (without `.md`) that depend on this story

**Dependency graph:**
- `auth-login` is the root — it blocks 26 stories (all auth, repo, gist, search, extensions, aliases, keys, orgs, codespaces, attestation).
- `repo-view` is the next critical node — it blocks 18 stories (PR, issue, release, label, workflow, secrets, variables, rulesets, cache, browse).
- Chains follow the pattern: **auth → list → view → mutate** (e.g., `auth-login → repo-view → pr-list → pr-view → pr-merge`).
- Leaf nodes (empty `blocks`) are terminal CRUD operations like `pr-close`, `issue-close`, `release-delete`.

**File naming:** `<command>-<subcommand>.md` (e.g., `repo-create.md`, `pr-review.md`).

**Template sections:**
- `# Title` — feature name
- `## As a` — user persona
- `## I want` — goal statement
- `## Acceptance criteria` — bullet list of verifiable outcomes
- `## Implementation notes` — technical guidance (optional)
- `## Scenarios` — Gherkin-style behavior scenarios (optional)

Always create a story file before implementing a new command or subcommand.

---

## Adding a New Command

1. Define the CLI args in `src/cli.rs` (Args struct + Subcommand variant).
2. Create `src/cmd/<command>.rs` with a `run()` function.
3. Add `pub mod <command>;` to `src/cmd/mod.rs`.
4. Wire the dispatch in `src/main.rs` `match cli.command { ... }`.
5. Add integration tests in `tests/` using `assert_cmd`.
6. Add snapshot tests for `--help` output using `insta`.
7. Update the usage table in `README.md`.
8. Run `just ci` before committing.

---

## Dependency Policy

- **No OpenSSL.** Use `rustls` everywhere. `deny.toml` bans `openssl` and `openssl-sys`.
- **No git2.** Use `gix` for all git operations. `deny.toml` bans `git2`.
- **Minimize dependencies.** Each new dependency must justify itself. Prefer crates from the rust-cli ecosystem.
- **Pin versions** in `[workspace.dependencies]`. Member crates inherit with `{ workspace = true }`.

---

## Documentation

- `#![deny(missing_docs)]` on the library crate.
- `//!` module overviews for every public module.
- `///` on every public item with `# Examples`, `# Errors`, `# Panics` sections.
- Intra-doc links (`[`Client`]`, `[`Repository`]`) throughout.
- README synced from crate docs via `cargo-rdme`.

---

## Resources

- `docs/research/api-overview.md` — GitHub API fundamentals (auth, versioning, pagination, rate limits)
- `docs/research/api-implementation-roadmap.md` — Prioritized 4-phase implementation plan
- `docs/research/rust-best-practices-applied.md` — Crate selection, config rationale, design principles
- `docs/research/research-linting.md` — Clippy, rustfmt, supply-chain tooling details
- `docs/research/research-cicd.md` — CI/CD pipeline design and action versions
- `docs/research/research-testing.md` — Testing strategy and tooling
- `docs/research/research-architecture.md` — Crate ecosystem and architecture patterns
- `docs/research/research-docs.md` — Documentation best practices
- `docs/research/research-deployment.md` — Distribution and release automation

---

## Open Plugins Integration

gor bundles an [Open Plugins](https://open-plugins.com/) v1.0.0 plugin manifest
and command files, so CherryPi and other conformant agent tools can discover
gor as an installable plugin.

### Plugin structure

```text
.plugin/plugin.json       # Manifest: name, version, metadata
commands/*.md             # Command files teaching agents how to use gor
```

Each `.md` file in `commands/` is loaded as a namespaced command available
at `/gor:<name>` (e.g. `/gor:pr`, `/gor:repo`).

### Installing the plugin

Any conformant agent tool that scans Open Plugins directories will discover
gor automatically when the repo root or a symlink is in the search path:

```bash
ln -s /path/to/gor ~/.local/share/cherrypi/plugins/gor
```

Or install via cargo and copy the plugin directory:

```bash
cargo install gor-cli
cp -r .plugin commands ~/.local/share/cherrypi/plugins/gor/
```

### Key principles

- The manifest (`name: "gor"`) namespaces all commands as `gor:<name>`.
- Command files are documentation-first: they teach the AI how to invoke `gor`
  with the right flags and `--json` output.
- Changes to the CLI surface (new subcommands, changed flags) should be
  reflected in the corresponding command file.
- Adding a new command group? Create a matching `commands/<group>.md`.