From e60a810c69563489c0f3718a2c5b2a186e27e811 Mon Sep 17 00:00:00 2001 From: Stephen Way Date: Wed, 13 May 2026 14:57:07 -0700 Subject: [PATCH] docs: sync README + CLAUDE.md + architecture.md with recent additions * 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) --- CLAUDE.md | 34 +++++++++++++++++++++++++++------- README.md | 15 ++++++++++----- docs/README.md | 2 ++ docs/architecture.md | 29 +++++++++++++++++++++++++---- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8cd330f..6df5912 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 `. The user has a CLAUDE.md preference: no em-dashes, terse responses, no preamble. diff --git a/README.md b/README.md index 8ba0c5a..308b0b8 100644 --- a/README.md +++ b/README.md @@ -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 ` (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 diff --git a/docs/README.md b/docs/README.md index d515cd8..b86998a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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). diff --git a/docs/architecture.md b/docs/architecture.md index 38e7f26..4873f42 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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.