Commit graph

5397 commits

Author SHA1 Message Date
Babak K. Shandiz
7603a0c8ce
fix(discussion/shared): quote arg in errors
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-24 21:56:11 +01:00
Babak K. Shandiz
57b3e9091c
fix(discussion/shared): anchor discussion url regexp
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-24 21:55:54 +01:00
Babak K. Shandiz
cd3b23bf36
docs(discussion view): add preview remark
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-24 21:37:58 +01:00
Max Beizer
dea54ab3ab
fix: gofmt alignment in GetWithComments response struct
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 21:24:31 +01:00
Max Beizer
ca84d4c6a3
Add discussion view --comments with threaded display
Implement the --comments flag for discussion view, showing threaded
comments with replies.

Features:
- --comments flag fetches and displays discussion comments
- --order flag (oldest/newest) controls comment ordering
- Answer badge (✓ Answer) on marked answer comments
- Threaded replies with indentation
- Truncation messages when more replies exist than fetched
- TTY: markdown-rendered comments with author/timestamp/reactions
- Non-TTY: stable tab-delimited format for scripting
- JSON: populated comment nodes via ExportData

Implementation:
- GetWithComments uses raw GraphQL to dynamically switch between
  first/last based on ordering. Fetches 30 comments with 4 replies
  each. Explicitly reverses for newest-first ordering.
- --order without --comments returns a flag error
- Reuses existing shared.ReactionGroupList for reaction display

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 21:24:30 +01:00
Max Beizer
d6e63f63d3
fix(discussion view): align with post-review list changes
- Rewrite GetByNumber to use strongly-typed GraphQL query instead of
  removed raw string interpolation (discussionFields/discussionNode)
- Fix State string references to use Closed bool throughout view.go
- Fix DiscussionAuthor → DiscussionActor type rename in tests
- Add ReactionGroups field to Discussion domain type and ExportData
- Add computed "state" field to ExportData for JSON output
- Add shared.DiscussionFields for view command's --json flag
- Regenerate client mock

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 21:24:30 +01:00
Max Beizer
d9e9751823
Add discussion view command
Implement `gh discussion view` for viewing a single discussion with:

- Number or URL argument via shared.ParseDiscussionArg
- TTY output: title, metadata (state, category, author, age, comment
  count), labels, markdown-rendered body, reactions
- Context-aware author attribution: "Asked by" for answerable
  categories (Q&A), "Started by" for others
- Non-TTY output: key-value pairs matching `gh issue view` format
- JSON output via Exporter (Discussion.ExportData)
- --web flag to open in browser
- Pager support for TTY output

Also adds:
- GetByNumber client method with not-found detection
- shared.ParseDiscussionArg for number/URL/#number parsing
- shared.ReactionGroupList for emoji reaction display

Comment threading (--comments) is deferred to the next PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 21:24:25 +01:00
Babak K. Shandiz
2b794ed992
refactor(discussion/client): remove redundant "first" variable init
The fetch loop already assigns "first" on each iteration, so the
initial assignment in the variables map is dead code. Remove it from
both List and Search.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 14:21:24 +01:00
Babak K. Shandiz
81117364ba
refactor(discussion/client): convert tests to httpStubs pattern
Replace responses/checkVarsFns with httpStubs func per test case in
TestList, TestSearch, and TestListCategories, matching the pattern used
in agent-task/capi tests. Expand inline JSON to heredoc, remove flower
box comments, and add "empty list" and "exact fit" test cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 14:19:19 +01:00
Max Beizer
45de7db4d5
style: run gofmt on client_impl_test.go
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 16:49:22 -05:00
Max Beizer
5db230b317
test(discussion): assert GQL variables, add Bot actor and limit>100 cases
- Replace false-positive filter tests with GraphQLQuery responders that
  assert on actual GQL variables (first, after, states, answered, orderBy,
  categoryId in List; query string qualifiers in Search)
- Add Bot actor test case (Bot.ID maps to DiscussionActor.ID, Name is empty)
- Add limit>100 test cases for both List and Search to verify the
  per-iteration first variable is set correctly (100 on page 1, remainder
  on page 2)
- Fix limit>100 bug in client_impl.go: move variables["first"] assignment
  inside the loop so each iteration caps at min(remaining, 100)
