Commit graph hyperhive/hive-forge/src/verbs
Author SHA1 Message Date
damocles
7a3e7af6b8 hive-forge: timeline --since no longer errors on an empty window 2026-09-12 10:33:30 +02:00
damocles
46456f75ce remove as_str() legacy wrappers, callers use .into() directly 2026-09-12 00:06:31 +02:00
damocles
299add158f convert hand-written enum as_str matches to strum derives workspace-wide 2026-09-12 00:06:31 +02:00
damocles
1be4b81f65 hive-forge: comments/timeline default to newest, timeline gets a real total
Mara's decision on #4179: the default window for comments/timeline (no
flags at all) is now the newest 10, matching an explicit --tail 10 --
agent can still opt into the old oldest-first window via an explicit
--limit. Same restructure in both verbs: limit u64 -> Option<u64>, only
the explicit-limit branch takes the head path now, the tail-or-default
branch covers both --tail N and the no-flags case.

timeline.rs also gets the real fix: TimelineListHeaders.x_total_count is
a genuine field forgejo-api already returns on every timeline call --
verified against the vendored crate source. The old 'no total exists'
premise in the module doc was false, same shape as #3200. Replaced the
over-fetch-by-one boolean 'more' with an exact count via a cheap
page_size=1 fetch_total, mirroring comments.rs's fetch_tail pagination
math for --tail. JSON output reshaped to {events, more_before,
more_after, since_more, since_limit_clamped}, matching comments.rs --
a breaking interface change, intentional.

Also fixed a real pre-existing bug in comments.rs's truncation_note:
the more_before message suggested retrying with --tail, which is
nonsensical since more_before only ever fires from a tail-shaped
window. Now correctly suggests --limit. Added a regression test.

fixes #4179
2026-09-11 19:13:30 +02:00
damocles
7b9d8e4c4c hive-forge: resolve ci-rerun --run's PR pseudo-ref before dispatching
A run triggered by a pull_request-event workflow carries a #<n> pseudo-ref
in prettyref rather than a real branch name. Dispatching a workflow with
that as the ref 500s. resolve_run now recognizes the #<n> form and falls
back to the same branch_for_pr lookup --pr already uses, so ci-rerun --run
works on PR-triggered runs the same way ci-rerun --pr does.

