Wraps the whole verb dispatch in run() with .with_context(|| format!("repo {repo}"))
instead of threading context through ~34 individual verb files. A body-decode
failure surfaces from forgejo-api as a bare ReqwestError with no status code or
URL retained (no client-injection point to capture more), so without this the
error alone can't distinguish a mistyped org/repo from a transient flake — see
the recent hive-forge triage-automation thread this was filed from.
Split run()'s match into a dispatch() fn so the repo can be captured once before
dispatching and the with_context wrap applied once after, uniformly, regardless
of which verb failed.
`fn main() -> Result<()>` let anyhow's Debug impl render failures with a
bare `Error:` header. hive-forge is almost always invoked from an agent's
bash task, where the completion wake points at the task's .out file - so a
failure that only writes to .err is easy to miss entirely (mara's "had no
clue it failed" on #2624).
Wrap the dispatch in a run() and own the failure path in main():
- prefix with the binary name (`hive-forge: FAILED: ...`) so the line is
unmistakably ours in a mixed transcript,
- render with {:#} (alternate Display), which keeps the full context chain
inline - plain Display would have dropped every `.context()` below the
top one,
- return ExitCode::FAILURE explicitly rather than relying on the Termination
impl.
Half of #2624: the other half (surfacing .err in the bash-mcp completion
when a task exits non-zero) is damocles's, per the issue thread.
Drop implementation mechanics from the `--help` surface, keep only
what a user needs to run the command:
- global `-r`/`-f`/`--json`: remove token-file paths, the bash-helper
history, and the "already-JSON verbs ignore --json" aside.
- verb `about` strings (repo-create/repo-labels/repo-search/artifact-get/
ci-log/ci-rerun/pr-commits): drop which-API / "no REST endpoint" /
web-route / workflow-dispatch internals and cross-refs.
Per-verb arg help (verbs/*.rs) trimmed in follow-up commits.
Targets a dashboard-provisioned external forge account (FORGES tab)
instead of the internal forge: resolves `forge-<label>-token` for the
token and `forge-<label>.json`'s base_url for the URL, the same two
files dashboard/extra_forges.rs writes, instead of
HIVE_FORGE_URL/forge-token. Falls back to today's behavior when unset.
Orthogonal to -r/--repo.
An unknown label gives a clear error listing the labels actually found
in the state dir instead of a raw file-not-found. The base_url JSON key
is read via a typed sidecar struct pinned to what extra_forges.rs
writes, so the read side can't silently drift from the write side.
hive-forge had close but no reopen, so reopening required the non-obvious
workaround 'issue edit <n> --state open'. Add a reopen verb mirroring close
(PATCH state=open), wired into both 'pr reopen' (kind-checked PR) and
'issue reopen' (kind-checked issue). Updates docs/forge.md + the subcommand
enumerations.
The clap subcommand doc in main.rs still described the old web-route
implementation (--job, 'POSTs the rerun web action'), which surfaces in
hive-forge --help. Update it to match the workflow-dispatch rework.
When a CI run fails for a transient reason (remote-builder flap, cold-daemon
window, act_runner hiccup) the only retrigger path was an empty commit, which
litters PR history and forces a force-push to clean up. This verb POSTs the
rerun action directly.
- `ci-rerun --run <n>` re-runs all jobs of a run (run number = the `runs/<n>`
the UI shows, same value ci-log / artifact-get take, surfaced as a CI
context's target_url by pr-status).
- `--pr <n>` resolves the run from the PR head sha's CI status target_url.
- `--job <i>` re-runs a single job.
Forgejo exposes no REST endpoint for rerunning a run, so this rides the run
page's web routes (`<base>/<owner>/<repo>/actions/runs/<n>[/jobs/<i>]/rerun`)
via a new `Client::post_web_no_content` (web base like post_json_web, tolerates
the redirect/empty response the rerun handler returns). Mirrors ci-log's
web-route approach + auth path.
docs/tools/forge.md updated with the verb.
Rebuild on forge's own notification read-state instead of the local
seen-cursor mirror (operator-nacked: mirrors state forge owns + drifts
on restart).
- comment refuses to post when forge has an unread notification for the
thread (someone commented since you last read it); --force overrides.
Degrades open if the notification check itself fails.
- comments / view mark the thread's notification read (the "I've seen
it" signal), clearing the guard for a subsequent reply.
- new crate::notify (no local file): unread_thread_id pages repo-scoped
unread notifications (newest-first, capped) + subject-url number match;
mark_thread_read via the new client.patch_no_content.
Pairs with the forge_notify harness change (#1895, merged) that leaves
delivered notifications unread until the agent actually reads. Covers
pr/issue comment too (they delegate to comment::run).
Group issue/PR operations under `pr` and `issue` parent commands
(`hive-forge pr close 42`, `issue create …`, `pr status --pr 42`) per the
operator decision — kind-namespaced verbs replace the flat surface.
- new `verbs::pr_cmd` / `verbs::issue_cmd` parent commands wrap the existing
per-verb modules (reuse their Args + run fns) under `#[command(subcommand)]`.
- kind-validation (the win over the old generic verbs): the generics that work
on both (view/comment/comments/close/labels/assign/timeline) call
`assert_kind` first, so `pr close <issue>` / `issue close <pr>` are rejected
with a 'use the other command' message. PR-only / issue-only verbs are
kind-correct by construction. `number` exposed `pub(crate)` on the shared
verbs so the wrappers can probe it.
- every flat kind verb (`close`, `pr-create`, `pr-status`, `issue-edit`, …)
kept as a `#[command(hide = true)]` back-compat alias — still parses, dropped
from --help; removed in a later sweep once usage migrates. (`pr`/`issue`
bare-show become `pr show` / `issue show` — the names are now parents.)
- docs/tools/forge.md documents the new surface + the deprecated aliases.
cargo build/clippy/fmt clean, 54 tests pass; --help surface + alias parsing
smoke-tested.
`labels <number>` only lists an issue/PR's labels; there was no way to
list the project-wide label set, which triage/labelling needs to discover
valid names. Add `repo-labels [pattern]` hitting GET /repos/{owner}/{repo}/labels
(paginated), with optional name-substring filter. Default prints one label
per line (name + tab-separated description when set); --json emits the full
label objects.
The verb that lists issues/PRs with filters is `list`, but `issues` and
`issue-list` are the names people reach for first — and clap's
"did you mean" tip points at `issue` / `issue-create`, never at `list`.
Two agents independently failed to find it that way.
Add `issues` and `issue-list` as visible clap aliases so the verb is
discoverable under the names people actually type (and they show in
`--help`). No behaviour change; `list` stays canonical.
Forgejo 15 has no REST endpoint to download an Actions artifact — the
only path is the web UI download route, which is keyed by the run's
global id rather than the per-repo run number shown in run-page URLs.
The REST artifacts list route keys off the run number instead, so the
two can't be chained directly.
artifact-get takes the run number (what pr-status surfaces as a CI
context target_url), translates it to the global run id via the REST
runs list by matching each run's html_url tail, then GETs the web
download route with the agent's forge token. Saves the artifact zip to
a path (default /tmp/forge-artifact-<name>.zip) or streams to stdout
with -o -.
The artifact name is percent-encoded into the path. The encoder that
list already used for query-string filters is promoted to a shared
verbs::pct_encode helper so both call sites stay in sync.
Lets an agent pull a CI-built artifact (e.g. a paper PDF) into /shared
without host access.
Adds `hive-forge pr-merge <n> [--method merge|rebase] [--keep-branch]
[--force]` wrapping POST /repos/{owner}/{repo}/pulls/{n}/merge, so agents on
the peer-review-and-merge workflow have a CLI path instead of the raw API.
- Methods: merge (default) | rebase. Squash is intentionally not offered.
- Deletes the head branch after merge unless --keep-branch.
- Safe by default: refuses unless the PR is mergeable, CI is not red, and no
review's current verdict requests changes (latest-per-reviewer wins, so a
later approval clears an earlier request-changes). --force overrides and
also sets Forgejo's force_merge.
- New client helper post_no_content for the 200-empty-body merge response.
Clean the legacy backlog so the tracker-tag lint can become a required
gate (mara's warn-during-cleanup -> full-tree-enforcement path). Rewrite
the ~33 real `closes/see #NNN` provenance refs in doc-comments to prose
across hive-forge, hive-c0re, hive-ag3nt, hive-matrix-mcp, hive-sh4re,
and add a `lint:allow` line marker to check-issue-refs.sh for genuine
non-tracker `#<digits>` (a hash-digit heading-detection test input).
Tree is now lint-clean; tracker-tag lint ready to promote to required.
Add `hivectl gateway {create-user,delete-user,list-users}` subcommands for
managing htpasswd files used by gateway Basic auth. Pure Rust bcrypt
(cost 12, $2y$ prefix nginx accepts). No external htpasswd binary required.
Also fix the NixOS module assertion: `cfg.auth ? htpasswdFile` is always
true in the module system (declared options always exist as keys); switch
to `nullOr path; default = null` + `!= null` check so the assertion
actually fires with a useful error when enable=true but no file is set.
Guard bind-mount and nginx config against null to prevent eval errors.
Update docs/gateway.md to show hivectl commands instead of raw htpasswd.
last unstarted piece of the original #694 epic. agents kept falling
back to curl for 'who closed this?' / 'when was this labelled?'
archaeology because view + comments only surface the body + comments,
not the structured timeline events (label adds, assignments, closes,
reopens, pushes, commit refs, review submissions, milestone changes).
separate verb rather than view --timeline because:
- composes naturally with view <n> / comments <n>
- keeps existing verb output shapes stable (no script breakage)
- argus on #770 already noted view's output is busy
human-readable by default ('**actor @ ts**: <summary>'), --json for
raw piping. unknown event types fall through to a '[<type>]'
placeholder so a forge schema bump doesn't panic the verb.
--tail N is a follow-up: timeline endpoint doesn't expose a total-count
field so the count-then-page strategy from #770 doesn't apply
directly.
6 tests cover comment / label add/remove / close / unknown-type
placeholder / missing-user fallback.