- Remove intStr helper; use strconv.Itoa directly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 16:36:07 -05:00
Max Beizer
aa080ad28a
refactor(discussion): convert client tests to table-driven
Addresses babakks' review on PR #13252:
- Convert 18 individual test functions to 3 table-driven functions
- Replace ptr helper with new(value) syntax throughout
- Assert all Discussion struct fields in success cases
- Add after-cursor test cases for pagination
- Fold pagination tests into table structure

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 15:04:59 -05:00
Max Beizer
35e8cc93cf
test(discussion): add httpmock unit tests for DiscussionClient
Add comprehensive httpmock-based unit tests for the client package covering:
- List: success path, discussions disabled, limit/filter validation, pagination
- Search: success path, filter validation, pagination
- ListCategories: success path, discussions disabled

Tests use httpmock.Registry with defer Verify(t) to ensure all stubs
are exercised, following the established testing pattern in this repo.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 09:12:31 -05:00
Babak K. Shandiz
2a4a982ae4
fix(discussion list): include search keywords in web mode
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 15:07:32 +01:00
Babak K. Shandiz
f62875fb63
fix(discussion list): quote author qualifier in web mode
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 15:05:22 +01:00
Babak K. Shandiz
8d70cc9ca8
refactor(discussion list): encapsulate printing within printDiscussions
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 15:03:02 +01:00
Babak K. Shandiz
d45ce940eb
refactor(discussion list): inline printed messages
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 15:00:17 +01:00
Babak K. Shandiz
2d1b1a79bf
fix(discussion list): only print remaining items in tty mode
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 14:52:05 +01:00
Babak K. Shandiz
835d7f3a48
fix(discussion/client): remove reaction groups from list/search types
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 14:50:40 +01:00
Babak K. Shandiz
9a42e904a5
fix(discussion/shared): deleted unused fields slice
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 14:37:25 +01:00
Babak K. Shandiz
afb1b7dfea
fix(discussion/shared): print quoted category slugs
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 14:36:21 +01:00
Babak K. Shandiz
e403e82633
refactor(discussion/client): use strongly-typed query for list function
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 14:30:21 +01:00
Babak K. Shandiz
9afbd61c5f
refactor(discussion/client): use strongly-typed query for search function
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 13:25:42 +01:00
Babak K. Shandiz
2a46a9d733
fix(discussion/client): change list return type to pointer
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 12:24:08 +01:00
Babak K. Shandiz
e6befd5efd
fix(discussion/client): simplify list query
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 12:16:23 +01:00
Babak K. Shandiz
034e38f0e7
fix(discussion/client): fetch author/answerChosenBy fields
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 12:11:00 +01:00
Babak K. Shandiz
15d3eeae06
fix(discussion/client): fetch body in list/search
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 11:53:36 +01:00
Babak K. Shandiz
236224dc44
fix(discussion list): replace state with closed in domain types
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 11:52:52 +01:00
Babak K. Shandiz
3f52503a67
fix(discussion list): use separate list of fields
Signed-off-by: Babak K. Shandiz <babakks@github.com>
2026-04-14 11:45:55 +01:00
Max Beizer
0687a29e51
Fix GQL schema: Discussion uses closed bool, not state string
The GraphQL Discussion type has a `closed` boolean field, not a
`state` string. Updated the API response struct and GQL fragment
to query `closed` instead of `state`, and derive the domain-level
State string ("OPEN"/"CLOSED") from the boolean in mapDiscussion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 11:51:22 -05:00
Max Beizer
17238050d7
Check hasDiscussionsEnabled in ListCategories
When --category is used, ListCategories runs before the List query.
On repos with discussions disabled, it silently returns empty categories,
leading to a confusing "must be one of []" error. Now it checks
hasDiscussionsEnabled and returns the standard "discussions disabled"
error, matching the behavior of List and Search.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 10:38:51 -05:00
Max Beizer
a08f5f22f0
Fix gofmt alignment in test file
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 10:24:52 -05:00
Max Beizer
e84ecb1585
Address review feedback on discussion list PR
- Export domain consts (FilterStateOpen/Closed, OrderByCreated/Updated,
  OrderDirectionAsc/Desc) in types.go
