Skip to content

Commit db504bb

Browse files
committed
feat(bugs): support priority in list, show, and a new prioritize command
1 parent 618ea7f commit db504bb

7 files changed

Lines changed: 682 additions & 48 deletions

File tree

‎.claude/skills/detail-bugs/SKILL.md‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
---
22
name: detail-bugs
3-
description: Interact with Detail bugs for a repository via the CLI — list and filter bugs, inspect reports, close as resolved or dismissed, and reopen previously closed bugs.
3+
description: Interact with Detail bugs for a repository via the CLI — list and filter bugs, inspect reports, close as resolved or dismissed, reopen previously closed bugs, and override a bug's priority.
44
---
55

66
# Detail Bugs
77

8-
The Detail CLI exposes per-repository bugs through four subcommands: `list`, `show`, `close`, and `reopen`. This skill describes that surface so you can pick the right command for whatever the user is trying to do.
8+
The Detail CLI exposes per-repository bugs through five subcommands: `list`, `show`, `close`, `reopen`, and `prioritize`. This skill describes that surface so you can pick the right command for whatever the user is trying to do.
99

1010
## Prerequisites
1111

@@ -28,6 +28,8 @@ Lists bugs for the inferred or specified repository.
2828

2929
- `--status pending|resolved|dismissed` — default `pending`; comma-separate or repeat the flag to combine (e.g. `--status resolved,dismissed`).
3030
- `--vulns` — only security vulnerabilities.
31+
- `--priority p1|p2|p3|none` — only bugs at these priorities; comma-separate or repeat the flag (e.g. `--priority p1,p2`). `none` selects bugs Detail never scored — most bugs found before priority scoring shipped, so prefer `--priority p1,p2,p3` over `--priority p1` when the user asks for "prioritized" bugs. Default: every priority.
32+
- `--sort newest|oldest|priority` — default `newest`. `priority` puts the most severe first and unscored last.
3133
- `--introduced-by <authors>` — filter by authors (comma-separated or repeated).
3234
- `--scan-id <wr_…>` — limit to a specific scan. Workflow IDs come from `detail scans list`.
3335
- `--since` / `--until` — accept a duration (`1d`, `24h`), an ISO date (`YYYY-MM-DD`), or an RFC3339 timestamp.
@@ -39,6 +41,8 @@ Lists bugs for the inferred or specified repository.
3941

4042
Shows the full report for a single bug. Reports often include a suggested fix.
4143

44+
Also shows `Priority` and, when Detail scored the bug, a `Rationale` explaining why. If someone has since overridden that score, an `Override` line reports what Detail originally assigned and why it was changed.
45+
4246
- `--format table|json` — use `json` when parsing rather than displaying.
4347

4448
### `detail bugs close <BUG_ID>`
@@ -53,3 +57,13 @@ Marks a bug as resolved or dismissed. The CLI prompts for `--state` interactivel
5357
### `detail bugs reopen <BUG_ID>`
5458

5559
Flips a previously resolved or dismissed bug back to `pending`. Takes only the bug ID — useful when a fix is reverted or a dismissal is overturned.
60+
61+
### `detail bugs prioritize <BUG_ID>`
62+
63+
Overrides Detail's priority for a bug and records the change on its timeline. The CLI prompts for `--priority` interactively in a TTY; pass it explicitly when invoking non-interactively.
64+
65+
- `--priority p1|p2|p3`.
66+
- `--comment "..."` — why the priority is changing. Worth passing: it is what a later `detail bugs show` reports as the override reason.
67+
- `--format table|json`.
68+
69+
Setting the priority a bug already has is a no-op — the CLI reports "no change" rather than recording a second identical entry.

