docs: sync README + CLAUDE.md + architecture.md with recent additions
Some checks are pending
ci / check (push) Waiting to run

* README: new commands (milestone, search code, watch/star, edit-comment,
  request-review) and the `--json-fields` / `--no-pager` globals.
* CLAUDE.md: layout includes json_filter and .forgejo/workflows; test
  count updated to 75; HTTP retry, JSON projection, pager-on-Unix
  conventions documented; setup-git hostname-validation invariant
  called out; auth token TTY guard noted.
* docs/architecture.md: HTTP retry section now covers 429 / Retry-After
  and Request::try_clone; new JSON projection section explains the
  global --json-fields flow; test strategy mentions remote CI and
  ignored env-mutating tests.
* docs/README.md: link to SECURITY.md and CHANGELOG.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stephen Way 2026-05-13 14:57:07 -07:00
parent faaf522b05
commit e60a810c69
No known key found for this signature in database
4 changed files with 64 additions and 16 deletions

View file

@ -26,18 +26,22 @@ src/
config/ hosts.toml shape + load/save
auth/ OS keychain wrapper
git/ git invocation + remote URL parser
output/ tables, colors, relative_time, state_pill, pager
output/ tables, colors, relative_time, state_pill, pager,
json_filter (--json-fields projection)
.forgejo/workflows/ CI mirror of the pre-push hook
hooks/pre-push local CI gate
scripts/ install-hooks.sh, e2e-smoke.sh
docs/ architecture, jq syntax, troubleshooting
docs/ architecture, jq, gh-to-fj, faq, troubleshooting
```
## Build / test / hook
- `cargo build --release` produces a ~4 MB binary at `target/release/fj`.
- `cargo test --all` runs 60 tests (51 unit + 9 wiremock integration).
- `cargo test --all` runs 75 tests (65 unit + 10 wiremock integration).
2 tests are `#[ignore]` (env-mutating, racy with cargo's harness).
- `cargo clippy --all-targets --all-features -- -D warnings` is the lint
bar. CI (the pre-push hook) treats warnings as errors.
bar. Both the pre-push hook and `.forgejo/workflows/ci.yml` treat
warnings as errors.
- `./scripts/install-hooks.sh` symlinks `hooks/pre-push` into `.git/hooks`.
- `FJ_E2E=1` enables the live API smoke against `stephen/fj-cli-test`.
- `FJ_SKIP_PREPUSH=1` bypasses the hook for genuine emergencies only.
@ -51,10 +55,20 @@ docs/ architecture, jq syntax, troubleshooting
open `$EDITOR`. See `cli::editor`.
- Lists with `--limit > 50` transparently follow `Link: rel=next` via
`Client::get_all`. Per-page cap is the Forgejo default (50).
- The simple jq projector lives in `cli::api::extract_path`. Supports
`.field`, `.0`, `.[0]`, `[-1]`, and `|` pipes. See `docs/jq.md`.
- HTTP retry: GET/HEAD/OPTIONS/PUT/DELETE get 3 attempts with 200/400/800
ms exponential backoff. 429 honors `Retry-After` (capped 30 s). POST
and PATCH are never retried.
- The simple jq projector for `fj api` lives in `cli::api::extract_path`.
Supports `.field`, `.0`, `.[0]`, `[-1]`, and `|` pipes. See
`docs/jq.md`.
- `--json-fields foo,bar` is a GLOBAL flag that applies gh-style
projection to ANY `--json` output. Lives in
`output::json_filter::project`, set from `cli::run`, read by
`output::print_json`.
- All commands respect `--host` / `FJ_HOST`, `--debug` / `FJ_DEBUG`,
`--no-pager`, `FJ_PAGER` / `PAGER`.
`--no-pager`, `FJ_PAGER` / `PAGER`, `--json-fields`.
- The pager is Unix-only. `output::pager::imp` is gated `#[cfg(unix)]`
with a no-op stub for other platforms.
## Things to remember
@ -65,6 +79,12 @@ docs/ architecture, jq syntax, troubleshooting
it gets its own keychain prompt. Tests that need keychain access are
the wiremock-backed ones in `src/client/integration_tests.rs`, which
bypass the keychain via `Client::for_base_url`.
- `fj auth setup-git` interpolates the hostname into a shell-evaluated
credential helper string. The host is validated against a strict DNS
pattern (`validate_hostname` in `cli/auth.rs`) before interpolation.
Don't bypass that validation if you refactor.
- `fj auth token` and `auth status --show-token` refuse to write a
plaintext token to a TTY by default (use `--force`).
- The repo URL in `Cargo.toml` is `https://rasterhub.com/rasterstate/fj`.
- Commit author is `Stephen Way <stephen@rasterstate.com>`. The user has
a CLAUDE.md preference: no em-dashes, terse responses, no preamble.

View file