- Change State fields to *string (nil = all states)
- Add DiscussionListResult type with NextCursor for pagination
- Update List/Search signatures: add after param, return result type
- Add limit <= 0 guard clauses in client methods
- Replace strings.ToUpper/ToLower with switch statements + default errors
- Rename pageLimit to remaining, hoist hasDiscussionsEnabled check
- Use qualifier/keyword terminology in search query building
- Quote author values with %q for whitespace safety
- Add Keywords string field (single string, not []string)
- Split --order into --sort {created|updated} and --order {asc|desc}
- Add --search/-S and --after flags
- Add next field in JSON output envelope
- Extract defaultLimit const
- Add toFilterState helper for CLI-to-domain mapping
- Move matchCategory to shared/categories.go with godoc
- Use single-line error messages with sorted slugs
- Add (preview) annotations to Short docs
- Update Use to "list [flags]"
- Add examples for --answered=false, --state all, multi-label
- Update tests for new signatures and new flags

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 10:20:09 -05:00
Max Beizer
90449b5197
Implement gh discussion list command
Add the discussion list command with full support for:
- Listing discussions with state, category, answered, and order filters
- Searching discussions by author and labels (uses Search API)
- Category resolution by slug or name (case-insensitive)
- TTY and non-TTY table output with colored IDs and labels
- JSON output with totalCount envelope
- Web mode (--web) to open discussions in browser
- Pagination for large result sets

Implement List, Search, and ListCategories methods on the
DiscussionClient. List and Search use plain text GraphQL for
flexible variable handling. ListCategories uses safely typed
GraphQL via shurcooL/githubv4.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-03 12:46:28 -05:00
Babak K. Shandiz
65f5b21121
Merge pull request #13082 from maxbeizer/discussion-cmd
Add `discussion` command group scaffolding
2026-04-02 18:14:07 +01:00
Max Beizer
45c68b48da
Add discussion command group scaffolding
Introduce the pkg/cmd/discussion/ package with:

- DiscussionClient interface and domain types (client/)
- Generated mock via moq (client/)
- Factory function for lazy client creation (shared/)
- JSON field definitions for --json output (shared/)
- Root 'discussion' command registered in the core group

The interface defines all planned operations (list, search, get, create,
update, close, reopen, comment, lock, unlock, mark-answer, unmark-answer)
with stub implementations that will be replaced as each subcommand is
added in subsequent PRs.

Domain types are intentionally separate from API types per review guidance.
No JSON struct tags are used; serialization is handled by ExportData methods.

Refs: cli/cli#12810, github/gh-cli-and-desktop#115

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 10:55:48 -05:00
William Martin
40da05861a
Merge pull request #13048 from thaJeztah/snappier
replace github.com/golang/snappy with klauspost/compress/snappy
2026-03-31 12:25:44 +02:00
Sebastiaan van Stijn
6868d273ec
replace github.com/golang/snappy with klauspost/compress/snappy
The github.com/golang/snappy repository was archived and is no longer
maintained. klauspost/compress provides a drop-in replacement, which
is actively maintained, and the klauspost/compress module is already
an existing (indirect) dependency.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-26 22:58:08 +01:00
William Martin
268453803e fix(api): propagate InvokingAgent in gh api HTTP client
The gh api command builds its own HTTP client inline without
forwarding InvokingAgent, so the User-Agent header was missing
the Agent/<name> suffix when invoked by AI coding agents.

Thread InvokingAgent through Factory → ApiOptions → HTTPClientOptions,
mirroring the existing AppVersion pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-26 16:55:16 +01:00
Kynan Ware
87426ee236 Add experimental huh-only prompter gated by GH_EXPERIMENTAL_PROMPTER
Introduce a new Prompter implementation (huhPrompter) that uses the
charmbracelet/huh library in its standard interactive mode, as an
alternative to the survey-based default prompter. The new implementation
is gated behind the GH_EXPERIMENTAL_PROMPTER environment variable,
following the same truthy/falsey pattern as GH_ACCESSIBLE_PROMPTER.