‎docs/HELP.md‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This document contains the help content for the `detail` command-line program.
1414
* [`detail bugs show`↴](#detail-bugs-show)
1515
* [`detail bugs close`↴](#detail-bugs-close)
1616
* [`detail bugs reopen`↴](#detail-bugs-reopen)
17+
* [`detail bugs prioritize`↴](#detail-bugs-prioritize)
1718
* [`detail completions`↴](#detail-completions)
1819
* [`detail rules`↴](#detail-rules)
1920
* [`detail rules create`↴](#detail-rules-create)
@@ -115,6 +116,7 @@ List, show, and close bugs
115116
* `show` — Show the report for a bug
116117
* `close` — Close a bug as resolved or dismissed
117118
* `reopen` — Reopen a previously resolved or dismissed bug — flips it back to pending. Useful when a "fix" PR is reverted or a "won't fix" decision is overturned
119+
* `prioritize` — Set a bug's priority, overriding Detail's own assessment
118120

119121

120122

@@ -137,6 +139,16 @@ List bugs for a given repository
137139
Possible values: `pending`, `resolved`, `dismissed`
138140

139141
* `--vulns` — Only show security vulnerabilities
142+
* `--priority <PRIORITY>` — Only show bugs at these priorities — repeat the flag or comma-separate values (e.g. `--priority p1,p2`). Use `none` for bugs Detail never assigned a priority. Default: all priorities
143+
144+
Possible values: `p1`, `p2`, `p3`, `none`
145+
146+
* `--sort <SORT>` — Result ordering. `priority` puts the most severe bugs first and unprioritized bugs last
147+
148+
Default value: `newest`
149+
150+
Possible values: `newest`, `oldest`, `priority`
151+
140152
* `--introduced-by <INTRODUCED_BY>` — Only show bugs introduced by these authors (comma-separated or repeat flag)
141153
* `--scan-id <SCAN_ID>` — Filter bugs to a specific scan by workflow request ID
142154
* `--since <SINCE>` — Only show bugs created at or after this point. Accepts a duration (e.g. 1d, 24h, 30m) interpreted as "now minus this", an ISO date (YYYY-MM-DD), or an RFC3339 timestamp
@@ -220,6 +232,32 @@ Reopen a previously resolved or dismissed bug — flips it back to pending. Usef
220232

221233

222234

235+
## `detail bugs prioritize`
236+
237+
Set a bug's priority, overriding Detail's own assessment
238+
239+
**Usage:** `detail bugs prioritize [OPTIONS] <BUG_ID>`
240+
241+
###### **Arguments:**
242+
243+
* `<BUG_ID>` — Bug ID
244+
245+
###### **Options:**
246+
247+
* `--priority <PRIORITY>` — Priority to set (prompted interactively if omitted in a TTY)
248+
249+
Possible values: `p1`, `p2`, `p3`
250+
251+
* `--comment <COMMENT>` — Why the priority is changing — recorded on the bug's timeline
252+
* `--format <FORMAT>` — Output format
253+
254+
Default value: `table`
255+
256+
Possible values: `table`, `json`
257+
258+
259+
260+
223261
## `detail completions`
224262

225263
Print shell completion script to stdout.

‎openapi.json‎

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@
7272
},
7373
"type": "array"
7474
},
75+
"priority": {
76+
"$ref": "#/components/schemas/Priority"
77+
},
78+
"priorityReason": {
79+
"$ref": "#/components/schemas/PriorityReason"
80+
},
7581
"repoId": {
7682
"$ref": "#/components/schemas/RepoId"
7783
},
@@ -180,6 +186,14 @@
180186
],
181187
"type": "string"
182188
},
189+
"BugSortOrder": {
190+
"enum": [
191+
"newest",
192+
"oldest",
193+
"priority"
194+
],
195+
"type": "string"
196+
},
183197
"BugSource": {
184198
"enum": [
185199
"review",
@@ -304,6 +318,37 @@
304318
"pattern": "^org_.*",
305319
"type": "string"
306320
},
321+
"Priority": {
322+
"description": "Detail's severity assessment, most severe first: P1 (High), P2 (Medium), P3 (Low).",
323+
"enum": [
324+
"P1",
325+
"P2",
326+
"P3"
327+
],
328+
"type": "string"
329+
},
330+
"PriorityReason": {
331+
"properties": {
332+
"generatedPriority": {
333+
"$ref": "#/components/schemas/Priority"
334+
},
335+
"isCurrent": {
336+
"type": "boolean"
337+
},
338+
"overrideComment": {
339+
"type": "string"
340+
},
341+
"text": {
342+
"type": "string"
343+
}
344+
},
345+
"required": [
346+
"text",
347+
"generatedPriority",
348+
"isCurrent"
349+
],
350+
"type": "object"
351+
},
307352
"Repo": {
308353
"properties": {
309354
"fullName": {
@@ -570,6 +615,26 @@
570615
"$ref": "#/components/schemas/BugReviewState"
571616
}
572617
},
618+
{
619+
"description": "Comma-separated priorities to include, e.g. \"P1,P2\". Use \"none\" to include bugs that were never assigned a priority. Omit to include every priority.",
620+
"example": "P1,none",
621+
"in": "query",
622+
"name": "priority",
623+
"required": false,
624+
"schema": {
625+
"type": "string"
626+
}
627+
},
628+
{
629+
"description": "Result ordering. `priority` puts the most severe bugs first and unprioritized bugs last.",
630+
"in": "query",
631+
"name": "sort",
632+
"required": false,
633+
"schema": {
634+
"$ref": "#/components/schemas/BugSortOrder",
635+
"default": "newest"
636+
}
637+
},
573638
{
574639
"in": "query",
575640
"name": "limit",
@@ -710,6 +775,95 @@
710775
"summary": "Get a bug"
711776
}
712777
},
778+
"/public/v1/bugs/{bug_id}/priority": {
779+
"post": {
780+
"description": "Overrides Detail's priority for a bug and records the change on the bug's timeline. Setting the priority the bug already has is a no-op and returns no priorityChangeId.",
781+
"operationId": "updatePublicBugPriority",
782+
"parameters": [
783+
{
784+
"in": "path",
785+
"name": "bug_id",
786+
"required": true,
787+
"schema": {
788+
"$ref": "#/components/schemas/BugId"
789+
}
790+
}
791+
],
792+
"requestBody": {
793+
"content": {
794+
"application/json": {
795+
"schema": {
796+
"properties": {
797+
"comment": {
798+
"description": "Why the priority is being changed.",
799+
"maxLength": 2000,
800+
"type": "string"
801+
},
802+
"priority": {
803+
"$ref": "#/components/schemas/Priority"
804+
}
805+
},
806+
"required": [
807+
"priority"
808+
],
809+
"type": "object"
810+
}
811+
}
812+
},
813+
"required": true
814+
},
815+
"responses": {
816+
"200": {
817+
"content": {
818+
"application/json": {
819+
"schema": {
820+
"properties": {
821+
"priority": {
822+
"$ref": "#/components/schemas/Priority"
823+
},
824+
"priorityChangeId": {
825+
"pattern": "^bfrpc_.*",
826+
"type": "string"
827+
}
828+
},
829+
"required": [
830+
"priority"
831+
],
832+
"type": "object"
833+
}
834+
}
835+
},
836+
"description": "OK"
837+
},
838+
"4XX": {
839+
"content": {
840+
"application/json": {
841+
"schema": {
842+
"$ref": "#/components/schemas/ApiError"
843+
}
844+
}
845+
},
846+
"description": "Client error"
847+
},
848+
"5XX": {
849+
"content": {
850+
"application/json": {
851+
"schema": {
852+
"$ref": "#/components/schemas/ApiError"
853+
}
854+
}
855+
},
856+
"description": "Server error"
857+
}
858+
},
859+
"security": [
860+
{
861+
"BearerAuth": []
862+
}
863+
],
864+
"summary": "Set a bug's priority"
865+
}
866+
},
713867
"/public/v1/bugs/{bug_id}/review": {
714868
"post": {
715869
"description": "Creates or updates a review on a bug (resolve, dismiss, or reopen).",

‎src/api/client.rs‎

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,24 @@ use progenitor::progenitor_client::{Error as ProgenitorError, ResponseValue};
1010

1111
use super::generated::types::CreateRuleBody;
1212
use super::types::{
13-
Bug, BugDismissalReason, BugId, BugReview, BugReviewState, BugsResponse,
13+
Bug, BugDismissalReason, BugId, BugReview, BugReviewState, BugSortOrder, BugsResponse,
1414
CreatePublicBugReviewBody, CreateRuleInput, CreateRuleResponse,
15-
ListPublicBugsWorkflowRequestId, RepoId, ReposResponse, Rule, RuleCreationRequestId, RuleId,
16-
RuleRequestStatus, RuleRequestsResponse, RulesResponse, ScansResponse, UserInfo,
15+
ListPublicBugsWorkflowRequestId, Priority, PriorityUpdate, RepoId, ReposResponse, Rule,
16+
RuleCreationRequestId, RuleId, RuleRequestStatus, RuleRequestsResponse, RulesResponse,
17+
ScansResponse, UpdatePublicBugPriorityBody, UserInfo,
1718
};
1819

20+
/// Server-side knobs for `GET /public/v1/bugs` beyond repo, status, and paging.
21+
/// Bundled so the fetch helpers can thread them through without growing a long
22+
/// positional tail.
23+
#[derive(Clone, Copy, Default)]
24+
pub struct BugListQuery<'a> {
25+
/// Comma-separated priority filter, already in the API's wire form.
26+
pub priority: Option<&'a str>,
27+
pub sort: Option<BugSortOrder>,
28+
pub scan_id: Option<&'a ListPublicBugsWorkflowRequestId>,
29+
}
30+
1931
/// Convert a progenitor client error into a concise anyhow error.
2032
///
2133
/// progenitor's own `Display` for `ErrorResponse` dumps headers and the typed
@@ -104,15 +116,17 @@ impl ApiClient {
104116
status: BugReviewState,
105117
limit: u32,
106118
offset: u32,
107-
scan_id: Option<&ListPublicBugsWorkflowRequestId>,
119+
query: BugListQuery<'_>,
108120
) -> Result<BugsResponse> {
109121
self.inner
110122
.list_public_bugs(
111123
NonZeroU64::new(limit.into()),
112124
Some(offset.into()),
125+
query.priority,
113126
repo_id,
127+
query.sort,
114128
status,
115-
scan_id,
129+
query.scan_id,
116130
)
117131
.await
118132
.map(ResponseValue::into_inner)
@@ -147,6 +161,31 @@ impl ApiClient {
147161
.map_err(api_error)
148162
}
149163

164+
/// Override Detail's priority for a bug.
165+
///
166+
/// A response with no `priority_change_id` means the bug already carried
167+
/// this priority and nothing was written.
168+
pub async fn set_bug_priority(
169+
&self,
170+
bug_id: &BugId,
171+
priority: Priority,
172+
comment: Option<&str>,
173+
) -> Result<PriorityUpdate> {
174+
// The spec caps the comment at 2000 chars, so progenitor wraps it in a
175+
// validating newtype — reject an over-long comment here rather than
176+
// letting the API do it in a round trip.
177+
let comment = comment
178+
.map(TryInto::try_into)
179+
.transpose()
180+
.map_err(|e| anyhow::anyhow!("Invalid comment: {e}"))?;
181+
let body = UpdatePublicBugPriorityBody { comment, priority };
182+
self.inner
183+
.update_public_bug_priority(bug_id, &body)
184+
.await
185+
.map(ResponseValue::into_inner)
186+
.map_err(api_error)
187+
}
188+
150189
pub async fn list_scans(
151190
&self,
152191
repo_id: &RepoId,

0 commit comments

Comments
 (0)