fix: improve feedback issue lookup help
This commit is contained in:
+36
-9
@@ -61,7 +61,7 @@ const GH_VALUE_OPTIONS = new Set([
|
||||
"--expect-updated-at", "--expect-body-sha", "--body-profile", "--label", "--field",
|
||||
"--value", "--section", "--to", "--status", "--row-file", "--category", "--branch",
|
||||
"--tasks", "--summary", "--focus", "--validation", "--progress", "--number", "--pr",
|
||||
"--search", "--inactive-hours", "--comment", "--comment-file", "--description",
|
||||
"--search", "--title-prefix", "--inactive-hours", "--comment", "--comment-file", "--description",
|
||||
"--attachment", "--output",
|
||||
]);
|
||||
const GH_FLAG_OPTIONS = new Set(["--dry-run", "--draft", "--notify-claudeqq-brief-diff", "--allow-short-body", "--body-stdin", "--body-patch-stdin", "--comment-stdin", "--raw", "--full", "--stat", "--merge", "--squash", "--rebase", "--delete-branch", "--private", "--public", "--auto-init"]);
|
||||
@@ -355,6 +355,7 @@ interface GitHubOptions {
|
||||
allowShortBody: boolean;
|
||||
labels: string[];
|
||||
search?: string;
|
||||
titlePrefix?: string;
|
||||
title?: string;
|
||||
body?: string;
|
||||
bodyFile?: string;
|
||||
@@ -932,6 +933,7 @@ function parseOptions(args: string[]): GitHubOptions {
|
||||
allowShortBody: hasFlag(args, "--allow-short-body"),
|
||||
labels: labelsOption(args),
|
||||
search: optionValue(args, "--search"),
|
||||
titlePrefix: optionValue(args, "--title-prefix")?.trim(),
|
||||
title: optionValue(args, "--title"),
|
||||
body: optionValue(args, "--body"),
|
||||
bodyFile: stdinAliasFileOption(args, "--body-file", "--body-stdin"),
|
||||
@@ -6163,7 +6165,7 @@ function shellWord(value: string): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function issueListNextCommand(repo: string, state: IssueListState, limit: number, search: string, labels: string[]): string {
|
||||
function issueListNextCommand(repo: string, state: IssueListState, limit: number, search: string, labels: string[], titlePrefix: string): string {
|
||||
const parts = [
|
||||
"bun scripts/cli.ts gh issue list",
|
||||
"--repo", shellWord(repo),
|
||||
@@ -6171,15 +6173,21 @@ function issueListNextCommand(repo: string, state: IssueListState, limit: number
|
||||
"--limit", String(Math.min(limit * 2, MAX_ISSUE_LIST_LIMIT)),
|
||||
];
|
||||
if (search.length > 0) parts.push("--search", shellWord(search));
|
||||
if (titlePrefix.length > 0) parts.push("--title-prefix", shellWord(titlePrefix));
|
||||
for (const label of labels) parts.push("--label", shellWord(label));
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
async function issueList(repo: string, token: string, state: IssueListState, limit: number, jsonFields: IssueListJsonField[] | undefined, search?: string, labels: string[] = [], noDump = false): Promise<GitHubCommandResult> {
|
||||
async function issueList(repo: string, token: string, state: IssueListState, limit: number, jsonFields: IssueListJsonField[] | undefined, search?: string, labels: string[] = [], noDump = false, titlePrefix?: string): Promise<GitHubCommandResult> {
|
||||
const normalizedSearch = String(search ?? "").trim();
|
||||
const normalizedTitlePrefix = String(titlePrefix ?? "").trim();
|
||||
if (titlePrefix !== undefined && normalizedTitlePrefix.length === 0) return validationError("issue list", repo, "--title-prefix requires a non-empty value");
|
||||
const result = await listIssues(token, repo, state, limit, normalizedSearch, labels);
|
||||
if (isGitHubError(result)) return commandError("issue list", repo, result, { state, limit, search: normalizedSearch || null, labels });
|
||||
const issues = result.items;
|
||||
if (isGitHubError(result)) return commandError("issue list", repo, result, { state, limit, search: normalizedSearch || null, titlePrefix: normalizedTitlePrefix || null, labels });
|
||||
const listedIssues = result.items;
|
||||
const issues = normalizedTitlePrefix.length > 0
|
||||
? listedIssues.filter((issue) => issue.title.startsWith(normalizedTitlePrefix))
|
||||
: listedIssues;
|
||||
const fields = jsonFields ?? ISSUE_LIST_JSON_FIELDS.slice();
|
||||
return {
|
||||
ok: true,
|
||||
@@ -6189,6 +6197,20 @@ async function issueList(repo: string, token: string, state: IssueListState, lim
|
||||
state,
|
||||
limit,
|
||||
search: normalizedSearch || null,
|
||||
titlePrefix: normalizedTitlePrefix || null,
|
||||
...(normalizedTitlePrefix.length > 0
|
||||
? {
|
||||
titleFilter: {
|
||||
field: "title",
|
||||
operator: "startsWith",
|
||||
prefix: normalizedTitlePrefix,
|
||||
scope: "bounded-listed-issues",
|
||||
inputCount: listedIssues.length,
|
||||
outputCount: issues.length,
|
||||
filteredOut: listedIssues.length - issues.length,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
labels,
|
||||
count: issues.length,
|
||||
rawCount: result.rawCount,
|
||||
@@ -6201,7 +6223,7 @@ async function issueList(repo: string, token: string, state: IssueListState, lim
|
||||
...(result.hasMore && limit < MAX_ISSUE_LIST_LIMIT
|
||||
? {
|
||||
next: {
|
||||
command: issueListNextCommand(repo, state, limit, normalizedSearch, labels),
|
||||
command: issueListNextCommand(repo, state, limit, normalizedSearch, labels, normalizedTitlePrefix),
|
||||
note: "Increase --limit to continue scanning beyond the current bounded result set.",
|
||||
},
|
||||
}
|
||||
@@ -6220,6 +6242,7 @@ async function issueList(repo: string, token: string, state: IssueListState, lim
|
||||
query: normalizedSearch
|
||||
? { q: `${normalizedSearch} repo:${repo} type:issue${state === "all" ? "" : ` state:${state}`}${labels.map((label) => ` ${githubSearchLabelQualifier(label)}`).join("")}`, per_page: GITHUB_REST_PAGE_SIZE }
|
||||
: { state, labels, per_page: GITHUB_REST_PAGE_SIZE },
|
||||
localTitlePrefixFilter: normalizedTitlePrefix || null,
|
||||
},
|
||||
note: "GitHub's issues endpoint may include pull requests; this command filters pull requests from .issues.",
|
||||
};
|
||||
@@ -7630,7 +7653,7 @@ export function ghHelp(): unknown {
|
||||
"bun scripts/cli.ts gh auth status [--repo owner/name]",
|
||||
"bun scripts/cli.ts gh repo view <owner/repo>|--repo owner/name",
|
||||
"bun scripts/cli.ts gh repo create <owner/repo>|--repo owner/name [--private|--public] [--description text] [--auto-init] [--dry-run]",
|
||||
"bun scripts/cli.ts gh issue list [owner/repo] [--state open|closed|all] [--limit N] [--search text] [--label label[,label...]]... [--repo owner/name] [--json number,title,state,closed,closedAt,url,updatedAt,createdAt,author,labels] [--raw|--full]",
|
||||
"bun scripts/cli.ts gh issue list [owner/repo] [--state open|closed|all] [--limit N] [--search text] [--title-prefix text] [--label label[,label...]]... [--repo owner/name] [--json number,title,state,closed,closedAt,url,updatedAt,createdAt,author,labels] [--raw|--full]",
|
||||
"bun scripts/cli.ts gh issue view <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--json body,title,state,closed,closedAt,comments,commentCount] [--raw|--full]",
|
||||
"bun scripts/cli.ts gh issue read <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--raw|--full] [compatibility alias for issue view]",
|
||||
"bun scripts/cli.ts gh issue attachment list <number|url|owner/repo#number> [--repo owner/name] [--number N compat]",
|
||||
@@ -7683,7 +7706,7 @@ export function ghHelp(): unknown {
|
||||
"repo view/create use GitHub REST through the same token path. repo create defaults to private repositories, preflights existing repos, supports --dry-run, and refuses duplicate creation.",
|
||||
"Token values are never printed; auth status reports only token source and presence.",
|
||||
"issue list and pr list accept a single positional owner/repo as a compatibility alias for --repo owner/name. The positional repo and --repo must match if both are supplied; non-repo positionals fail structurally instead of falling back to the default repo.",
|
||||
"issue list defaults to --state open and bounded --limit 30; it paginates GitHub REST/Search pages internally when --limit exceeds GitHub's per-page cap and discloses pagination/rawCount/hasMore so operators do not mistake a single page for the full repository. --search uses GitHub Search Issues API with repo/type/state qualifiers for low-friction dedupe lookup before creating a new issue. Supported --json fields are number,title,state,closed,closedAt,url,updatedAt,createdAt,author,labels and unknown fields fail structurally.",
|
||||
"issue list defaults to --state open and bounded --limit 30; it paginates GitHub REST/Search pages internally when --limit exceeds GitHub's per-page cap and discloses pagination/rawCount/hasMore so operators do not mistake a single page for the full repository. --search uses GitHub Search Issues API with repo/type/state qualifiers for low-friction dedupe lookup before creating a new issue. --title-prefix filters the bounded listed issues locally by exact title startsWith, useful for [FEEDBACK] dedupe, and reports titleFilter input/output counts. Supported --json fields are number,title,state,closed,closedAt,url,updatedAt,createdAt,author,labels and unknown fields fail structurally.",
|
||||
"PR list defaults to --state all for compatibility with earlier UniDesk CLI behavior; supported states are open, closed, and all.",
|
||||
"issue view is the canonical GitHub CLI-compatible read path; read remains a UniDesk compatibility alias. View/read accept positional numbers, GitHub issue URLs, and owner/repo#number shorthand, deriving --repo unless an explicit conflicting --repo is supplied. --number is accepted on single issue/comment numeric target commands for low-friction compatibility and returns a standard syntax hint; list/create/scan-escape/cleanup-plan/board-audit/board-row list do not accept it. Comment delete treats --number as commentId, not an issue number. View supports lifecycle fields closed/closedAt plus legacy --json field selection such as --json body and still exposes .data.issue.body for compatibility; unsupported fields fail structurally.",
|
||||
"issue attachment list/download scan issue body and comments for GitHub user attachment URLs (`https://github.com/user-attachments/assets/...`). list is read-only and returns bounded attachment metadata. download writes the selected attachment to --output or /tmp/unidesk-gh-attachments, returns bytes/SHA-256/content-type/path, redacts redirected signed URL query parameters, and never prints binary bytes.",
|
||||
@@ -7859,6 +7882,10 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult
|
||||
const command = [top, sub].filter((value): value is string => value !== undefined).join(" ") || "gh";
|
||||
return validationError(command, options.repo, "--search is only supported by gh issue list");
|
||||
}
|
||||
if (optionWasProvided(args, "--title-prefix") && !(top === "issue" && sub === "list")) {
|
||||
const command = [top, sub].filter((value): value is string => value !== undefined).join(" ") || "gh";
|
||||
return validationError(command, options.repo, "--title-prefix is only supported by gh issue list");
|
||||
}
|
||||
if (optionWasProvided(args, "--inactive-hours") && !(top === "issue" && sub === "stale-close")) {
|
||||
const command = [top, sub].filter((value): value is string => value !== undefined).join(" ") || "gh";
|
||||
return validationError(command, options.repo, "--inactive-hours is only supported by gh issue stale-close");
|
||||
@@ -8054,7 +8081,7 @@ export async function runGhCommand(args: string[]): Promise<GitHubCommandResult
|
||||
const missing = authRequired(options.repo, `issue ${sub ?? ""}`.trim(), probe);
|
||||
if (missing !== null || token === null) return missing ?? authRequired(options.repo, `issue ${sub ?? ""}`.trim(), { present: false, source: null, ghFallbackAttempted: true });
|
||||
|
||||
if (sub === "list") return issueList(options.repo, token, options.listState, options.limit, options.issueListJsonFields, options.search, options.labels, options.raw || options.full);
|
||||
if (sub === "list") return issueList(options.repo, token, options.listState, options.limit, options.issueListJsonFields, options.search, options.labels, options.raw || options.full, options.titlePrefix);
|
||||
if (sub === "stale-close") return issueStaleClose(options.repo, token, options);
|
||||
if (sub === "create") return issueCreate(options.repo, token, options);
|
||||
if (sub === "edit") {
|
||||
|
||||
@@ -734,6 +734,9 @@ export async function staticNamespaceHelp(args: string[]): Promise<unknown | nul
|
||||
if (top === "hwlab" && (sub === "node" || sub === "nodes") && args[2] === "test-accounts") {
|
||||
return loadHelp(async () => (await import("./hwlab-test-accounts")).hwlabTestAccountsHelp(), hwlabNodeHelpSummary());
|
||||
}
|
||||
if (top === "hwlab" && (sub === "node" || sub === "nodes") && args[2] === "web-probe") {
|
||||
return loadHelp(async () => (await import("./hwlab-node")).hwlabNodeWebProbeHelp(), hwlabNodeHelpSummary());
|
||||
}
|
||||
if (top === "hwlab" && (sub === "node" || sub === "nodes")) return loadHelp(async () => (await import("./hwlab-node")).hwlabNodeHelp(), hwlabNodeHelpSummary());
|
||||
if (top === "hwlab" && sub === "g14") return loadHelp(async () => (await import("./hwlab-g14")).hwlabG14Help(), hwlabG14HelpSummary());
|
||||
if (top === "hwlab") return loadHelp(async () => (await import("./hwlab-cd")).hwlabHelp(), hwlabHelpSummary());
|
||||
|
||||
@@ -229,7 +229,7 @@ export function hwlabNodeHelp(): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
function hwlabNodeWebProbeHelp(): Record<string, unknown> {
|
||||
export function hwlabNodeWebProbeHelp(): Record<string, unknown> {
|
||||
return {
|
||||
ok: true,
|
||||
command: "hwlab nodes web-probe",
|
||||
|
||||
Reference in New Issue
Block a user