Key differences from the accessible prompter:
- No WithAccessible(true) flag (full interactive TUI)
- Uses EchoModePassword (masked with *) instead of EchoModeNone
- No default value annotations appended to prompt text

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-26 14:24:56 +01:00
Kynan Ware
8f7d20855e
Merge pull request #13025 from cli/kw/refactor/reviewer-assignee-actor-symmetry
Consolidate actor-mode signals into ApiActorsSupported
2026-03-25 11:43:18 -06:00
William Martin
69d89a6590
Merge pull request #12884 from cli/babakks/use-min-discovery-fields-for-issue-create
fix(issue): avoid fetching unnecessary fields for discovery
2026-03-25 15:39:40 +01:00
Kynan Ware
391e6616d5 fix(survey): use useReviewerSearch consistently in prompt path
The reviewer prompt branch checked reviewerSearchFunc != nil directly
instead of useReviewerSearch, making the fetch and prompt decisions
inconsistent. This mirrors how the assignee path already uses
useAssigneeSearch at both the fetch and prompt gates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 23:28:26 -06:00
Kynan Ware
bff468bafe fix(pr create): wire up @copilot assignee replacement and [bot] suffix
Two bugs introduced in #13009 found during acceptance testing:

1. `pr create --assignee @copilot` sent the literal `@copilot` to the
   API because NewIssueState only ran MeReplacer on assignees, not
   CopilotReplacer. Fixed by switching to SpecialAssigneeReplacer (which
   handles both @me and @copilot) like issue create already does.

2. The replaceActorsForAssignable mutation requires the [bot] suffix on
   bot actor logins (e.g. `copilot-swe-agent[bot]`), unlike
   requestReviewsByLogin which has a separate botLogins field. Added the
   suffix in ReplaceActorsForAssignableByLogin for CopilotAssigneeLogin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 23:26:28 -06:00
Kynan Ware
6a68ebc1c9 refactor(survey): simplify ApiActorsSupported in RepoMetadataInput
The expression was redundantly re-checking whether assignees or
reviewers were chosen. RepoMetadata already gates on
input.Assignees || input.Reviewers before consulting
ApiActorsSupported, so passing state.ApiActorsSupported directly
is sufficient.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 21:37:58 -06:00
Kynan Ware
ae5e857c2e refactor(featuredetection): rename ActorIsAssignable to ApiActorsSupported
Aligns the feature detector field name with the downstream
ApiActorsSupported flag introduced in the previous commit, so the
signal has one consistent name from detection through to consumption.

Also consolidates leftover TODO tags (actorIsAssignableCleanup,
requestReviewsByLoginCleanup) under the single // TODO ApiActorsSupported
tag so there's exactly one thing to grep for.

Pure rename with no logic changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 21:09:04 -06:00
Kynan Ware
3c00ffdade refactor(pr shared): consolidate ActorAssignees and ActorReviewers into ApiActorsSupported
The CLI had two per-entity flags (ActorAssignees on EditableAssignees and
IssueMetadataState, ActorReviewers on IssueMetadataState) threaded through
different layers of the stack to distinguish github.com from GHES. Both
flags were always set from the same source (issueFeatures.ActorIsAssignable)
and never had different values, but they were carried independently on
different structs. This led to a confusing asymmetry where:

- EditableAssignees had ActorAssignees but EditableReviewers had nothing
- The PR edit flow piggybacked on editable.Assignees.ActorAssignees to
  make reviewer mutation decisions, which was misleading
- RepoMetadataInput only had ActorAssignees with no reviewer equivalent

This commit replaces all per-entity flags with a single ApiActorsSupported
bool hoisted to the shared level on Editable, IssueMetadataState, and
RepoMetadataInput. Both assignees and reviewers now key off the same signal.

Every branch site is marked with // TODO ApiActorsSupported so we can grep
for cleanup sites when GHES eventually supports the actor-based mutations
(replaceActorsForAssignable, requestReviewsByLogin).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 21:04:41 -06:00
Kynan Ware
1df6f84d70
Merge pull request #13009 from cli/fix/pr-create-assignee-metadata-13000
Use login-based assignee mutation on github.com
2026-03-24 20:29:11 -06:00
Kynan Ware
4e6bc78e04 refactor(pr shared): extract SpecialAssigneeReplacer for @me and Copilot expansion
The inline replaceSpecialAssigneeNames closures in AssigneeIds and
AssigneeLogins were duplicated. Extract them into an exported
SpecialAssigneeReplacer type that consolidates MeReplacer and
CopilotReplacer into a single ReplaceSlice call, parameterised by
actorAssignees and copilotUseLogin.

Adopt the new type in the issue create flow as well, replacing the
manual MeReplacer + conditional CopilotReplacer sequence.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 18:59:21 -06:00
William Martin
1ea2952566
Merge pull request #13023 from cli/agent-in-header
Record agentic invocations in User-Agent header
2026-03-24 20:28:32 +01:00