CHANGELOG.md updated with the audit-driven hardening pass spanning 13 findings across cmd/ and internal/. Adds CLAUDE.md documenting dev workflow, codex review pattern, release process, and homebrew tap update steps.
7.2 KiB
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
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 <T>" 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
codex exec --skip-git-repo-check --sandbox read-only \ -m gpt-5.4-mini --config model_reasoning_effort="medium" "<prompt>" 2>/dev/nullFor follow-up rounds resume the same session:
echo "<prompt>" | 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).
-
Bump version
// cmd/root.go Version: "0.4.0", -
Update CHANGELOG.md — prepend a new section. Format:
## [0.4.0] - YYYY-MM-DD ### BREAKING - <thing that broke> ### Added - ... ### Changed - ... ### Fixed - ... ### Security - ... -
Commit the version+changelog bump as a single commit:
git commit -m "chore: bump version to 0.4.0" -
Tag the commit:
git tag -a v0.4.0 -m "Release v0.4.0: <one-line summary>" -
Push commits and tag:
git push origin main git push origin v0.4.0 -
Create the Forgejo release page via fj itself:
fj release create v0.4.0 \ --title "v0.4.0: <summary>" \ --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.)
-
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:
url "ssh://git@forgejo.zerova.net/public/fj.git",
tag: "v0.4.0", # was v0.3.2
revision: "<SHA of v0.4.0 tag>" # update
test do
assert_match "0.4.0", shell_output("#{bin}/fj --version") # update
end
To get the SHA:
git -C ~/repos/fj rev-parse v0.4.0
Then in ~/repos/homebrew-sid:
# 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
fjreads the current dir'sgit originto detect the host. In a directory whose origin points at github.com (e.g. /opt/stacks/claude-code-proxy/build), barefj api ...errors with "no configuration found for host github.com". Pass--hostname forgejo.zerova.netexplicitly, orcdsomewhere else.--json=fieldswas removed in v0.4.0 in favor of--json-fields fields(or--json-fields=fields). The old=fieldsform was aNoOptDefVal=" "sentinel hack.--jsonis now a plain Bool meaning "as JSON".--configwas silently ignored before v0.4.0. Old fj versions read --config into Viper butinternal/config.Load()always read the default path. Fixed;fj --config other.yaml auth loginnow writes to other.yaml.- The
actionsandrun/workflowcommand trees share factory functions incmd/actions.go(newRunCmd,newWorkflowCmd). Don't add flags directly torunListCmdstyle globals — they don't exist anymore. Edit the factory and bothfj actions run listandfj run listget the change.
Useful commands
# 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.