fj/CLAUDE.md
sid 0069198ca6
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
CI / functional (push) Has been cancelled
chore: bump version to 0.4.0
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.
2026-05-02 16:05:15 -06:00

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/null
    

    For 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).

  1. Bump version

    // cmd/root.go
    Version: "0.4.0",
    
  2. Update CHANGELOG.md — prepend a new section. Format:

    ## [0.4.0] - YYYY-MM-DD
    
    ### BREAKING
    - <thing that broke>
    
    ### Added
    - ...
    ### Changed
    - ...
    ### Fixed
    - ...
    ### Security
    - ...
    
  3. Commit the version+changelog bump as a single commit:

    git commit -m "chore: bump version to 0.4.0"
    
  4. Tag the commit:

    git tag -a v0.4.0 -m "Release v0.4.0: <one-line summary>"
    
  5. Push commits and tag:

    git push origin main
    git push origin v0.4.0
    
  6. 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.)

  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:

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

  • 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

# 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.