@ -32,15 +32,16 @@ fj api /repos/search -X GET -f q=foo # raw API escape hatch
| Group | Common operations |
| ---------- | --------------------------------------------------------------------------------- |
| `auth` | login, status, logout, list, switch, token, refresh, setup-git |
| `repo` | list, view, clone, create, fork, sync, edit, rename, archive, unarchive, delete, branches, topics, mirror, mirror-sync |
| `issue` | list, view, create, edit, close, reopen, comment, develop |
| `pr` | list, view, create, edit, diff, commits, files, checks, ready, review, status, checkout, merge, close, reopen |
| `repo` | list, view, clone, create, fork, sync, edit, rename, archive, unarchive, delete, branches, topics, mirror, mirror-sync, watch, unwatch, star, unstar, starred |
| `issue` | list, view, create, edit, close, reopen, comment, edit-comment, delete-comment, develop |
| `pr` | list, view, create, edit, diff, commits, files, checks, ready, review, request-review, unrequest-review, status, checkout, merge, close, reopen |
| `release` | list, view, create, edit, delete, upload, download |
| `label` | list, create, edit, delete |
| `milestone`| list, view, create, edit, close, reopen, delete, assign |
| `run` | list, view, rerun, cancel (Forgejo Actions workflow runs) |
| `secret` | list, set, delete (Actions secrets) |
| `variable` | list, set, delete (Actions variables) |
| `search` | repos, issues, prs, users |
| `search` | repos, issues, prs, users, code |
| `browse` | open the current repo (or a path within it) in your browser |
| `status` | notifications inbox + `--mark-read` |
| `org` | list, view, teams |
@ -60,7 +61,11 @@ fj api /repos/search -X GET -f q=foo # raw API escape hatch
- `--host <hostname>` (or `FJ_HOST`): pick the host explicitly.
- `--debug` (or `FJ_DEBUG=1`): log every HTTP request to stderr.
- `--web` on most list/view subcommands: open the relevant page in your default browser.
- `--no-pager` (or `FJ_NO_PAGER=1`): skip the pager.
- `--json-fields foo,bar`: gh-style projection on top of any `--json`
output. Dotted paths supported (`--json-fields owner.login,id`).
- `--web` on most list/view subcommands: open the relevant page in
your default browser.
## Config

View file

@ -12,3 +12,5 @@
For build/test workflow see [`../CONTRIBUTING.md`](../CONTRIBUTING.md).
For project conventions see [`../CLAUDE.md`](../CLAUDE.md).
For security policy see [`../SECURITY.md`](../SECURITY.md).
For release notes see [`../CHANGELOG.md`](../CHANGELOG.md).

View file

@ -78,11 +78,14 @@ a clone.
In `client::request_with_headers`:
- The request is built once and stored as `reqwest::Request`.
- The request is built once and stored as `reqwest::Request`. Each retry
uses `Request::try_clone`.
- Idempotent methods (GET, HEAD, OPTIONS, PUT, DELETE) get 3 attempts.
- POST and PATCH get 1 attempt — they're never retried.
- POST and PATCH get 1 attempt; never retried.
- Backoff is `200ms * 2^attempt` between retries.
- On 5xx or transport error, we retry. On any other status, we return.
- On 5xx or transport error, we retry.
- On 429 we honor the `Retry-After` header (capped at 30 s) and fall
back to exponential backoff if the header is absent or non-numeric.
## Auto-pagination
@ -126,13 +129,31 @@ pager cleanly.
Only if neither pass triggers do we hand off to clap.
## JSON projection
A global `--json-fields foo,bar` flag works on top of any `--json`
output. `cli::run` reads it once and stores the parsed list in a
`OnceLock` in `output::mod.rs`. Every `output::print_json` call routes
through `json_filter::project` before printing, so individual commands
need no awareness of the filter.
Supports dotted paths (`owner.login`). Missing fields project to `null`
rather than failing, matching gh.
## Test strategy
- **Unit tests** stay inline next to the code they cover. Pure
functions only (no I/O).
functions only (no I/O), with two exceptions marked `#[ignore]`:
`editor_command_uses_visual_first` and
`edit_text_with_true_returns_initial` mutate `$EDITOR` / `$VISUAL`,
which races with the cargo test harness on macOS. Run them with
`cargo test -- --ignored --test-threads=1`.
- **Integration tests** for HTTP behavior live in
`src/client/integration_tests.rs` and use a wiremock server.
Construct test clients via `Client::for_base_url`.
- **Live E2E** lives in `scripts/e2e-smoke.sh`, gated on `FJ_E2E=1`
in the pre-push hook. It hits the real Forgejo at `rasterhub.com`
against `stephen/fj-cli-test`.
- **Remote CI** at `.forgejo/workflows/ci.yml` runs the same gate
(fmt, clippy `-D warnings`, test, release build) on every push and
PR. The local hook is no longer the only gate.