# fj — guide for Claude Code sessions `fj` is a personal Forgejo/Gitea CLI tool, modeled on GitHub's `gh`. It targets `forgejo.zerova.net` (and Codeberg). The user (sid) owns it; the canonical repo is `public/fj` on forgejo.zerova.net (mirrored from there to nowhere else). This file is read first by Claude Code when working in `~/repos/fj`. Goal: get a session productive quickly without re-deriving the dev workflow each time. ## Layout ``` ~/repos/fj/ ├── cmd/ cobra command definitions, one file per subject area │ ├── root.go rootCmd, --config plumbing, OnInitialize │ ├── auth.go login/status/logout/token (uses persistent --hostname) │ ├── api.go raw API access; --json/--json-fields/--jq/--paginate │ ├── json.go shared JSON output helpers (addJSONFlags/wantJSON/outputJSON) │ ├── paginate.go generic paginateGitea[T] helper for list commands │ ├── errors.go CLIError with structured Hint field │ ├── actions.go Forgejo Actions; runs/workflows via factory functions │ ├── aliases.go top-level `fj run` / `fj workflow` aliases — calls actions.go factories │ ├── repo.go pr.go issue.go release.go wiki.go label.go milestone.go │ └── ... ├── internal/ │ ├── api/client.go SharedHTTPClient (30s timeout); GetJSON/DoJSON/DownloadFile │ ├── config/config.go YAML config; honors --config via SetExplicitConfigPath │ ├── git/ repo + host detection from `git remote` │ ├── iostreams/ wrapped stdin/stdout/stderr + spinner + pager + colors │ └── text/ formatting helpers ├── main.go thin entrypoint; ContextualError + JSON-error rendering ├── Makefile build / lint / test (no release automation) ├── CHANGELOG.md Keep-a-Changelog format └── README.md ``` ## Build, install, test ```bash go build ./... # quick build check go test ./... # unit tests go install . # build + install to ~/go/bin/fj (the binary that's on PATH) make lint # golangci-lint, if you have it ``` After any change in cmd/ or internal/, run `go install .` and the global `fj` reflects it immediately. There's no daemon/restart. ## Auth The user is authenticated as `sid` on `forgejo.zerova.net`. Token lives in `~/.config/fj/config.yaml` (mode 0600). For HTTPS git pushes from this host, the token can be injected via `git -c "http.extraHeader=Authorization: token " push` — the local SSH key (`sid@debian` on forgejo) is also registered, so `git@forgejo.zerova.net:public/fj.git` works directly. ## Code review pattern (use this for non-trivial changes) For audits or significant refactors, run **three reviewers in parallel** with non-overlapping focuses (we did this in the v0.4.0 cycle and it found bugs none would have caught alone): - **Codex** — read-only sandbox, peer-AI cross-check ```bash codex exec --skip-git-repo-check --sandbox read-only \ -m gpt-5.4-mini --config model_reasoning_effort="medium" "" 2>/dev/null ``` For follow-up rounds resume the same session: `echo "" | codex exec --skip-git-repo-check resume --last 2>/dev/null`. Codex remembers prior critique. - **Claude general-purpose agent A** — architecture / UX / code-quality - **Claude general-purpose agent B** — security / correctness / error handling Tell each reviewer what the **siblings** are covering so they don't duplicate. Cap reports at ~600 words. Consolidate findings by severity (HIGH / MEDIUM / LOW) before presenting to the user. ## Release process We use semver. **Pre-1.0**: breaking change → minor bump (e.g. v0.3.x → v0.4.0). 1. **Bump version** ```go // cmd/root.go Version: "0.4.0", ``` 2. **Update CHANGELOG.md** — prepend a new section. Format: ```markdown ## [0.4.0] - YYYY-MM-DD ### BREAKING - ### Added - ... ### Changed - ... ### Fixed - ... ### Security - ... ``` 3. **Commit** the version+changelog bump as a single commit: ```bash git commit -m "chore: bump version to 0.4.0" ``` 4. **Tag** the commit: ```bash git tag -a v0.4.0 -m "Release v0.4.0: " ``` 5. **Push** commits and tag: ```bash git push origin main git push origin v0.4.0 ``` 6. **Create the Forgejo release page** via fj itself: ```bash fj release create v0.4.0 \ --title "v0.4.0: " \ --notes "$(awk '/^## \[0.4.0\]/{flag=1;next} /^## /{flag=0} flag' CHANGELOG.md)" ``` (The awk one-liner extracts the just-added CHANGELOG section as release notes.) 7. **Update the homebrew tap** — see the next section. ## Updating the homebrew tap (`public/homebrew-sid`) The tap lives at `~/repos/homebrew-sid` (or `git@forgejo.zerova.net:public/homebrew-sid.git`). The `Formula/fj.rb` formula references the source by `tag:` + `revision:` (SHA), so a release bump touches three lines: ```ruby url "ssh://git@forgejo.zerova.net/public/fj.git", tag: "v0.4.0", # was v0.3.2 revision: "" # update test do assert_match "0.4.0", shell_output("#{bin}/fj --version") # update end ``` To get the SHA: ```bash git -C ~/repos/fj rev-parse v0.4.0 ``` Then in `~/repos/homebrew-sid`: ```bash # edit Formula/fj.rb (the three lines above) git commit -am "fj: bump to v0.4.0" git push origin main ``` After push, users can `brew update && brew upgrade fj` to pick up the new version. ## Common footguns - **`fj` reads the current dir's `git origin`** to detect the host. In a directory whose origin points at github.com (e.g. /opt/stacks/claude-code-proxy/build), bare `fj api ...` errors with "no configuration found for host github.com". Pass `--hostname forgejo.zerova.net` explicitly, or `cd` somewhere else. - **`--json=fields` was removed in v0.4.0** in favor of `--json-fields fields` (or `--json-fields=fields`). The old `=fields` form was a `NoOptDefVal=" "` sentinel hack. `--json` is now a plain Bool meaning "as JSON". - **`--config` was silently ignored before v0.4.0.** Old fj versions read --config into Viper but `internal/config.Load()` always read the default path. Fixed; `fj --config other.yaml auth login` now writes to other.yaml. - **The `actions` and `run`/`workflow` command trees share factory functions** in `cmd/actions.go` (`newRunCmd`, `newWorkflowCmd`). Don't add flags directly to `runListCmd` style globals — they don't exist anymore. Edit the factory and both `fj actions run list` and `fj run list` get the change. ## Useful commands ```bash # Live test against forgejo (using the new flags) fj --hostname forgejo.zerova.net api repos/public/fj --json-fields full_name,description # Walk paginated endpoints fj --hostname forgejo.zerova.net api 'repos/public/fj/commits?limit=10' --paginate --jq '.[].sha[0:8]' # Confirm both command trees stay in sync after edits diff <(fj run list --help | grep -E "^ -|^ --" | sort) \ <(fj actions run list --help | grep -E "^ -|^ --" | sort) # Empty diff = trees agree. Any output = factory drift. ```