Files
pikasTech-unidesk/scripts/src/gh/help.ts
T
2026-07-11 06:30:19 +02:00

217 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPEC: PJ2026-010606 GitHub入口 draft-2026-06-25-gh-split
// Moved mechanically from scripts/src/gh.ts:8158-8346.
import { positionalArgs } from "./options";
import { DEFAULT_REPO } from "./types";
export function ghHelp(): unknown {
return {
command: "gh",
output: "text",
usage: [
"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] [--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 comments <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--limit N] [--full|--raw] [bounded recent comment progress path]",
"bun scripts/cli.ts gh issue attachment list <number|url|owner/repo#number> [--repo owner/name] [--number N compat]",
"bun scripts/cli.ts gh issue attachment download <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--attachment index|assetId|url] [--output path] [--dry-run]",
"bun scripts/cli.ts gh issue create --title <title> (--body-stdin|--body-file <file|->) [--label label[,label...]]... [--repo owner/name] [--dry-run]",
"bun scripts/cli.ts gh issue update <number> --mode replace|append (--body-stdin|--body-file <file|->) [--title title] [--repo owner/name] [--number N compat] [--dry-run] [--expect-updated-at ts|--expect-body-sha sha256] [--body-profile auto|code-queue-board|commander-brief] [--allow-short-body] [--full|--raw]",
"bun scripts/cli.ts gh issue patch <number|url|owner/repo#number> --body-patch-stdin [--repo owner/name] [--number N compat] [--dry-run] [--expect-updated-at ts|--expect-body-sha sha256] [--body-profile auto|code-queue-board|commander-brief] [--allow-short-body] [--full|--raw]",
"bun scripts/cli.ts gh issue edit <number> (--body-stdin|--body-file <file|->) [--repo owner/name] [--number N compat] [--full|--raw] [compat alias for issue update --mode replace]",
"bun scripts/cli.ts gh issue edit 24 --body-stdin --notify-claudeqq-brief-diff [--dry-run]",
"bun scripts/cli.ts gh issue comment create <number> (--body-stdin|--body-file <file|->|--body <short-text>) [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh issue comment view|read <commentId> [--repo owner/name] [--number N compat] [--full|--raw]",
"bun scripts/cli.ts gh issue comment update <commentId> (--body-stdin|--body-file <file|->|--body <short-text>) [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh issue comment patch <commentId> --body-patch-stdin [--repo owner/name] [--number N compat] [--dry-run] [--expect-updated-at ts|--expect-body-sha sha256]",
"bun scripts/cli.ts gh issue comment edit <commentId> (--body-stdin|--body-file <file|->|--body <short-text>) [--repo owner/name] [--number N compat] [--dry-run] [compatibility alias for issue comment update]",
"bun scripts/cli.ts gh issue comment delete <commentId> [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh issue close|reopen <number> [--repo owner/name] [--number N compat] [--comment-stdin] [--dry-run]",
"bun scripts/cli.ts gh issue stale-close [--repo owner/name] [--inactive-hours N] [--limit N] [--label label[,label...]]... [--dry-run]",
"bun scripts/cli.ts gh issue delete <number> [unsupported: use close]",
"bun scripts/cli.ts gh issue scan-escape [--repo owner/name] [--limit N] [--dry-run]",
"bun scripts/cli.ts gh issue cleanup-plan [--repo owner/name] [--limit N] [--dry-run]",
"bun scripts/cli.ts gh issue board-audit [--repo owner/name] [--board-issue 20] [--limit N] [--known-meta-issue N[,N...]] [--ignore-issue N[,N...]] [--dry-run]",
"bun scripts/cli.ts gh issue board-row list [--repo owner/name] --board-issue 20 [--state open|closed|all] [--dry-run]",
"bun scripts/cli.ts gh issue board-row get <issueNumber> [--repo owner/name] [--number N compat] --board-issue 20",
"bun scripts/cli.ts gh issue board-row update <issueNumber> [--repo owner/name] [--number N compat] --board-issue 20 --field progress|status|validation|branch|tasks|focus --value <text> [--dry-run] [--expect-updated-at ts|--expect-body-sha sha256]",
"bun scripts/cli.ts gh issue board-row add <issueNumber> [--repo owner/name] [--number N compat] --board-issue 20 --section open|closed --row-file <markdown-row-file> [--dry-run] [--expect-body-sha sha256]",
"bun scripts/cli.ts gh issue board-row upsert <issueNumber> [--repo owner/name] [--number N compat] --board-issue 20 --section open|closed [--category text] --branch <branch> --tasks <task> --summary <text> --focus <text> --validation <text> --progress <text> [--status OPEN|CLOSED] [--dry-run] [--expect-updated-at ts|--expect-body-sha sha256]",
"bun scripts/cli.ts gh issue board-row move <issueNumber> [--repo owner/name] [--number N compat] --board-issue 20 --to open|closed [--status OPEN|CLOSED] [--dry-run] [--expect-body-sha sha256]",
"bun scripts/cli.ts gh issue board-row delete <issueNumber> [--repo owner/name] [--number N compat] --board-issue 20 [--dry-run] [--expect-body-sha sha256]",
"bun scripts/cli.ts gh preflight <prNumber|owner/repo#number> [--repo owner/name] [--number N compat] [--full|--raw] [compatibility alias for gh pr preflight]",
"bun scripts/cli.ts gh pr list [owner/repo] [--repo owner/name] [--state open|closed|all] [--limit N] [--json number,title,state,url,updatedAt,createdAt,author,head,base,draft]",
"bun scripts/cli.ts gh pr files <number> [--repo owner/name] [--number N compat] [--limit N] [number may appear before or after options]",
"bun scripts/cli.ts gh pr diff <number> --stat [--repo owner/name] [--number N compat] [--limit N] [number may appear before or after options; compatibility alias for pr files; no raw diff]",
"bun scripts/cli.ts gh pr review-plan <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--limit N] [bounded changed-file index plus per-file patch drill-down commands]",
"bun scripts/cli.ts gh pr diff <number|url|owner/repo#number> --file <path> [--hunk N] [--repo owner/name] [--number N compat] [--limit N] [--full|--raw] [bounded file/hunk patch drill-down]",
"bun scripts/cli.ts gh pr view <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--json body,title,state,stateDetail,closed,closedAt,merged,mergedAt,mergeCommit,head,base,draft,headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup] [--raw|--full]",
"bun scripts/cli.ts gh pr read <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--raw|--full] [compatibility alias for pr view]",
"bun scripts/cli.ts gh pr preflight <number|owner/repo#number> [--repo owner/name] [--number N compat] [--full|--raw] [number may appear before or after options]",
"bun scripts/cli.ts gh pr closeout <number|owner/repo#number> [--repo owner/name] [--number N compat] [--full|--raw] [number may appear before or after options; compatibility alias for pr preflight]",
"bun scripts/cli.ts gh pr create --title <title> (--body-stdin|--body-file <file|->|--body <text>) --base <branch> --head <branch> [--repo owner/name] [--draft] [--dry-run]",
"bun scripts/cli.ts gh pr edit <number> [--title title] [--body-stdin|--body-file <file|->|--body <text>] [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh pr update <number> --mode replace|append [--body-stdin|--body-file <file|->|--body <text>] [--title title] [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh pr comment create <number> (--body-stdin|--body-file <file|->|--body <text>) [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh pr comment view|read <commentId> [--repo owner/name] [--number N compat] [--full|--raw]",
"bun scripts/cli.ts gh pr comment update <commentId> (--body-stdin|--body-file <file|->|--body <text>) [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh pr comment edit <commentId> (--body-stdin|--body-file <file|->|--body <text>) [--repo owner/name] [--number N compat] [--dry-run] [compatibility alias for pr comment update]",
"bun scripts/cli.ts gh pr comment delete <commentId> [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh pr close|reopen <number> [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh pr ready <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--dry-run]",
"bun scripts/cli.ts gh pr merge <number> [--repo owner/name] [--number N compat] [--merge|--squash|--rebase] [--delete-branch|--keep-branch] [--sync-node NODE]... [--skip-local-closeout] [--dry-run]",
"bun scripts/cli.ts gh pr delete <number> [unsupported: use close]",
],
defaults: { repo: DEFAULT_REPO },
notes: [
"Issue and PR create/read/update/comment/close/reopen use GitHub REST and do not require the gh binary when GH_TOKEN or GITHUB_TOKEN is present.",
"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.",
"Default gh output is k8s-style text/table/summary with Next drill-down commands. Machine-readable or full-disclosure output must be requested explicitly with --json, --full, or --raw where supported.",
"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. --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 view/read/update/edit/delete treat --number as commentId, not an issue number. View supports lifecycle fields closed/closedAt plus legacy --json field selection; explicit --json fields limit output even with --raw/--full. When body is requested, the full issue body is emitted once at `.data.issue.body`; `.data.json.body` is omitted and compatibility.bodyPath/bodyMigrationHint disclose the stable replacement path. For recent comment progress, prefer `gh issue comments <number>`: it defaults to a bounded recent-comment table and structured output lives at `.data.comments` instead of `.data.json.comments`. `gh issue view --json comments` remains the compatibility path, now with explicit preferredCommand/migrationHint guidance when comments are requested or when large output is compacted. Use gh issue comment view <commentId> --full for one full comment body. 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.",
"--raw and --full are explicit full-disclosure aliases for gh issue list/read/view/comments/update/edit/patch/comment view, gh pr list/read/view/comment view, and gh pr diff --file. For issue read/view commands, --full expands issue fields but keeps comment lists bounded; --raw is the explicit all-comment-body escape hatch. For `gh issue comments`, both --full and --raw keep the list bounded to recent comments and include full comment bodies in structured output. Use issue comment view <commentId> --full for a single full comment body. For issue writes, default success output omits full issue.body and returns bodyChars/bodySha/bodyPreview plus readCommands; --full|--raw includes the full returned issue body only on commands that explicitly support full disclosure.",
"CLI output larger than config/unidesk-cli.yaml output.maxStdoutBytes is automatically written to /tmp/unidesk-cli-output; stdout stays bounded with outputTruncated=true, warning text, dump file metadata, and drill-down read commands.",
"issue create accepts --body-stdin or --body-file <file|-> plus repeatable --label values and comma-separated labels; inline --body is intentionally unsupported for issue creation. Dry-run prints the parsed labels and non-dry-run sends them in the GitHub REST create-issue payload.",
"--body-stdin is the first-class heredoc/stdin source for Markdown bodies. Use quoted heredoc syntax such as bun scripts/cli.ts gh issue comment create 1 --body-stdin <<'EOF' so real newlines, backticks, and tables are read as stdin bytes instead of shell arguments.",
"update defaults to --mode replace; --mode append reads the current body and appends file bytes so real newlines, backticks, and Markdown tables are preserved.",
"issue edit is a compatibility alias for issue update --mode replace.",
"issue update accepts --body-stdin or --body-file <file|->, refuses literal null, blank, and too-short bodies by default. Use --allow-short-body only for intentional short writes; #20 requires its board heading, warns when HWLAB product/user issue routing appears in favor of pikasTech/HWLAB, and still rejects commander brief update sections; commander-brief requires its stable heading on legacy #24 plus daily rolling brief issues titled YYYY-MM-DD 指挥简报(北京时间).",
"issue update dry-run reports bounded bodyPreview/bodyPreviewLines, old/new body length slots, body SHA, required heading checks, literal \\n detection, shell-pollution signals, guard/concurrency summary, wouldPatch, and readCommands without printing an unbounded full body. Non-dry-run automatically reads current issue metadata before PATCH and returns oldBodySha/updatedAt; --expect-updated-at or --expect-body-sha remain available for explicit stale-cache protection.",
"issue patch reads the current GitHub issue body, applies a Codex apply_patch envelope against virtual file issue.md from --body-patch-stdin or --body-patch-file <file|->, then runs the same issue body guard before PATCH. It returns old/new bodySha, updatedAt, patch summary, and bounded previews; context mismatch fails with redacted diagnostics and no GitHub write.",
"issue comment view/read reads one comment by commentId. Default output is compact metadata plus bodyChars/bodySha/preview; --full includes that one full comment body. issue comment create/update/edit accept --body-stdin or --body-file <file|-> for Markdown/generated content and --body only for short single-line text. Blank, multiline, shell-polluted, secret-like, and overlong inline bodies fail structurally. Use comment update/edit to correct existing wording in place; delete remains for intentional removal.",
"issue comment patch reads the current issue comment by commentId, applies a Codex apply_patch envelope against virtual file comment.md, then PATCHes only that comment. It returns comment id, old/new bodySha, updatedAt, patch summary, and redacted mismatch diagnostics without echoing the full comment body.",
"issue close/reopen default success output is compact and omits full issue.body. Optional --comment-stdin posts a bounded lifecycle comment before the state change and aborts the state change if the comment POST fails. --comment-stdin with a quoted heredoc is the only lifecycle closeout comment path; inline --comment and --comment-file are unsupported. Use gh issue view <number> --json body or --full/--raw on view when full text is needed.",
"issue stale-close is the reusable lifecycle cleanup path for policies such as closing open issues inactive for more than 48 hours. It selects open issues by GitHub updatedAt older than observedAt - --inactive-hours, treats comments and state changes as activity, filters pull requests, supports --dry-run, and returns bounded candidate/closed/failure summaries without echoing full bodies.",
"For one-shot issue writes, prefer quoted heredoc stdin: bun scripts/cli.ts gh issue update <number> --repo owner/name --body-stdin <<'EOF' ... EOF or gh issue comment create <number> --body-stdin <<'EOF' ... EOF. --body-file <file|-> remains available for reusable files and pipes.",
"For JSON request bodies in other CLI namespaces, prefer --body-file or --body-stdin over long inline shell arguments. GitHub issue/PR Markdown writes use --body-stdin for heredoc/stdin or --body-file <file|-> for reusable files.",
"issue scan-escape classifies literal \\n findings as suspected-pollution, explanatory-mention, or risk, and emits cleanupSuggestions with body/comment ids plus diff-like previews. cleanup-plan is an alias that remains dry-run/read-only.",
"issue board-audit is read-only and defaults to repo pikasTech/unidesk plus board issue #20. It reads only the board issue body, returns body size/SHA and parsed Markdown board sections, and no longer validates GitHub open/closed issue coverage against OPEN/CLOSED tables. The legacy coverage fields remain present as empty arrays/zero counts for compatibility.",
"issue board-row list/get reuse the board-audit table parser and are read-only. board-row update changes one table cell by issue number, returns old/new row, body SHA, body guard and request plan, and defaults to dry-run unless --expect-updated-at or --expect-body-sha is supplied for the guarded PATCH. Field aliases map status and validation to the 验收状态 column, tasks to 相关 Code Queue 任务, and focus to 当前关注点.",
"issue board-row upsert updates an existing row when the issue is already present, or generates a complete row in --section open|closed when missing. It returns operation=update or operation=add, defaults to dry-run, requires --expect-body-sha or --expect-updated-at before PATCH, and refuses section migration; use board-row move for OPEN/CLOSED migration.",
"issue board-row add/move/delete are row-scoped #20 table mutations. add validates a one-line --row-file against the target table column count, Issue column, and GitHub 状态 column; move refuses duplicate/ambiguous rows and can update GitHub 状态 via --status; delete removes only the matched row. All three default to dry-run and require --expect-body-sha or --expect-updated-at before PATCH. add/move/delete return old/new row, body SHA, and line/section plan details for the parsed table mutation, and the shared #20 guard warns about HWLAB product/user issue routing in favor of pikasTech/HWLAB without refusing the write.",
"issue edit 24 --notify-claudeqq-brief-diff remains the legacy #24 notification helper: it reads the old issue body, PATCHes the new body, and sends only newly added '## 更新 ... 北京时间' sections to ClaudeQQ; ClaudeQQ failure does not roll back GitHub.",
"Commander brief ClaudeQQ defaults to private target 645275593 through backend-core /api/microservices/claudeqq/proxy; UNIDESK_COMMANDER_BRIEF_CLAUDEQQ_* env vars can override target, base URL, timeout, and enabled state.",
"comment update/edit PATCHes /repos/{owner}/{repo}/issues/comments/{comment_id} and preserves the comment id/timeline; comment delete is supported because GitHub supports deleting issue comments, but routine wording fixes should use update/edit. issue/pr hard delete is unsupported and close is the lifecycle alternative.",
"PR files is the canonical compact changed-file/stat summary. It uses GitHub REST, returns bounded file rows, additions/deletions/changes when available, truncation metadata, and a next command for full details. Raw diff patches are not emitted by default; gh pr diff <number> --stat is a compatibility alias for the same JSON summary.",
"PR review-plan is the review-first bounded patch index. It uses GitHub REST PR files, prints changed files with additions/deletions/hunk counts/patch-line counts/default truncation flags, and emits one per-file gh pr diff --file drill-down command without creating a local worktree.",
"PR diff --file reads one changed file patch from GitHub REST and prints only a bounded excerpt by default. Add --hunk N to inspect one hunk, --limit N to change displayed patch lines, or --full/--raw for explicit structured full-patch disclosure. --stat remains the no-patch file/stat compatibility path.",
"PR edit/update PATCHes /repos/{owner}/{repo}/pulls/{number} through REST only, never GitHub Projects Classic GraphQL/projectCards, and returns low-noise JSON with repo, PR number, changedFields, url, and body size/SHA metadata instead of echoing the full body.",
"PR view is the canonical GitHub CLI-compatible read path; read remains a UniDesk compatibility alias. PR view/read accept positional numbers, GitHub PR URLs, and owner/repo#number shorthand, deriving --repo unless an explicit conflicting --repo is supplied. --number is accepted on single PR/comment numeric target commands for low-friction compatibility and returns a standard syntax hint; list/create do not accept it. PR comment update/edit/delete treat --number as commentId, not a PR number. PR view/read supports REST closeout fields stateDetail, closed, closedAt, merged, mergedAt, mergeCommit, headRefName, and baseRefName; explicit --json fields limit output even with --raw/--full. When body is requested, the full PR body is emitted once at `.data.pullRequest.body`; `.data.json.body` is omitted and compatibility.bodyPath/bodyMigrationHint disclose the stable replacement path. Mergeable, mergeStateStatus, and statusCheckRollup are fetched through GitHub GraphQL only when requested or when --json is omitted and --raw/--full requests full disclosure, and closeoutMetadata makes GraphQL errors plus UNKNOWN/null metadata explicit.",
"PR preflight/closeout accept the same owner/repo#number shorthand as PR view/read so merge readiness checks do not require repeating --repo after a PR URL has already been normalized.",
"PR list does not fetch mergeability or statusCheckRollup; request those closeout fields with gh pr view <number> --json headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup.",
"PR preflight is a low-noise read-only closeout helper for explicit diagnosis only. It combines redacted auth capability, PR branch/state metadata, mergeability, mergeStateStatus, compact status check counts, and an explicit read-only policy. It is not a required step before gh pr merge. Use --full or --raw to include all fetched status contexts.",
"PR merge is the one-command guarded write path: it reads closeout metadata itself, retries GitHub UNKNOWN/null mergeability with YAML-configured exponential backoff, refuses non-open/draft/conflicting/non-clean/failed/pending PRs, then uses GitHub REST merge. It defaults to deleting the merged same-repo head branch, cleaning the matching local .worktree when clean, and fast-forwarding the local main worktree on the PR base branch; --sync-node NODE additionally runs mapped node source-workspace sync. Use --dry-run to see the exact merge and closeout plan without writing.",
],
};
}
export function isGhHelpRequest(args: string[]): boolean {
if (args.length === 0) return true;
if (args[0] === "help" || args[0] === "--help" || args[0] === "-h") return true;
if (args.includes("--help") || args.includes("-h")) return true;
const cleaned = args.filter((arg) => arg !== "--help" && arg !== "-h");
return positionalArgs(cleaned).includes("help");
}
export function ghHelpCommandTokens(args: string[]): string[] {
const cleaned = args.filter((arg) => arg !== "--help" && arg !== "-h");
const positionals = positionalArgs(cleaned);
if (positionals[0] === "help") positionals.shift();
if (positionals[positionals.length - 1] === "help") positionals.pop();
return positionals;
}
export function ghHelpUsageLines(): string[] {
const help = ghHelp() as { usage?: unknown };
if (!Array.isArray(help.usage)) return [];
return help.usage.filter((line): line is string => typeof line === "string");
}
export function ghUsageCommandTokens(usageLine: string): string[] {
const prefix = "bun scripts/cli.ts gh ";
if (!usageLine.startsWith(prefix)) return [];
const tokens: string[] = [];
for (const token of usageLine.slice(prefix.length).split(/\s+/u)) {
if (token.length === 0) continue;
if (token.startsWith("[") || token.startsWith("<") || token.startsWith("--")) break;
tokens.push(token);
}
return tokens;
}
export function ghUsageMatchesCommandTokens(usageLine: string, requestedTokens: string[]): boolean {
const usageTokens = ghUsageCommandTokens(usageLine);
if (requestedTokens.length === 0 || requestedTokens.length > usageTokens.length) return false;
return requestedTokens.every((requested, index) => usageTokens[index]?.split("|").includes(requested) === true);
}
export function ghScopedHelpNotes(tokens: string[]): string[] {
const key = tokens.join(" ");
const notes = [
"Scoped help is bounded to the requested command or command group; use `bun scripts/cli.ts gh --help` for the full top-level command index.",
];
if (key === "issue comment" || key.startsWith("issue comment ")) {
notes.push("Issue comments use `--body-stdin` or `--body-file <file|->` for Markdown bodies; inline `--body` is only for short single-line comments.");
notes.push("Use `issue comment view <commentId> --full` to read one full comment body; use `issue comments <number>` for the bounded recent-comment issue view.");
notes.push("Use `issue comment update/edit` for wording fixes, `issue comment patch` for apply_patch-style local edits, and `issue comment delete` only for intentional removal.");
} else if (key === "issue comments") {
notes.push("Use `issue comments <number>` as the low-friction recent-progress path; default output is bounded and structured output is at `.data.comments`.");
notes.push("`--full` or `--raw` includes full bodies for the bounded recent list without changing the stable top-level `comments` path.");
} else if (key === "issue close" || key === "issue reopen") {
notes.push("Issue close/reopen can post a lifecycle comment with `--comment-stdin` before changing state.");
notes.push("Use `--comment-stdin` with a quoted heredoc for closeout evidence; inline `--comment` and `--comment-file` are unsupported.");
} else if (key === "pr comment" || key.startsWith("pr comment ")) {
notes.push("PR comments are GitHub issue comments under the hood; use comment id targets for update/edit/delete.");
notes.push("Use `pr comment view <commentId> --full` to read one full comment body by id.");
} else if (key === "pr ready") {
notes.push("PR ready 先读取目标 PR 并确认其为 opendraft PR 通过 GitHub GraphQL markPullRequestReadyForReview 转为 ready,已 ready 时幂等成功。");
notes.push("使用 `--dry-run` 只读检查当前 draft 状态和 mutation 计划,不改变 GitHub 状态。");
} else if (key === "pr merge") {
notes.push("PR merge is one-command guarded: it performs the readiness check itself; `gh pr preflight` is optional read-only diagnosis, not a required first step.");
notes.push("When GitHub reports mergeability as UNKNOWN/null, merge automatically retries with YAML-configured exponential backoff and shows retry attempts as N/M.");
notes.push("After merge it deletes the same-repo head branch by default, removes a clean matching local `.worktree`, and fast-forwards the local main worktree; use `--keep-branch` or `--skip-local-closeout` only when intentionally preserving state.");
} else if (key === "pr review-plan" || key === "pr diff") {
notes.push("Use `pr review-plan` first for a bounded changed-file index with per-file drill-down commands.");
notes.push("Use `pr diff <number> --file <path> [--hunk N]` for bounded patch review; full patch disclosure requires explicit --full or --raw.");
}
return notes;
}
export function ghScopedHelp(args: string[]): unknown | null {
if (!isGhHelpRequest(args)) return null;
const requestedTokens = ghHelpCommandTokens(args);
if (requestedTokens.length === 0) return ghHelp();
const usageLines = ghHelpUsageLines();
for (let length = requestedTokens.length; length >= 1; length -= 1) {
const scopedTokens = requestedTokens.slice(0, length);
const usage = usageLines.filter((line) => ghUsageMatchesCommandTokens(line, scopedTokens));
if (usage.length === 0) continue;
return {
command: `gh ${scopedTokens.join(" ")}`,
output: "text",
scoped: true,
requestedCommand: requestedTokens.join(" "),
matchedCommand: scopedTokens.join(" "),
usage,
notes: ghScopedHelpNotes(scopedTokens),
fullHelpCommand: "bun scripts/cli.ts gh --help",
};
}
return {
command: `gh ${requestedTokens.join(" ")}`,
output: "text",
scoped: true,
requestedCommand: requestedTokens.join(" "),
usage: [],
message: "No scoped help entry matched this gh command; use the top-level help for the full command index.",
fullHelpCommand: "bun scripts/cli.ts gh --help",
};
}