# AGENTS.md `tg` is a Go 1.26+ CLI for Tangled (git forge on atproto), built with Cobra + Viper. Entry point: `cmd/tg/main.go`. ## Verify changes (in order) 1. `nix fmt` — treefmt wrapper (gofmt, prettier, alejandra, shfmt/shellcheck, statix, deadnix). Without Nix, run those formatters directly. 2. `nix build` — the real gate: compiles, runs `go test ./...`, and generates man pages + shell completions. **`go build` passing is not enough.** 3. Quick iteration only: `go build ./cmd/tg`, `go test ./...`. ## MCP server - `mcp/` is the tgmcp Python package, using FastMCP and Pydantic to wrap tg's JSON commands. - Run Python commands from `mcp/` with uv. Its dependencies and tests are separate from Go. - `nix build .#tgmcp` builds the MCP server with tg from the same checkout. - Keep `mcp/src/tgmcp/models.py` in sync with JSON types in `internal/app/domain_types.go`. ## Nix gotchas - Changing `go.mod`/`go.sum` invalidates `vendorHash` in `nix/tg.nix`. `nix build` fails with a hash mismatch; copy the `got:` hash into `vendorHash` and rebuild. Don't commit dependency changes until the build passes with the updated hash. - Man pages are generated at build time by the hidden `tg man` command (invoked from `nix/tg.nix`). - Docs are the Astro site in `website/`: hand-written pages in `website/src/content/docs/` (the `reference/commands/` pages are generated and gitignored, produced from help text by `website/scripts/gen-commands.mjs`, which needs `tg` built first). Build with `cd website && bun install && bun run build`. ## CI Tangled pipelines in `.tangled/workflows/` (nixery engine), not GitHub Actions: `nix build .#tg`, `go test ./...`, and the website build (`.tangled/workflows/website.yml`). Default branch is `master`. ## Architecture - `internal/app/` — frontend-independent application layer. `app.Service` bundles the resolver, appview, and auth dependencies and exposes every operation (target resolution, issue/PR/repo/string/SSH-key CRUD, auth flows) as methods returning typed domain structs. All application logic lives here. - `internal/cli/` — thin Cobra frontend over `internal/app`: one file per command, all built in `NewRoot`. Each `RunE` parses flags/args into a service call and renders the returned struct. - `tangled/` — read-only client for the Bobbin appview XRPC API (default `https://api.tangled.org`; override via `--appview`/`TG_APPVIEW`). - Writes go two ways: PDS record mutations with the user's session (`atproto/`), and knot server RPCs (`knot/`) authed with a PDS-minted service-auth JWT. Both are orchestrated by `internal/app`. - `atproto/` — handle↔DID resolution, PDS discovery, OAuth + app-password sessions stored in the OS keyring. - `internal/gitutil/` — git subprocesses (clone, fetch, patch apply); its tests need `git` in PATH. ## Conventions - New command: add the service method to `internal/app/`, then a thin file in `internal/cli/` that parses flags and calls it, registered with `AddCommand` in `NewRoot`. - Commands returning data must support `--json` via `output(cmd, data, human)` in `internal/cli/output.go`. The JSON structs are the canonical domain types in `internal/app/types.go`. - Repo-targeting commands accept `handle/repo` or auto-detect from the `origin` remote (see `internal/app/target.go` and the `resolveTarget`/`resolveTargetFlag` shims in `internal/cli/target.go`). - Config is Viper: flag > `TG_` env var > `$XDG_CONFIG_HOME/tg/config.toml` > default. - Tests are plain unit tests with table-driven style; keyring tests use an in-memory fake, no real keyring needed. Tests for application logic live in `internal/app/`. - Commit messages: lowercase `: `, area = package/command path (e.g. `app:`, `cli:`, `atproto/auth:`, `nix/tg:`, `gitutil:`).