diagnosis + discriminating control table (real branch vs #<n> pseudo-ref)
from atlas.

fixes #4201
2026-09-11 17:22:33 +02:00
damocles
911238ad73 hive-forge: fix pr_merge.rs's dead pr-merge: bail! prefix
7 bail! messages in pr_merge.rs were prefixed pr-merge: -- the flat-alias
verb name that main.rs's renamed-verb list now refuses to run, not the
current pr merge kind-namespaced form. Someone who read the prefix and
tried hive-forge pr-merge got a second, unrelated refusal for a verb that
doesn't exist anymore.

#4157 swept the renamed verbs out of docs, prompts and the deprecation
list; it didn't cover runtime error prefixes, which is where these
survived (same shape as the two pr_status.rs had, fixed in #4182/#4184).

One of the seven also told the reader to check pr status --pr {number};
now pr status {number}, matching #4184's new positional form.

fixes #4183
2026-09-11 11:03:07 +02:00
atlas
84203a3f9b hive-forge: let pr status take the PR number positionally
Every other PR-scoped verb takes the number as a positional — `pr show
42`, `pr comments 42`, `pr assign-reviewer 42 argus`. `pr status` alone
required `--pr 42`, so whichever form you learn first is wrong for the
other, and clap's error for the mistake suggests `-- --pr`, which would
pass the literal string on as the next positional.

The number is positional here too now. `--pr` stays, because it was the
only spelling this verb had; `--sha` keeps its flag because the two are
alternatives rather than one required argument.

Two error strings in this file also named `pr-status`, a form that has
refused to run since the subcommand rename. One of them is the message
you get for passing neither argument — i.e. exactly when you are already
unsure what the verb is called.

The four new cases cover both halves: that clap accepts each spelling,
and that something reads it. `target_pr` is named rather than inlined
for exactly that reason — dropping the positional from the selection
passed every parse-only case while leaving `pr status 42` reporting
"pass a PR number". The conflict case earns its keep the same way: clap
accepting an argument and clap ignoring it are indistinguishable from a
passing parse, so `42 --sha abc` and `42 --pr 42` both have to be
rejected.

Closes #4182.
2026-09-11 09:04:20 +02:00
atlas
55f01942a2 docs, prompts, hive-forge: stop handing readers the renamed verbs
docs/tools/forge.md already listed the nine renamed verbs as removed, then
used them ~30 more times in pasteable blocks. Sweeps every occurrence a
reader would type, including three runtime messages that told the user to
run a verb the same binary rejects.

The renamed-verb list itself keeps the old names; it is what documents them.

Refs #4155
2026-09-10 17:22:57 +02:00
iris
7396903994 rewrite generated CLI docs' passive voice to active
hive-forge and hivectl doc-comment strings for pr-create --label, diff
--full, list --label, ci-runs --branch, and several hivectl subcommand
options read as if things happened to themselves. Name the actor (hivectl,
hive-forge, or the CLI) instead, and regenerate the three docs/tools/*-cli.md
files from the rebuilt binaries.

swarmctl-cli.md needs no source change: its only passive hit is
clap-markdown's own generated boilerplate, out of scope here.
2026-09-08 14:56:26 +02:00
iris
e82a735745 docs: fix write-good.So/ThereIs/Weasel lint findings
Fixes the "obvious ones first" slice of #4042 (mara: do the obvious
ones first) -- 81 hits across write-good.So, write-good.ThereIs, and
write-good.Weasel, all in docs/. Each is a genuine sentence rewrite
(lead with the real subject instead of "There is/are", drop a
sentence-initial "So ", replace a vague intensifier), not a blind
regex substitution -- read every hit in its real file context before
touching it.

3 of the 81 hits were in CI-generated CLI docs (docs/tools/{hivectl,
swarmctl,forge}-cli.md) -- fixed at the clap #[arg(...)]/doc-comment
source in hivectl/src/cli.rs, swarmctl/src/main.rs, and
hive-forge/src/verbs/repo_add_collaborator.rs, then regenerated via
each crate's `markdown-docs` subcommand so CI's freshness check stays
green.

Verified: fresh vale re-run shows 0 remaining So/ThereIs/Weasel hits
and no new hits introduced (983->982, exactly the one incidental fix
this pass also picked up at docs/scheduler/observability.md:48).
cargo fmt --check and clippy clean on the three touched crates.

Remaining write-good backlog (Passive: 726, TooWordy: 207) is
judgment-heavy and left for a follow-up slice of #4042, not bulk-
rewritten here.
2026-09-07 17:49:27 +02:00
iris
17554ea563 fix: sync generated hive-forge CLI docs with their clap source strings
Same bug as the swarmctl/hivectl fix, a third instance argus's review
didn't name but nix/checks.nix's hive-forge-docs freshness check (same
pattern as hivectl-docs/swarmctl-docs) caught in CI: the earlier
Contractions/Foreign/Auto batches edited docs/tools/forge-cli.md
directly instead of the clap #[arg(...)]/doc-comment strings in
hive-forge/src/main.rs and hive-forge/src/verbs/*.rs.

Applied the same 13 wording changes to source that the earlier commits
already made to the generated .md, matched 1:1 against
'git diff origin/main HEAD -- docs/tools/forge-cli.md' rather than
guessed. Several source doc comments feed two rendered sections each
(e.g. reaction.rs's one Add-variant doc renders under both
'issue reaction add' and 'pr reaction add', since both subcommands
share the same enum) -- one source fix, two generated-doc fixes.

Regenerated from the now-fixed source and confirmed byte-identical to
what's already committed (diff exit 0) -- source and generated output
are back in sync, same as the swarmctl/hivectl fix.

cargo clippy -p hive-forge --all-targets -- -D warnings and
scripts/check-doc-refs.sh both clean.
2026-09-07 16:28:06 +02:00
atlas
cdac6091eb hive-forge: name the CI job ci-log actually served
`ci-log --run <n> --job <idx>` accepted any index and exited 0. Past the
run's job count it printed job 0's log, with nothing marking the
substitution. Measured against run 3363 (4 jobs): indices 0-3 gave three
distinct md5s, while 99 and 12345 both returned output byte-identical to
job 0.

Both web log routes clamp an out-of-range index to job 0 and answer 200.
I had claimed nothing in the response distinguishes them -- that was
measured on the body only, and the body was the wrong place to look. The
`Content-Disposition` header names the job the server actually served:

    --job 3   -> filename="ci-doc-pointer lint-8363.log"
    --job 99  -> filename="ci-nix flake check-8360.log"   (job 0's)

So the fix is to report what came back rather than to pre-validate the
index. Establishing the real job count needs two extra API calls on every
indexed read -- `ActionRun` carries no job count, and the only job
endpoints are the per-run listing and a per-job log keyed by internal job
id, not by index. The header costs nothing: it is already in the response
being read.

The provenance line goes to stderr, so it cannot corrupt a piped log;
under `--json` it is a `served` field instead. An out-of-range `--job` is
therefore no longer an error -- it is a read whose true subject is named.

`get_bytes_named` is a sibling of `get_bytes_raw` rather than a signature
change, leaving the attachment and artifact download paths untouched.

Rebased onto main after the two flat-rename PRs landed; the only conflict
was the test module's import list, resolved by keeping both sides. While
reading the surrounding context this commit's own defect surfaced:
`check_status`'s doc comment had been left glued to the head of
`disposition_filename`'s, so one function carried two unrelated
descriptions and the other carried none. No gate can see that -- it is
well-formed rustdoc either way.
2026-09-02 21:57:43 +02:00
atlas
74b48c5afa hive-forge: test the two invariants dependency.rs documents but never checked
dep_meta's comment records a measured 404: leaving owner/repo as None
made every same-repo dependency fail, because forgejo resolves the
dependency's repo from the request BODY rather than the URL. Nothing
stopped a later tidy-up from simplifying them back to None.

index() promises "error instead of wrapping". A wrap would not fail
loudly -- it would hand the forge a negative index it looks up as some
other issue.
2026-09-02 19:01:31 +02:00
atlas
8a48f958b8 hive-forge: ci-runs --branch takes the refs the listing prints
The runs-list query's `ref` filter matches only a fully-qualified ref,
while the listing prints `prettyref` (`main`, `#3967`). So no value a
caller can read off the output was accepted by the filter, and the
rejection rendered as "no matching runs" — indistinguishable from a
branch that has never been built.

Qualify the value before it reaches the query: `refs/...` passes
through, `#N`/`N` becomes the pull head ref, anything else becomes a
branch. A slash cannot mark an already-qualified ref, since branch
names contain them.

Not shared with ci-rerun: its `ref` is a workflow_dispatch body field
taking a bare branch name, so the same normalisation would break it.

Closes #3970
2026-09-02 17:16:56 +02:00
damocles
fe7bf81d4a hive-forge: defer repo resolution to the accessor, not construction 2026-09-02 17:08:01 +02:00
atlas
b0507a0936 hive-forge: move the lint test module to the end of the file
clippy::items_after_test_module (pedantic, denied) — a #[cfg(test)] mod
has to be the last item in its file. Pure move: same line count, same
line multiset, no content change.

`cargo test` was green while this was broken; only clippy saw it.
2026-09-02 11:22:42 +02:00
atlas
a78e783c3b hive-forge: 10 behaviour tests for lint's pure helpers, which had none
WIP commit so the mutation run below has a clean base to restore to.
2026-09-02 11:17:14 +02:00
damocles
16f82f3ef6 hive-forge: remove pr-blocked-by-issue hint now that the direction works 2026-09-02 10:22:20 +02:00
damocles
56c0602c2f hive-forge: add ci-runs listing verb, fix ci-log's ambiguous no-log message 2026-09-02 08:36:44 +02:00
iris
07b62612b0 docs: restructure into topic subdirectories, collapse duplicated index
Per mara's go-ahead on hyperhive#3902 ("getting started is good, but
terminal rendering does not go in there i think"):

Moved 21 top-level docs/*.md files into 7 new topic subdirectories
(existing web-ui/, turn-loop/, swarm/, tools/, crates/ untouched):
  getting-started/  setup.md
  agent-lifecycle/  agent-hierarchy.md, approvals.md, persistence.md
  trust-boundary/   boundary.md, security.md
  integrations/     forge.md, matrix.md, github.md, knowledge.md
  networking/       gateway.md, network.md, snapshot-store.md
  scheduler/        jobq.md, coordinator.md, ci.md, observability.md
  process/          conventions.md, gotchas.md, pr-review-gate.md
  web-ui/           terminal-rendering.md (moved into the EXISTING dir,
                    per mara's correction to the original getting-started
                    guess -- it's UI implementation detail, not onboarding)

The physical layout now matches docs/README.md's own topical headers,
which already amounted to this taxonomy -- see the scoping comment on
the issue for the two findings that motivated this (a genuine
duplication between CLAUDE.md's old "Reading paths" list and
docs/README.md's grouped one, since drifted out of sync with each
other; and the flat layout not matching the grouping we already had).

Fixed every cross-reference this moved across the whole repo (~120
files: docs/ internal links at every depth, Rust doc comments, nix
module option docs, crate READMEs) -- verified two ways: a grep sweep
confirming zero remaining references to any old path, and a script
that resolves every markdown link in docs/**/*.md + CLAUDE.md +
README.md against the filesystem and reports anything that doesn't
exist (zero broken links).

Collapsed CLAUDE.md's "Reading paths" section (the duplicate) down to
a pointer at docs/README.md, now the single index. Rewrote
docs/README.md itself to use the new subdirectory paths and added the
one doc it was missing that CLAUDE.md's old copy had (pr-review-gate.md).

Classified all 22 docs/*.md files first via a haiku subagent (mara's
suggestion) on two axes -- proposed grouping and operator-vs-
implementation focus -- before finalizing the taxonomy; spot-checked
the report and found internal inconsistencies (its classification
table disagreed with its own summary section for a few files), so this
taxonomy is my original proposal + the one correction mara gave
directly, not a blind application of the subagent's table. The
operator-focus data it gathered is still useful for a follow-up
content pass (docs skewing 'mixed' rather than pure operator-facing),
not addressed in this PR -- structure only.

nix fmt clean, both pre-push lints clean.
2026-09-02 01:55:37 +02:00
damocles
f5a4e380c9 hive-forge: precise wording on stale-branches rebase+ff ancestry claim 2026-08-28 21:44:19 +02:00
damocles
b8eb598329 hive-forge: fix stale-branches comment/docs — hyperhive rebase+ff merges, not squash 2026-08-28 21:44:19 +02:00
damocles
fa73cd1ddb hive-forge: flag stale-branches merge outcome as unknown when the PR fetch is truncated 2026-08-28 21:44:19 +02:00
damocles
6e05863f02 hive-forge: lint stale-branches reports each branch's merge outcome 2026-08-28 21:44:19 +02:00
atlas
7630b0993c hive-forge: hoist a PR row's merge state in list --json
Forgejo's issue-list endpoint answers 'was this merged?' only inside the
nested pull_request object, while state says closed for a merged PR and
for one closed without merging alike. So the obvious top-level query is
null or ambiguous for every row, and with a // default it renders as a
confident 'nothing merged' that cannot ever be right -- a wrong answer
shaped exactly like a clean one.

Copy merged and merged_at up to the top level of each PR row so the
obvious query is the correct one. Additive: the nested object is left
untouched so an existing consumer keeps working, and issue rows have no
pull_request and pass through unchanged.

The head branch is deliberately not hoisted: this endpoint does not carry
it at all -- the row's ref is an empty string, not the branch -- so there
is nothing to lift. pr show has head_branch.
2026-08-26 23:39:05 +02:00
damocles
676f7715fd hive-forge: stop embedding the forge token in clone URLs 2026-08-26 00:20:58 +02:00
damocles
62ee57123d hive-forge: document ci-rerun --pr's event-type gap on PR status 2026-08-24 15:05:25 +02:00
damocles
ad7a0572cd hive-forge: explain the pr-blocked-by-issue dependency failure instead of a raw 500 2026-08-24 12:09:08 +02:00
damocles
bd4906ab17 hive-forge: unit-test blocking_open_count 2026-08-19 15:17:28 +02:00
damocles
a3f14f5126 hive-forge: surface issues an issue/pr blocks in list/issue/pr 2026-08-19 15:03:15 +02:00
damocles
3c4ff447e3 hive-forge: issue-create refuses an empty body 2026-08-19 01:43:10 +02:00
damocles
8b83eca0c1 fix(#3435): hive-forge issue/pr show fail on forgejo's null-for-empty reactions
Forgejo returns a bare `null` body (not `[]`) for a reactions list when
nothing has reacted yet. issue_reactions/comment_reactions deserialized
straight into forgejo-api's typed Vec<Reaction>, which has no null
tolerance, so issue show / pr show / view failed on every item with zero
reactions - i.e. nearly everything.

Generalize pr_status's existing null_as_empty (same quirk, hit earlier
on combined-status statuses) into a shared helper in verbs/mod.rs, and
fetch reactions via the raw JSON path (Client::get_api_json) with a
NullableVec<T> wrapper instead of the typed client's bare Vec<Reaction>.

Also names the failing request in errors going forward, since
get_api_json's error context includes the URL - closes the gap the
issue itself flagged (the old error said what didn't parse but not
what was fetched).
2026-08-18 15:26:07 +02:00
damocles
fb9cc5635a hive-forge reaction: add --list-allowed to surface the instance's configured shortcodes 2026-08-18 13:53:27 +02:00
damocles
2c45a9960f hive-forge: show + post/remove emoji reactions on issues, PRs, and comments 2026-08-18 13:53:27 +02:00
damocles
a796c24037 hive-forge list: show dep-progress count for items with open dependencies 2026-08-18 12:18:57 +02:00
damocles
4f8b78a4d4 hive-forge dependency: don't drop the original errors if the verification read-back also fails 2026-08-16 18:51:46 +02:00
damocles
eefd971bd8 hive-forge dependency: surface a converged-despite-error case instead of swallowing it 2026-08-16 18:48:39 +02:00
damocles
7668454bae hive-forge: verify dependency add/remove by read-back instead of trusting HTTP status 2026-08-16 18:47:01 +02:00
damocles
bad5285f2e fix(hive-forge): populate owner/repo in dependency add/remove body 2026-08-16 14:57:17 +02:00
damocles
b7a7628845 hive-forge: point to per-line review comments instead of inlining them, fix pr reviews line numbers 2026-08-15 12:34:23 +02:00
damocles
6a95aceedb hive-forge: add dependency verb for issue/pr blocking relationships 2026-08-15 09:44:28 +02:00
damocles
b4e56b964f pr-merge: don't tell the caller to just un-draft a PR that isn't theirs 2026-08-13 15:21:12 +02:00
damocles
dd5ccb5ce7 hive-forge: don't assert 'conflicts' from a bare mergeable=false 2026-08-13 15:21:12 +02:00
damocles
6f3ac755e0 hive-forge: surface comment/issue attachments in comments/view/comment-show/issue 2026-08-11 20:31:36 +02:00
atlas
0e64f46221 fix(#3129): treat a blank head sha as unknown too, not as a real value
argus's review note: the emptiness guard was one-sided. A reviewed sha
of "" was already treated as unknown, but a head of "" was not — so a
forge returning an empty string rather than omitting the field would
make every review compare unequal and mark the whole PR stale. That is
the wrong-direction failure this function exists to prevent, and the
asymmetry contradicted its own doc comment.

Both sides now check emptiness, for the same reason: a blank string is
a value the forge sent, not a sha it has.
2026-08-09 21:52:09 +02:00
atlas
178eb13993 fix(#3129): age out a review against the PR head, not forgejo's stale bit
Forgejo's per-review `stale` flag is eventually consistent: seconds
after a push it still reports the pre-push answer, so a verdict against
the previous head reads as current in exactly the window where the CLI
gets run right after pushing.

latest_reviews now fetches the PR head once and ORs a direct comparison
of the review's own commit_id into the flag. Fixing it at construction
rather than at the call sites means superseded() is unchanged and all
three consumers are corrected together: assign-reviewer's refusal,
pr_merge's changes-requested gate, and — the one that matters most —
pr_status's readiness verdict, which could otherwise report a dead
approval as valid.

Unknowns fail toward keeping the verdict: a missing head or commit_id
degrades to today's behaviour instead of voiding every review on the PR.
2026-08-09 21:47:57 +02:00
atlas
6e64489050 fix(forge): suggest the nearest name when a filter value misses
The third bullet of the issue's ask, dropped in the first pass and caught
in review: a filter value that does not resolve is a near-miss far more
often than an invention, and an error that only lists all 18 available
names makes the reader do the diff by eye -- on the one occasion they
already know they mistyped something.

Thresholded rather than always suggesting the minimum-distance
candidate: a wrong suggestion is worse than none, because it invites a
second failed attempt at a name that was never there. The bound scales
with the needle (a third of its length, capped at 3), so a short name
does not match half the repo and a long one still tolerates a typo or
two, and an unrelated word falls back to the full list.

Tie-break is on length then alphabetical, so the suggestion does not
depend on the order the forge happened to return its labels in.
2026-08-05 22:13:45 +02:00
atlas
0aa9a854bc fix(forge): validate list's label + milestone filters, and paginate both
A filter value the forge cannot resolve is DISCARDED, not rejected, so a
typo does not narrow the result set -- it returns the unfiltered one.
That does not waste a query, it inverts the answer: "is anything open in
this milestone" comes back as every open issue and reads as yes, and a
duplicate check gets a list that never narrowed.

`list` now resolves both before querying. Labels reuse the write side's
resolver; the ids are discarded because this endpoint filters by name, so
resolution here is a spell-check rather than a lookup -- reusing it keeps
the message identical to the one the write side has always produced.
Milestones accept a title or an id and are checked against the ALL-state
set: filtering on a closed milestone is a normal query, and validating
against open-only would reject exactly the retrospective ones.

Both fetchers paginate. `repo_labels` asked for one page of 100 and
treated it as the population -- the inverse of the trailer bug, same
root: a valid label past the cut fails to resolve, and the error then
prints an "available labels" list that is itself truncated, so the
message argues for the typo.

`--assignee` / `--author` stay unvalidated on purpose: someone who has
left still legitimately appears on old issues, so a login that is not a
current member is not necessarily a typo.

Also drops the docs paragraph claiming unknown labels are silently
dropped on the write side; that has not been true since the resolver
landed.
2026-08-05 22:06:12 +02:00
atlas
8e690c0694 feat(forge): search + milestone filters, and a page trailer that can't lie
`list` already built its query with `q: None, milestones: None` — both
fields were on the request it was sending. So full-text search over
title and body is a flag, not a new verb, and a text match is only
useful composed with the other filters anyway.

The trailer was the real defect. It fired on `count == limit`, but the
forge clamps page size to its own `api.MAX_RESPONSE_ITEMS`: ask for 400,
get a full 50, and `50 != 400` kept it silent — suppressing the warning
in precisely the case where the truncation is invisible. It now reports
the real total from `X-Total-Count`, which the response header struct
already parsed and the call site discarded. The requested limit is not
clamped client-side: that ceiling is the remote's configuration, not
ours.
2026-08-05 18:45:17 +02:00
damocles
4cf647aa70 hive-forge: add --since cursor paging to comments and timeline 2026-08-03 20:50:53 +02:00