// 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: "json",
    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 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 <short-text>|--comment-stdin|--comment-file <file|->] [--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 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 merge <number> [--repo owner/name] [--number N compat] [--merge|--squash|--rebase] [--delete-branch] [--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.",
      "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; full issue body is included only when requested with --json body or when --json is omitted and --full/--raw requests all fields. Comment lists always stay bounded except --raw; use gh issue comment view <commentId> --full for a single 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/update/edit/patch/comment view and gh pr list/read/view/comment view. For issue read/view commands, --full expands issue fields but keeps comment lists bounded; --raw is the explicit all-comment-body escape hatch. 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 <short-text>, --comment-stdin, or --comment-file <file|-> posts a bounded lifecycle comment before the state change and aborts the state change if the comment POST fails. --comment-stdin is the first-class heredoc path for generated Markdown closeout evidence; --comment remains the short inline form. 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 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. 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. Use --dry-run to see the exact merge 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; issue-level `--json comments --full` keeps the comment list bounded.");
    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 close" || key === "issue reopen") {
    notes.push("Issue close/reopen can post a lifecycle comment with `--comment`, `--comment-stdin`, or `--comment-file <file|->` before changing state.");
    notes.push("For long closeout evidence, prefer `--comment-stdin` with a quoted heredoc.");
  } 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 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.");
  }
  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: "json",
      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: "json",
    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",
  };
}
