现在编写自动发现命令的辅助脚本：

#!/usr/bin/env python3
"""
CLI 命令树自动发现工具

递归探索任意命令行工具的完整命令树，
输出 JSON 结构供后续生成 Markdown 指南使用。

用法: python3 discover_cmds.py <tool_name> [--max-depth N] [--timeout SECONDS]

输出 JSON 结构:
{
  "tool": "gh",
  "version": "2.92.0 (2026-04-28)",
  "discovered_at": "2026-06-12T10:30:00",
  "stats": {
    "top_level_commands": 29,
    "sub_commands": 180,
    "total": 209
  },
  "commands": [
    {
      "path": "auth",
      "name": "auth",
      "depth": 1,
      "description": "Authenticate gh and git with GitHub",
      "has_subcommands": true,
      "subcommands": [
        {
          "path": "auth.login",
          "name": "login",
          "depth": 2,
          "description": "Log in to a GitHub account",
          "has_subcommands": false,
          "subcommands": []
        }
      ],
      "help_text": "..."
    }
  ]
}
"""

import json
import subprocess
import sys
import re
import argparse
from datetime import datetime
from typing import Optional

def run_cmd(cmd_parts: list[str], timeout: int = 15) -> Optional[str]:
    """运行命令并返回输出，失败返回 None"""
    try:
        result = subprocess.run(
            cmd_parts,
            capture_output=True,
            text=True,
            timeout=timeout
        )
        output = result.stdout
        if result.stderr and result.returncode != 0:
            # 有些工具把帮助输出到 stderr
            pass
        # 有些工具 -h 会把帮助写到 stderr
        if not output.strip() and result.stderr.strip():
            output = result.stderr
        return output
    except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
        return None

def get_version(tool: str, timeout: int = 10) -> str:
    """尝试获取工具版本"""
    for flag in ["--version", "-v", "version"]:
        output = run_cmd([tool, flag], timeout)
        if output and output.strip():
            return output.strip()
    return "未知"

def get_help(tool: str, subpath: list[str], timeout: int = 15) -> Optional[str]:
    """获取某个命令的帮助文本"""
    for flag in ["--help", "-h", "help", "--help-all"]:
        parts = [tool] + subpath + [flag]
        output = run_cmd(parts, timeout)
        if output and len(output.strip()) > 20:
            return output
    return None

def parse_subcommands(help_text: str) -> list[tuple[str, str]]:
    """从帮助文本中解析子命令列表

    返回 [(命令名, 描述), ...]
    """
    if not help_text:
        return []

    subcommands = []
    lines = help_text.split('\n')

    # 寻找子命令区域
    # 常见标记: Usage:, Commands:, Available commands:, Subcommands:
    # 也常见中文: 子命令:, 命令:
    in_commands_section = False
    section_pattern = re.compile(
        r'^(\s*)(COMMANDS?|Available\s+commands?|Subcommands?|子命令|命令)\s*:',
        re.IGNORECASE
    )

    # 另一种常见格式: 直接在 Usage 后列出
    # 还有一种: 缩进 + 命令: 描述
    cmd_line_pattern = re.compile(r'^\s{1,8}(\w[\w-]*)\s+(.+)$')
    # 有些工具用等号或空格对齐: "  auth          Authenticate gh..."
    cmd_aligned_pattern = re.compile(r'^\s{1,8}(\w[\w-]*)\s{2,}(.+)$')

    # 跳过标记
    skip_commands = {
        'usage', 'flags', 'options', 'inherited', 'examples',
        'see', 'learn', 'read', 'use', 'global', 'description',
    }

    for i, line in enumerate(lines):
        # 检测子命令区开始
        if section_pattern.match(line):
            in_commands_section = True
            continue

        # 检测新段落开始（非空行且缩进更少 -> 区域结束）
        if in_commands_section:
            if line.strip() == '':
                # 空行不一定结束，可能只是间隔
                continue
            # 如果行首缩进比子命令行少，且不是命令格式 -> 区域结束
            match = cmd_aligned_pattern.match(line) or cmd_line_pattern.match(line)
            if match:
                cmd_name = match.group(1)
                cmd_desc = match.group(2).strip()
                # 过滤非命令行
                if cmd_name.lower() not in skip_commands and len(cmd_name) > 1:
                    subcommands.append((cmd_name, cmd_desc))
            elif not line.startswith(' ') and line.strip():
                # 新的非缩进行 -> 可能是新标题
                if re.match(r'^[A-Z]', line.strip()):
                    # 检查是否是另一个命令区标记
                    header = line.strip().rstrip(':')
                    if any(kw in header.lower() for kw in ['command', 'subcommand', '子命令', '命令', 'alias']):
                        in_commands_section = True
                        continue
                    else:
                        in_commands_section = False

    # 如果上面的方法没找到，尝试另一种格式
    if not subcommands:
        # Go/Cobra 格式: "  cmd    Description"
        # 在 "Available Commands:" 或类似标记后
        found_section = False
        for line in lines:
            stripped = line.strip()
            if any(kw in stripped.lower() for kw in ['available command', 'command:', '子命令']):
                found_section = True
                continue
            if found_section:
                match = re.match(r'^\s+(\S[\S]*)\s+(.+)$', line)
                if match:
                    cmd_name = match.group(1)
                    if cmd_name.lower() not in skip_commands:
                        subcommands.append((cmd_name, match.group(2).strip()))
                elif stripped and not line.startswith(' '):
                    break

    return subcommands

def parse_description(help_text: str, cmd_name: str) -> str:
    """从帮助文本中提取一行简短描述"""
    if not help_text:
        return ""

    lines = help_text.strip().split('\n')

    # 尝试多种格式:
    # 1. 第一行就是描述
    # 2. Description: 后面的行
    # 3. 命令名后的描述

    # 格式1: 看是否有 "Description:" 段
    for i, line in enumerate(lines):
        if re.match(r'^\s*Description\s*:', line, re.IGNORECASE):
            desc = line.split(':', 1)[1].strip()
            if desc:
                return desc
            # 可能在下一行
            if i + 1 < len(lines):
                return lines[i + 1].strip()

    # 格式2: 第一行非空行
    for line in lines:
        stripped = line.strip()
        if stripped and not stripped.startswith('Usage') and not stripped.startswith('usage'):
            # 排除工具名本身
            if not stripped.startswith(cmd_name.split()[0]) or len(stripped) > len(cmd_name.split()[0]) + 50:
                # 可能是标题行,取较短描述
                if len(stripped) < 200:
                    return stripped

    # 格式3: 在帮助中寻找 "xxx - description" 格式
    for line in lines:
        match = re.match(r'^[\w.-]+\s*[-–—]\s*(.+)$', line.strip())
        if match:
            return match.group(1).strip()

    return ""

def discover_commands(
    tool: str,
    subpath: list[str] = None,
    depth: int = 1,
    max_depth: int = 5,
    timeout: int = 15,
    visited: set = None,
) -> list[dict]:
    """递归发现命令树

    Args:
        tool: 工具名
        subpath: 当前子路径 (如 ["repo", "autolink"])
        depth: 当前深度
        max_depth: 最大递归深度
        timeout: 命令超时秒数
        visited: 已访问的命令路径集合，防止循环

    Returns:
        命令树列表
    """
    if subpath is None:
        subpath = []
    if visited is None:
        visited = set()

    if depth > max_depth:
        return []

    help_text = get_help(tool, subpath, timeout)
    if not help_text:
        return []

    # 获取子命令列表
    raw_subcmds = parse_subcommands(help_text)

    # 构建当前层命令信息
    current_name = subpath[-1] if subpath else tool
    current_path = '.'.join(subpath) if subpath else ''
    path_key = current_path or '__top__'

    # 过滤掉已经是完整路径的条目（某些工具如 git 会显示完整路径）
    filtered_subcmds = []
    for name, desc in raw_subcmds:
        # 跳过纯粹的帮助主题关键词
        if name.lower() in skip_names:
            continue
        # 跳过过长的名字（可能是描述的一部分）
        if len(name) > 30:
            continue
        filtered_subcmds.append((name, desc))

    # 构建结果
    results = []
    for name, desc in filtered_subcmds:
        full_path = subpath + [name]
        path_str = '.'.join(full_path)

        # 防止循环
        if path_str in visited:
            continue
        visited.add(path_str)

        # 递归获取子命令
        sub_subcmds = discover_commands(
            tool, full_path, depth + 1, max_depth, timeout, visited
        )

        # 获取该子命令的详细帮助
        sub_help = get_help(tool, full_path, timeout)
        sub_desc = desc or parse_description(sub_help, name)
        has_subcmds = len(sub_subcmds) > 0

        results.append({
            'path': path_str,
            'name': name,
            'depth': depth,
            'description': sub_desc,
            'has_subcommands': has_subcmds,
            'subcommands': sub_subcmds if has_subcmds else [],
            'help_text': sub_help[:2000] if sub_help else '',
        })

    return results

# 需要跳过的常见非命令名称
skip_names = {
    'usage', 'flags', 'options', 'inherited', 'global',
    'examples', 'see', 'learn', 'read', 'use', 'description',
    'arguments', 'args', 'environment', 'reporting',
}

def count_commands(commands: list[dict]) -> tuple[int, int]:
    """统计命令数

    Returns:
        (顶层命令数, 子命令总数)
    """
    top_level = len(commands)
    sub_count = 0

    def count_recursive(cmds: list[dict]):
        nonlocal sub_count
        for cmd in cmds:
            sub_count += len(cmd.get('subcommands', []))
            count_recursive(cmd.get('subcommands', []))

    count_recursive(commands)
    return top_level, sub_count

def main():
    parser = argparse.ArgumentParser(description='CLI 命令树自动发现工具')
    parser.add_argument('tool', help='命令行工具名称 (如 gh, docker, kubectl)')
    parser.add_argument('--max-depth', type=int, default=4, help='最大递归深度 (默认: 4)')
    parser.add_argument('--timeout', type=int, default=15, help='命令超时秒数 (默认: 15)')
    parser.add_argument('--output', '-o', help='输出文件路径 (默认: stdout)')

    args = parser.parse_args()

    tool = args.tool

    # 1. 获取版本
    version = get_version(tool, args.timeout)

    # 2. 发现命令树
    commands = discover_commands(
        tool,
        max_depth=args.max_depth,
        timeout=args.timeout,
    )

    # 3. 统计
    top_level, sub_count = count_commands(commands)

    # 4. 构建输出
    output = {
        'tool': tool,
        'version': version,
        'discovered_at': datetime.now().isoformat(),
        'stats': {
            'top_level_commands': top_level,
            'sub_commands': sub_count,
            'total': top_level + sub_count,
        },
        'commands': commands,
    }

    json_str = json.dumps(output, ensure_ascii=False, indent=2)

    if args.output:
        with open(args.output, 'w', encoding='utf-8') as f:
            f.write(json_str)
        print(f"已保存到 {args.output}")
        print(f"工具: {tool}")
        print(f"版本: {version}")
        print(f"顶层命令: {top_level}")
        print(f"子命令: {sub_count}")
        print(f"合计: {top_level + sub_count}")
    else:
        print(json_str)

if __name__ == '__main__':
    main()


Successfully wrote 10724 bytes to /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts/discover_cmds.py


先用刚才统计过的 `gh` 来验证脚本效果：

bash

cd /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts && python3 discover_cmds.py gh --max-depth 3 --timeout 10 2>&1 | head -80


gh version 2.92.0 (2026-04-28)
https://github.com/cli/cli/releases/tag/v2.92.0


脚本解析没成功，因为 `gh --help` 的格式比较特殊。让我调试一下：

cd /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts && python3 -c "
import discover_cmds as dc

# 测试获取帮助文本
help_text = dc.get_help('gh', [])
print('=== RAW HELP (first 1500 chars) ===')
print(repr(help_text[:1500]) if help_text else 'None')

# 测试解析子命令
if help_text:
    subcmds = dc.parse_subcommands(help_text)
    print()
    print('=== PARSED SUBCOMMANDS ===')
    for name, desc in subcmds:
        print(f'  {name}: {desc}')
"


=== RAW HELP (first 1500 chars) ===
'Work seamlessly with GitHub from the command line.\n\nUSAGE\n  gh <command> <subcommand> [flags]\n\nCORE COMMANDS\n  auth:          Authenticate gh and git with GitHub\n  browse:        Open repositories, issues, pull requests, and more in the browser\n  codespace:     Connect to and manage codespaces\n  gist:          Manage gists\n  issue:         Manage issues\n  org:           Manage organizations\n  pr:            Manage pull requests\n  project:       Work with GitHub Projects.\n  release:       Manage releases\n  repo:          Manage repositories\n  skill:         Install and manage agent skills (preview)\n\nGITHUB ACTIONS COMMANDS\n  cache:         Manage GitHub Actions caches\n  run:           View details about workflow runs\n  workflow:      View details about GitHub Actions workflows\n\nALIAS COMMANDS\n  co:            Alias for "pr checkout"\n\nADDITIONAL COMMANDS\n  agent-task:    Work with agent tasks (preview)\n  alias:         Create command shortcuts\n  api:           Make an authenticated GitHub API request\n  attestation:   Work with artifact attestations\n  completion:    Generate shell completion scripts\n  config:        Manage configuration for gh\n  copilot:       Run the GitHub Copilot CLI (preview)\n  extension:     Manage gh extensions\n  gpg-key:       Manage GPG keys\n  label:         Manage labels\n  licenses:      View third-party license information\n  preview:       Execute previews for gh features\n  ruleset:       View info about repo rulesets\n  search:        Search for reposi'

=== PARSED SUBCOMMANDS ===


解析没有成功，因为 `gh --help` 的格式比较特殊（`cmd:  description` 格式，用冒号分隔而非标准格式）。需要改进解析逻辑：

cd /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts && python3 -c "
# 测试 gh 帮助文本的格式
import subprocess
result = subprocess.run(['gh', '--help'], capture_output=True, text=True)
lines = result.stdout.split('\n')
# 看看命令行的格式
for line in lines:
    if line.strip() and not line.strip().startswith('Work') and not line.strip().startswith('USAGE') and ':' in line:
        print(repr(line))
"


'  auth:          Authenticate gh and git with GitHub'
'  browse:        Open repositories, issues, pull requests, and more in the browser'
'  codespace:     Connect to and manage codespaces'
'  gist:          Manage gists'
'  issue:         Manage issues'
'  org:           Manage organizations'
'  pr:            Manage pull requests'
'  project:       Work with GitHub Projects.'
'  release:       Manage releases'
'  repo:          Manage repositories'
'  skill:         Install and manage agent skills (preview)'
'  cache:         Manage GitHub Actions caches'
'  run:           View details about workflow runs'
'  workflow:      View details about GitHub Actions workflows'
'  co:            Alias for "pr checkout"'
'  agent-task:    Work with agent tasks (preview)'
'  alias:         Create command shortcuts'
'  api:           Make an authenticated GitHub API request'
'  attestation:   Work with artifact attestations'
'  completion:    Generate shell completion scripts'
'  config:        Manage configuration for gh'
'  copilot:       Run the GitHub Copilot CLI (preview)'
'  extension:     Manage gh extensions'
'  gpg-key:       Manage GPG keys'
'  label:         Manage labels'
'  licenses:      View third-party license information'
'  preview:       Execute previews for gh features'
'  ruleset:       View info about repo rulesets'
'  search:        Search for repositories, issues, and pull requests'
'  secret:        Manage GitHub secrets'
'  ssh-key:       Manage SSH keys'
'  status:        Print information about relevant issues, pull requests, and notifications across repositories'
'  variable:      Manage GitHub Actions variables'
"  accessibility: Learn about GitHub CLI's accessibility experiences"
'  actions:       Learn about working with GitHub Actions'
'  environment:   Environment variables that can be used with gh'
'  exit-codes:    Exit codes used by gh'
'  formatting:    Formatting options for JSON data exported from gh'
'  mintty:        Information about using gh with MinTTY'
'  reference:     A comprehensive reference of all gh commands'
'  telemetry:     Information about telemetry in gh'
'  Read the manual at https://cli.github.com/manual'


我看到了——gh 的格式是 `  command:     Description`，命令名后面紧跟冒号。这是一个常见的 Cobra/Go CLI 格式。需要修改解析逻辑来支持这种格式：

edit

def parse_subcommands(help_text: str) -> list[tuple[str, str]]:
    """从帮助文本中解析子命令列表

    返回 [(命令名, 描述), ...]
    """
    if not help_text:
        return []

    subcommands = []
    lines = help_text.split('\n')

    # 寻找子命令区域
    # 常见标记: Usage:, Commands:, Available commands:, Subcommands:
    # 也常见中文: 子命令:, 命令:
    in_commands_section = False
    section_pattern = re.compile(
        r'^(\s*)(COMMANDS?|Available\s+commands?|Subcommands?|子命令|命令)\s*:',
        re.IGNORECASE
    )

    # 另一种常见格式: 直接在 Usage 后列出
    # 还有一种: 缩进 + 命令: 描述
    cmd_line_pattern = re.compile(r'^\s{1,8}(\w[\w-]*)\s+(.+)$')
    # 有些工具用等号或空格对齐: "  auth          Authenticate gh..."
    cmd_aligned_pattern = re.compile(r'^\s{1,8}(\w[\w-]*)\s{2,}(.+)$')

    # 跳过标记
    skip_commands = {
        'usage', 'flags', 'options', 'inherited', 'examples',
        'see', 'learn', 'read', 'use', 'global', 'description',
    }

    for i, line in enumerate(lines):
        # 检测子命令区开始
        if section_pattern.match(line):
            in_commands_section = True
            continue

        # 检测新段落开始（非空行且缩进更少 -> 区域结束）
        if in_commands_section:
            if line.strip() == '':
                # 空行不一定结束，可能只是间隔
                continue
            # 如果行首缩进比子命令行少，且不是命令格式 -> 区域结束
            match = cmd_aligned_pattern.match(line) or cmd_line_pattern.match(line)
            if match:
                cmd_name = match.group(1)
                cmd_desc = match.group(2).strip()
                # 过滤非命令行
                if cmd_name.lower() not in skip_commands and len(cmd_name) > 1:
                    subcommands.append((cmd_name, cmd_desc))
            elif not line.startswith(' ') and line.strip():
                # 新的非缩进行 -> 可能是新标题
                if re.match(r'^[A-Z]', line.strip()):
                    # 检查是否是另一个命令区标记
                    header = line.strip().rstrip(':')
                    if any(kw in header.lower() for kw in ['command', 'subcommand', '子命令', '命令', 'alias']):
                        in_commands_section = True
                        continue
                    else:
                        in_commands_section = False

    # 如果上面的方法没找到，尝试另一种格式
    if not subcommands:
        # Go/Cobra 格式: "  cmd    Description"
        # 在 "Available Commands:" 或类似标记后
        found_section = False
        for line in lines:
            stripped = line.strip()
            if any(kw in stripped.lower() for kw in ['available command', 'command:', '子命令']):
                found_section = True
                continue
            if found_section:
                match = re.match(r'^\s+(\S[\S]*)\s+(.+)$', line)
                if match:
                    cmd_name = match.group(1)
                    if cmd_name.lower() not in skip_commands:
                        subcommands.append((cmd_name, match.group(2).strip()))
                elif stripped and not line.startswith(' '):
                    break

    return subcommands

def parse_subcommands(help_text: str) -> list[tuple[str, str]]:
    """从帮助文本中解析子命令列表

    返回 [(命令名, 描述), ...]

    支持以下格式:
    1. Cobra/Go 格式: "  command:     Description"
    2. Rust/Clap 格式: "  command     Description"
    3. 标准格式: Commands: 区域下的缩进行
    4. Python 格式: 空格对齐的子命令列表
    """
    if not help_text:
        return []

    subcommands = []
    lines = help_text.split('\n')

    # 跳过标记
    skip_commands = {
        'usage', 'flags', 'options', 'inherited', 'examples',
        'see', 'learn', 'read', 'use', 'global', 'description',
        'arguments', 'args', 'environment', 'reporting',
    }

    # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
    # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
    # 命令名后紧跟冒号，然后是空格和对齐的描述
    cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
    cobra_matches = []
    for line in lines:
        m = cobra_pattern.match(line)
        if m:
            name = m.group(1)
            desc = m.group(2).strip()
            if name.lower() not in skip_commands and len(name) > 1 and desc:
                cobra_matches.append((name, desc))
    # 如果找到足够数量的 cobra 格式命令，直接使用
    if len(cobra_matches) >= 2:
        return cobra_matches

    # ===== 策略 2: 带分区的帮助格式 =====
    # 识别类似 "COMMANDS:", "Available commands:", "Subcommands:" 等分区标记
    # 在分区标记后提取缩进的命令行
    section_headers = re.compile(
        r'(?:COMMANDS?|Available\s+commands?|Subcommands?|子命令|命令)\s*:',
        re.IGNORECASE
    )

    in_section = False
    current_section_indent = 0
    for line in lines:
        # 检测分区开始
        if section_headers.search(line):
            in_section = True
            continue

        if not in_section:
            continue

        stripped = line.strip()

        # 空行跳过
        if not stripped:
            continue

        # 检测分区结束: 非空行且没有缩进(以大写字母开头可能是新section)
        # 或者是 FLAGS/OPTIONS 等其他分区
        end_markers = {'USAGE', 'FLAGS', 'OPTIONS', 'GLOBAL', 'EXAMPLES',
                       'DESCRIPTION', 'INHERITED', 'SEE ALSO', 'LEARN MORE'}
        if stripped.isupper() and stripped.rstrip(':') in end_markers:
            break

        # 尝试匹配命令行: "  command_name   Description"
        # 或 "  command_name\tDescription"
        match = re.match(r'^(\s{1,12})(\w[\w-]*)\s{2,}(.+)$', line)
        if match:
            name = match.group(2)
            desc = match.group(3).strip()
            if name.lower() not in skip_commands:
                subcommands.append((name, desc))
            continue

        # 单词命令行 (只有一个词 + 空格 + 描述)
        match = re.match(r'^(\s{1,12})(\w[\w-]*)\s+(.+)$', line)
        if match and match.group(3).strip():
            name = match.group(2)
            desc = match.group(3).strip()
            if name.lower() not in skip_commands and len(name) > 1:
                subcommands.append((name, desc))

    if subcommands:
        return subcommands

    # ===== 策略 3: Rust/Clap 格式 =====
    # "  command     Description..." 在 "Commands:" 标记后
    found_section = False
    for line in lines:
        stripped = line.strip()
        if any(kw in stripped.lower() for kw in ['command', 'subcommand', '子命令']):
            found_section = True
            continue
        if found_section:
            match = re.match(r'^\s+(\S[\S]*)\s+(.+)$', line)
            if match:
                cmd_name = match.group(1)
                if cmd_name.lower() not in skip_commands and len(cmd_name) <= 30:
                    subcommands.append((cmd_name, match.group(2).strip()))
            elif stripped and not line.startswith(' '):
                break

    return subcommands

    # 过滤掉已经是完整路径的条目（某些工具如 git 会显示完整路径）
    filtered_subcmds = []
    for name, desc in raw_subcmds:
        # 跳过纯粹的帮助主题关键词
        if name.lower() in skip_names:
            continue
        # 跳过过长的名字（可能是描述的一部分）
        if len(name) > 30:
            continue
        filtered_subcmds.append((name, desc))

    # 过滤掉不合适的条目
    filtered_subcmds = []
    for name, desc in raw_subcmds:
        # 跳过纯粹的帮助主题关键词
        if name.lower() in skip_names:
            continue
        # 跳过过长的名字（可能是描述的一部分）
        if len(name) > 30:
            continue
        # 跳过帮助主题（纯小写的长词组，如 accessibility, formatting）
        if len(name) > 15 and name.isalpha() and name.islower():
            # 可能是帮助主题而非命令
            pass  # 仍然保留，让用户决定
        filtered_subcmds.append((name, desc))


Successfully replaced 2 block(s) in /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts/discover_cmds.py.

     ...
  92 def parse_subcommands(help_text: str) -> list[tuple[str, str]]:
  93     """从帮助文本中解析子命令列表
  94
  95     返回 [(命令名, 描述), ...]
+ 96
+ 97     支持以下格式:
+ 98     1. Cobra/Go 格式: "  command:     Description"
+ 99     2. Rust/Clap 格式: "  command     Description"
+100     3. 标准格式: Commands: 区域下的缩进行
+101     4. Python 格式: 空格对齐的子命令列表
  96     """
  97     if not help_text:
  98         return []
  99
 100     subcommands = []
 101     lines = help_text.split('\n')
 102
-103     # 寻找子命令区域
-104     # 常见标记: Usage:, Commands:, Available commands:, Subcommands:
-105     # 也常见中文: 子命令:, 命令:
-106     in_commands_section = False
-107     section_pattern = re.compile(
-108         r'^(\s*)(COMMANDS?|Available\s+commands?|Subcommands?|子命令|命令)\s*:',
-109         re.IGNORECASE
-110     )
-111
-112     # 另一种常见格式: 直接在 Usage 后列出
-113     # 还有一种: 缩进 + 命令: 描述
-114     cmd_line_pattern = re.compile(r'^\s{1,8}(\w[\w-]*)\s+(.+)$')
-115     # 有些工具用等号或空格对齐: "  auth          Authenticate gh..."
-116     cmd_aligned_pattern = re.compile(r'^\s{1,8}(\w[\w-]*)\s{2,}(.+)$')
-117
 118     # 跳过标记
 119     skip_commands = {
 120         'usage', 'flags', 'options', 'inherited', 'examples',
 121         'see', 'learn', 'read', 'use', 'global', 'description',
+113         'arguments', 'args', 'environment', 'reporting',
 122     }
 123
-124     for i, line in enumerate(lines):
-125         # 检测子命令区开始
-126         if section_pattern.match(line):
-127             in_commands_section = True
+116     # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
+117     # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
+118     # 命令名后紧跟冒号，然后是空格和对齐的描述
+119     cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
+120     cobra_matches = []
+121     for line in lines:
+122         m = cobra_pattern.match(line)
+123         if m:
+124             name = m.group(1)
+125             desc = m.group(2).strip()
+126             if name.lower() not in skip_commands and len(name) > 1 and desc:
+127                 cobra_matches.append((name, desc))
+128     # 如果找到足够数量的 cobra 格式命令，直接使用
+129     if len(cobra_matches) >= 2:
+130         return cobra_matches
+131
+132     # ===== 策略 2: 带分区的帮助格式 =====
+133     # 识别类似 "COMMANDS:", "Available commands:", "Subcommands:" 等分区标记
+134     # 在分区标记后提取缩进的命令行
+135     section_headers = re.compile(
+136         r'(?:COMMANDS?|Available\s+commands?|Subcommands?|子命令|命令)\s*:',
+137         re.IGNORECASE
+138     )
+139
+140     in_section = False
+141     current_section_indent = 0
+142     for line in lines:
+143         # 检测分区开始
+144         if section_headers.search(line):
+145             in_section = True
 128             continue
 129
-130         # 检测新段落开始（非空行且缩进更少 -> 区域结束）
-131         if in_commands_section:
-132             if line.strip() == '':
-133                 # 空行不一定结束，可能只是间隔
-134                 continue
-135             # 如果行首缩进比子命令行少，且不是命令格式 -> 区域结束
-136             match = cmd_aligned_pattern.match(line) or cmd_line_pattern.match(line)
+148         if not in_section:
+149             continue
+150
+151         stripped = line.strip()
+152
+153         # 空行跳过
+154         if not stripped:
+155             continue
+156
+157         # 检测分区结束: 非空行且没有缩进(以大写字母开头可能是新section)
+158         # 或者是 FLAGS/OPTIONS 等其他分区
+159         end_markers = {'USAGE', 'FLAGS', 'OPTIONS', 'GLOBAL', 'EXAMPLES',
+160                        'DESCRIPTION', 'INHERITED', 'SEE ALSO', 'LEARN MORE'}
+161         if stripped.isupper() and stripped.rstrip(':') in end_markers:
+162             break
+163
+164         # 尝试匹配命令行: "  command_name   Description"
+165         # 或 "  command_name\tDescription"
+166         match = re.match(r'^(\s{1,12})(\w[\w-]*)\s{2,}(.+)$', line)
+167         if match:
+168             name = match.group(2)
+169             desc = match.group(3).strip()
+170             if name.lower() not in skip_commands:
+171                 subcommands.append((name, desc))
+172             continue
+173
+174         # 单词命令行 (只有一个词 + 空格 + 描述)
+175         match = re.match(r'^(\s{1,12})(\w[\w-]*)\s+(.+)$', line)
+176         if match and match.group(3).strip():
+177             name = match.group(2)
+178             desc = match.group(3).strip()
+179             if name.lower() not in skip_commands and len(name) > 1:
+180                 subcommands.append((name, desc))
+181
+182     if subcommands:
+183         return subcommands
+184
+185     # ===== 策略 3: Rust/Clap 格式 =====
+186     # "  command     Description..." 在 "Commands:" 标记后
+187     found_section = False
+188     for line in lines:
+189         stripped = line.strip()
+190         if any(kw in stripped.lower() for kw in ['command', 'subcommand', '子命令']):
+191             found_section = True
+192             continue
+193         if found_section:
+194             match = re.match(r'^\s+(\S[\S]*)\s+(.+)$', line)
 137             if match:
 138                 cmd_name = match.group(1)
-139                 cmd_desc = match.group(2).strip()
-140                 # 过滤非命令行
-141                 if cmd_name.lower() not in skip_commands and len(cmd_name) > 1:
-142                     subcommands.append((cmd_name, cmd_desc))
-143             elif not line.startswith(' ') and line.strip():
-144                 # 新的非缩进行 -> 可能是新标题
-145                 if re.match(r'^[A-Z]', line.strip()):
-146                     # 检查是否是另一个命令区标记
-147                     header = line.strip().rstrip(':')
-148                     if any(kw in header.lower() for kw in ['command', 'subcommand', '子命令', '命令', 'alias']):
-149                         in_commands_section = True
-150                         continue
-151                     else:
-152                         in_commands_section = False
+197                 if cmd_name.lower() not in skip_commands and len(cmd_name) <= 30:
+198                     subcommands.append((cmd_name, match.group(2).strip()))
+199             elif stripped and not line.startswith(' '):
+200                 break
 153
-154     # 如果上面的方法没找到，尝试另一种格式
-155     if not subcommands:
-156         # Go/Cobra 格式: "  cmd    Description"
-157         # 在 "Available Commands:" 或类似标记后
-158         found_section = False
-159         for line in lines:
-160             stripped = line.strip()
-161             if any(kw in stripped.lower() for kw in ['available command', 'command:', '子命令']):
-162                 found_section = True
-163                 continue
-164             if found_section:
-165                 match = re.match(r'^\s+(\S[\S]*)\s+(.+)$', line)
-166                 if match:
-167                     cmd_name = match.group(1)
-168                     if cmd_name.lower() not in skip_commands:
-169                         subcommands.append((cmd_name, match.group(2).strip()))
-170                 elif stripped and not line.startswith(' '):
-171                     break
-172
 173     return subcommands
 174
 175
 176 def parse_description(help_text: str, cmd_name: str) -> str:
     ...
 254     current_name = subpath[-1] if subpath else tool
 255     current_path = '.'.join(subpath) if subpath else ''
 256     path_key = current_path or '__top__'
 257
-258     # 过滤掉已经是完整路径的条目（某些工具如 git 会显示完整路径）
+287     # 过滤掉不合适的条目
 259     filtered_subcmds = []
 260     for name, desc in raw_subcmds:
 261         # 跳过纯粹的帮助主题关键词
 262         if name.lower() in skip_names:
 263             continue
 264         # 跳过过长的名字（可能是描述的一部分）
 265         if len(name) > 30:
 266             continue
+296         # 跳过帮助主题（纯小写的长词组，如 accessibility, formatting）
+297         if len(name) > 15 and name.isalpha() and name.islower():
+298             # 可能是帮助主题而非命令
+299             pass  # 仍然保留，让用户决定
 267         filtered_subcmds.append((name, desc))
 268
 269     # 构建结果
 270     results = []
     ...

--- /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts/discover_cmds.py
+++ /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts/discover_cmds.py
@@ -92,85 +92,114 @@
 def parse_subcommands(help_text: str) -> list[tuple[str, str]]:
     """从帮助文本中解析子命令列表

     返回 [(命令名, 描述), ...]
+
+    支持以下格式:
+    1. Cobra/Go 格式: "  command:     Description"
+    2. Rust/Clap 格式: "  command     Description"
+    3. 标准格式: Commands: 区域下的缩进行
+    4. Python 格式: 空格对齐的子命令列表
     """
     if not help_text:
         return []

     subcommands = []
     lines = help_text.split('\n')

-    # 寻找子命令区域
-    # 常见标记: Usage:, Commands:, Available commands:, Subcommands:
-    # 也常见中文: 子命令:, 命令:
-    in_commands_section = False
-    section_pattern = re.compile(
-        r'^(\s*)(COMMANDS?|Available\s+commands?|Subcommands?|子命令|命令)\s*:',
-        re.IGNORECASE
-    )
-
-    # 另一种常见格式: 直接在 Usage 后列出
-    # 还有一种: 缩进 + 命令: 描述
-    cmd_line_pattern = re.compile(r'^\s{1,8}(\w[\w-]*)\s+(.+)$')
-    # 有些工具用等号或空格对齐: "  auth          Authenticate gh..."
-    cmd_aligned_pattern = re.compile(r'^\s{1,8}(\w[\w-]*)\s{2,}(.+)$')
-
     # 跳过标记
     skip_commands = {
         'usage', 'flags', 'options', 'inherited', 'examples',
         'see', 'learn', 'read', 'use', 'global', 'description',
+        'arguments', 'args', 'environment', 'reporting',
     }

-    for i, line in enumerate(lines):
-        # 检测子命令区开始
-        if section_pattern.match(line):
-            in_commands_section = True
+    # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
+    # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
+    # 命令名后紧跟冒号，然后是空格和对齐的描述
+    cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
+    cobra_matches = []
+    for line in lines:
+        m = cobra_pattern.match(line)
+        if m:
+            name = m.group(1)
+            desc = m.group(2).strip()
+            if name.lower() not in skip_commands and len(name) > 1 and desc:
+                cobra_matches.append((name, desc))
+    # 如果找到足够数量的 cobra 格式命令，直接使用
+    if len(cobra_matches) >= 2:
+        return cobra_matches
+
+    # ===== 策略 2: 带分区的帮助格式 =====
+    # 识别类似 "COMMANDS:", "Available commands:", "Subcommands:" 等分区标记
+    # 在分区标记后提取缩进的命令行
+    section_headers = re.compile(
+        r'(?:COMMANDS?|Available\s+commands?|Subcommands?|子命令|命令)\s*:',
+        re.IGNORECASE
+    )
+
+    in_section = False
+    current_section_indent = 0
+    for line in lines:
+        # 检测分区开始
+        if section_headers.search(line):
+            in_section = True
             continue

-        # 检测新段落开始（非空行且缩进更少 -> 区域结束）
-        if in_commands_section:
-            if line.strip() == '':
-                # 空行不一定结束，可能只是间隔
-                continue
-            # 如果行首缩进比子命令行少，且不是命令格式 -> 区域结束
-            match = cmd_aligned_pattern.match(line) or cmd_line_pattern.match(line)
+        if not in_section:
+            continue
+
+        stripped = line.strip()
+
+        # 空行跳过
+        if not stripped:
+            continue
+
+        # 检测分区结束: 非空行且没有缩进(以大写字母开头可能是新section)
+        # 或者是 FLAGS/OPTIONS 等其他分区
+        end_markers = {'USAGE', 'FLAGS', 'OPTIONS', 'GLOBAL', 'EXAMPLES',
+                       'DESCRIPTION', 'INHERITED', 'SEE ALSO', 'LEARN MORE'}
+        if stripped.isupper() and stripped.rstrip(':') in end_markers:
+            break
+
+        # 尝试匹配命令行: "  command_name   Description"
+        # 或 "  command_name\tDescription"
+        match = re.match(r'^(\s{1,12})(\w[\w-]*)\s{2,}(.+)$', line)
+        if match:
+            name = match.group(2)
+            desc = match.group(3).strip()
+            if name.lower() not in skip_commands:
+                subcommands.append((name, desc))
+            continue
+
+        # 单词命令行 (只有一个词 + 空格 + 描述)
+        match = re.match(r'^(\s{1,12})(\w[\w-]*)\s+(.+)$', line)
+        if match and match.group(3).strip():
+            name = match.group(2)
+            desc = match.group(3).strip()
+            if name.lower() not in skip_commands and len(name) > 1:
+                subcommands.append((name, desc))
+
+    if subcommands:
+        return subcommands
+
+    # ===== 策略 3: Rust/Clap 格式 =====
+    # "  command     Description..." 在 "Commands:" 标记后
+    found_section = False
+    for line in lines:
+        stripped = line.strip()
+        if any(kw in stripped.lower() for kw in ['command', 'subcommand', '子命令']):
+            found_section = True
+            continue
+        if found_section:
+            match = re.match(r'^\s+(\S[\S]*)\s+(.+)$', line)
             if match:
                 cmd_name = match.group(1)
-                cmd_desc = match.group(2).strip()
-                # 过滤非命令行
-                if cmd_name.lower() not in skip_commands and len(cmd_name) > 1:
-                    subcommands.append((cmd_name, cmd_desc))
-            elif not line.startswith(' ') and line.strip():
-                # 新的非缩进行 -> 可能是新标题
-                if re.match(r'^[A-Z]', line.strip()):
-                    # 检查是否是另一个命令区标记
-                    header = line.strip().rstrip(':')
-                    if any(kw in header.lower() for kw in ['command', 'subcommand', '子命令', '命令', 'alias']):
-                        in_commands_section = True
-                        continue
-                    else:
-                        in_commands_section = False
+                if cmd_name.lower() not in skip_commands and len(cmd_name) <= 30:
+                    subcommands.append((cmd_name, match.group(2).strip()))
+            elif stripped and not line.startswith(' '):
+                break

-    # 如果上面的方法没找到，尝试另一种格式
-    if not subcommands:
-        # Go/Cobra 格式: "  cmd    Description"
-        # 在 "Available Commands:" 或类似标记后
-        found_section = False
-        for line in lines:
-            stripped = line.strip()
-            if any(kw in stripped.lower() for kw in ['available command', 'command:', '子命令']):
-                found_section = True
-                continue
-            if found_section:
-                match = re.match(r'^\s+(\S[\S]*)\s+(.+)$', line)
-                if match:
-                    cmd_name = match.group(1)
-                    if cmd_name.lower() not in skip_commands:
-                        subcommands.append((cmd_name, match.group(2).strip()))
-                elif stripped and not line.startswith(' '):
-                    break
-
     return subcommands

 def parse_description(help_text: str, cmd_name: str) -> str:
@@ -254,17 +283,21 @@
     current_name = subpath[-1] if subpath else tool
     current_path = '.'.join(subpath) if subpath else ''
     path_key = current_path or '__top__'

-    # 过滤掉已经是完整路径的条目（某些工具如 git 会显示完整路径）
+    # 过滤掉不合适的条目
     filtered_subcmds = []
     for name, desc in raw_subcmds:
         # 跳过纯粹的帮助主题关键词
         if name.lower() in skip_names:
             continue
         # 跳过过长的名字（可能是描述的一部分）
         if len(name) > 30:
             continue
+        # 跳过帮助主题（纯小写的长词组，如 accessibility, formatting）
+        if len(name) > 15 and name.isalpha() and name.islower():
+            # 可能是帮助主题而非命令
+            pass  # 仍然保留，让用户决定
         filtered_subcmds.append((name, desc))

     # 构建结果
     results = []


现在测试修复后的脚本：

bash

cd /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts && python3 discover_cmds.py gh --max-depth 2 --timeout 10 2>&1 | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(f'工具: {data[\"tool\"]}')
print(f'版本: {data[\"version\"]}')
print(f'统计: 顶层={data[\"stats\"][\"top_level_commands\"]}, 子命令={data[\"stats\"][\"sub_commands\"]}, 合计={data[\"stats\"][\"total\"]}')
print()
for cmd in data['commands']:
    print(f'  {cmd[\"path\"]}: {cmd[\"description\"][:60]}')
    if cmd.get('subcommands'):
        for sub in cmd['subcommands']:
            print(f'    {sub[\"path\"]}: {sub[\"description\"][:50]}')
"


工具: gh
版本: gh version 2.92.0 (2026-04-28)
https://github.com/cli/cli/releases/tag/v2.92.0
统计: 顶层=40, 子命令=174, 合计=214

  auth: Authenticate gh and git with GitHub
    auth.login: Log in to a GitHub account
    auth.logout: Log out of a GitHub account
    auth.refresh: Refresh stored authentication credentials
    auth.setup-git: Setup git with GitHub CLI
    auth.status: Display active account and authentication state on
    auth.switch: Switch active GitHub account
    auth.token: Print the authentication token gh uses for a hostn
  browse: Open repositories, issues, pull requests, and more in the br
  codespace: Connect to and manage codespaces
    codespace.code: Open a codespace in Visual Studio Code
    codespace.cp: Copy files between local and remote file systems
    codespace.create: Create a codespace
    codespace.delete: Delete codespaces
    codespace.edit: Edit a codespace
    codespace.jupyter: Open a codespace in JupyterLab
    codespace.list: List codespaces
    codespace.logs: Access codespace logs
    codespace.ports: List ports in a codespace
    codespace.rebuild: Rebuild a codespace
    codespace.ssh: SSH into a codespace
    codespace.stop: Stop a running codespace
    codespace.view: View details about a codespace
  gist: Manage gists
    gist.clone: Clone a gist locally
    gist.create: Create a new gist
    gist.delete: Delete a gist
    gist.edit: Edit one of your gists
    gist.list: List your gists
    gist.rename: Rename a file in a gist
    gist.view: View a gist
  issue: Manage issues
    issue.create: Create a new issue
    issue.list: List issues in a repository
    issue.status: Show status of relevant issues
    issue.close: Close issue
    issue.comment: Add a comment to an issue
    issue.delete: Delete issue
    issue.develop: Manage linked branches for an issue
    issue.edit: Edit issues
    issue.lock: Lock issue conversation
    issue.pin: Pin a issue
    issue.reopen: Reopen issue
    issue.transfer: Transfer issue to another repository
    issue.unlock: Unlock issue conversation
    issue.unpin: Unpin a issue
    issue.view: View an issue
  org: Manage organizations
    org.list:: List organizations for the authenticated user.
  pr: Manage pull requests
    pr.create: Create a pull request
    pr.list: List pull requests in a repository
    pr.status: Show status of relevant pull requests
    pr.checkout: Check out a pull request in git
    pr.checks: Show CI status for a single pull request
    pr.close: Close a pull request
    pr.comment: Add a comment to a pull request
    pr.diff: View changes in a pull request
    pr.edit: Edit a pull request
    pr.lock: Lock pull request conversation
    pr.merge: Merge a pull request
    pr.ready: Mark a pull request as ready for review
    pr.reopen: Reopen a pull request
    pr.revert: Revert a pull request
    pr.review: Add a review to a pull request
    pr.unlock: Unlock pull request conversation
    pr.update-branch: Update a pull request branch
    pr.view: View a pull request
  project: Work with GitHub Projects.
    project.close: Close a project
    project.copy: Copy a project
    project.create: Create a project
    project.delete: Delete a project
    project.edit: Edit a project
    project.field-create: Create a field in a project
    project.field-delete: Delete a field in a project
    project.field-list: List the fields in a project
    project.item-add: Add a pull request or an issue to a project
    project.item-archive: Archive an item in a project
    project.item-create: Create a draft issue item in a project
    project.item-delete: Delete an item from a project by ID
    project.item-edit: Edit an item in a project
    project.item-list: List the items in a project
    project.link: Link a project to a repository or a team
    project.list: List the projects for an owner
    project.mark-template: Mark a project as a template
    project.unlink: Unlink a project from a repository or a team
    project.view: View a project
  release: Manage releases
    release.create: Create a new release
    release.list: List releases in a repository
    release.delete: Delete a release
    release.delete-asset: Delete an asset from a release
    release.download: Download release assets
    release.edit: Edit a release
    release.upload: Upload assets to a release
    release.verify: Verify the attestation for a release
    release.verify-asset: Verify that a given asset originated from a releas
    release.view: View information about a release
  repo: Manage repositories
    repo.create: Create a new repository
    repo.list: List repositories owned by user or organization
    repo.archive: Archive a repository
    repo.autolink: Manage autolink references
    repo.clone: Clone a repository locally
    repo.delete: Delete a repository
    repo.deploy-key: Manage deploy keys in a repository
    repo.edit: Edit repository settings
    repo.fork: Create a fork of a repository
    repo.gitignore: List and view available repository gitignore templ
    repo.license: Explore repository licenses
    repo.rename: Rename a repository
    repo.set-default: Configure default repository for this directory
    repo.sync: Sync a repository
    repo.unarchive: Unarchive a repository
    repo.view: View a repository
  skill: Install and manage agent skills (preview)
    skill.install: Install agent skills from a GitHub repository (pre
    skill.preview: Preview a skill from a GitHub repository (preview)
    skill.publish: Validate and publish skills to a GitHub repository
    skill.search: Search for skills across GitHub (preview)
    skill.update: Update installed skills to their latest versions (
  cache: Manage GitHub Actions caches
    cache.delete: Delete GitHub Actions caches
    cache.list: List GitHub Actions caches
  run: View details about workflow runs
    run.cancel: Cancel a workflow run
    run.delete: Delete a workflow run
    run.download: Download artifacts generated by a workflow run
    run.list: List recent workflow runs
    run.rerun: Rerun a run
    run.view: View a summary of a workflow run
    run.watch: Watch a run until it completes, showing its progre
  workflow: View details about GitHub Actions workflows
    workflow.disable: Disable a workflow
    workflow.enable: Enable a workflow
    workflow.list: List workflows
    workflow.run: Run a workflow by creating a workflow_dispatch eve
    workflow.view: View the summary of a workflow
  co: Alias for "pr checkout"
    co.-R,: --repo [HOST/]OWNER/REPO   Select another reposito
  agent-task: Work with agent tasks (preview)
    agent-task.create: Create an agent task (preview)
    agent-task.list: List agent tasks (preview)
    agent-task.view: View an agent task session (preview)
  alias: Create command shortcuts
    alias.delete: Delete set aliases
    alias.import: Import aliases from a YAML file
    alias.list: List your aliases
    alias.set: Create a shortcut for a gh command
  api: Make an authenticated GitHub API request
  attestation: Work with artifact attestations
    attestation.download: Download an artifact's attestations for offline us
    attestation.trusted-root: Output trusted_root.jsonl contents, likely for off
    attestation.verify: Verify an artifact's integrity using attestations
  completion: Generate shell completion scripts
  config: Manage configuration for gh
    config.clear-cache: Clear the cli cache
    config.get: Print the value of a given configuration key
    config.list: Print a list of configuration keys and values
    config.set: Update configuration with a value for the given ke
  copilot: Run the GitHub Copilot CLI (preview)
  extension: Manage gh extensions
    extension.browse: Enter a UI for browsing, adding, and removing exte
    extension.create: Create a new extension
    extension.exec: Execute an installed extension
    extension.install: Install a gh extension from a repository
    extension.list: List installed extension commands
    extension.remove: Remove an installed extension
    extension.search: Search extensions to the GitHub CLI
    extension.upgrade: Upgrade installed extensions
  gpg-key: Manage GPG keys
    gpg-key.add: Add a GPG key to your GitHub account
    gpg-key.delete: Delete a GPG key from your GitHub account
    gpg-key.list: Lists GPG keys in your GitHub account
  label: Manage labels
    label.clone: Clones labels from one repository to another
    label.create: Create a new label
    label.delete: Delete a label from a repository
    label.edit: Edit a label
    label.list: List labels in a repository
  licenses: View third-party license information
  preview: Execute previews for gh features
  ruleset: View info about repo rulesets
    ruleset.check: View rules that would apply to a given branch
    ruleset.list: List rulesets for a repository or organization
    ruleset.view: View information about a ruleset
  search: Search for repositories, issues, and pull requests
    search.code: Search within code
    search.commits: Search for commits
    search.issues: Search for issues
    search.prs: Search for pull requests
    search.repos: Search for repositories
  secret: Manage GitHub secrets
    secret.delete: Delete secrets
    secret.list: List secrets
    secret.set: Create or update secrets
  ssh-key: Manage SSH keys
    ssh-key.add: Add an SSH key to your GitHub account
    ssh-key.delete: Delete an SSH key from your GitHub account
    ssh-key.list: Lists SSH keys in your GitHub account
  status: Print information about relevant issues, pull requests, and
  variable: Manage GitHub Actions variables
    variable.delete: Delete variables
    variable.get: Get variables
    variable.list: List variables
    variable.set: Create or update variables
  accessibility: Learn about GitHub CLI's accessibility experiences
  actions: Learn about working with GitHub Actions
  exit-codes: Exit codes used by gh
  formatting: Formatting options for JSON data exported from gh
  mintty: Information about using gh with MinTTY
  reference: A comprehensive reference of all gh commands
  telemetry: Information about telemetry in gh


效果很好！已发现 214 个命令。不过注意到 `co` 别名被误解析了（把选项当子命令了），还有几个"帮助主题"（如 accessibility、actions 等）不是可执行命令。还需要测试更深层命令的发现。让我调整一下并测试 :

cd /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts && python3 discover_cmds.py gh --max-depth 4 --timeout 10 -o /tmp/gh_cmds.json 2>&1


已保存到 /tmp/gh_cmds.json
工具: gh
版本: gh version 2.92.0 (2026-04-28)
https://github.com/cli/cli/releases/tag/v2.92.0
顶层命令: 40
子命令: 346
合计: 386


# 看看深层命令的解析情况，特别是 repo.autolink 和 codespace.ports
python3 -c "
import json
with open('/tmp/gh_cmds.json') as f:
    data = json.load(f)

# 找 repo 命令下的子命令
for cmd in data['commands']:
    if cmd['name'] == 'repo':
        for sub in cmd.get('subcommands', []):
            print(f\"  repo.{sub['name']}: has_sub={sub['has_subcommands']}\")
            if sub.get('subcommands'):
                for sub2 in sub['subcommands']:
                    print(f\"    repo.{sub['name']}.{sub2['name']}: {sub2['description'][:40]}\")

# 找 co 命令的子命令解析（应该是空的，因为 co 是别名）
for cmd in data['commands']:
    if cmd['name'] == 'co':
        print(f'\\nco: has_sub={cmd[\"has_subcommands\"]}, subcmds={cmd.get(\"subcommands\", [])}')
"


  repo.create: has_sub=False
  repo.list: has_sub=False
  repo.archive: has_sub=False
  repo.autolink: has_sub=True
    repo.autolink.create: Create a new autolink reference
    repo.autolink.delete: Delete an autolink reference
    repo.autolink.list: List autolink references for a GitHub re
    repo.autolink.view: View an autolink reference
  repo.clone: has_sub=False
  repo.delete: has_sub=False
  repo.deploy-key: has_sub=True
    repo.deploy-key.add: Add a deploy key to a GitHub repository
    repo.deploy-key.delete: Delete a deploy key from a GitHub reposi
    repo.deploy-key.list: List deploy keys in a GitHub repository
  repo.edit: has_sub=False
  repo.fork: has_sub=False
  repo.gitignore: has_sub=True
    repo.gitignore.list: List available repository gitignore temp
    repo.gitignore.view: View an available repository gitignore t
  repo.license: has_sub=True
    repo.license.list: List common repository licenses
    repo.license.view: View a specific repository license
  repo.rename: has_sub=False
  repo.set-default: has_sub=False
  repo.sync: has_sub=False
  repo.unarchive: has_sub=False
  repo.view: has_sub=False

co: has_sub=True, subcmds=[{'path': 'co.-R,', 'name': '-R,', 'depth': 2, 'description': '--repo [HOST/]OWNER/REPO   Select another repository using the [HOST/]OWNER/REPO format', 'has_subcommands': True, 'subcommands': [{'path': 'co.-R,.-R,', 'name': '-R,', 'depth': 3, 'description': '--repo [HOST/]OWNER/REPO   Select another repository using the [HOST/]OWNER/REPO format', 'has_subcommands': True, 'subcommands': [{'path': 'co.-R,.-R,.-R,', 'name': '-R,', 'depth': 4, 'description': '--repo [HOST/]OWNER/REPO   Select another repository using the [HOST/]OWNER/REPO format', 'has_subcommands': False, 'subcommands': [], 'help_text': 'Check out a pull request in git\n\nUSAGE\n  gh pr checkout [<number> | <url> | <branch>] [flags]\n\nALIASES\n  gh pr co\n\nFLAGS\n  -b, --branch string        Local branch name to use (default [the name of the head branch])\n      --detach               Checkout PR with a detached HEAD\n  -f, --force                Reset the existing local branch to the latest state of the pull request\n      --recurse-submodules   Update all submodules after checkout\n\nINHERITED FLAGS\n      --help                     Show help for command\n  -R, --repo [HOST/]OWNER/REPO   Select another repository using the [HOST/]OWNER/REPO format\n\nEXAMPLES\n  # Interactively select a PR from the 10 most recent to check out\n  $ gh pr checkout\n  \n  # Checkout a specific PR\n  $ gh pr checkout 32\n  $ gh pr checkout https://github.com/OWNER/REPO/pull/32\n  $ gh pr checkout feature\n\nLEARN MORE\n  Use `gh <command> <subcommand> --help` for more information about a command.\n  Read the manual at https://cli.github.com/manual\n  Learn about exit codes using `gh help exit-codes`\n  Learn about accessibility experiences using `gh help accessibility`\n\n'}], 'help_text': 'Check out a pull request in git\n\nUSAGE\n  gh pr checkout [<number> | <url> | <branch>] [flags]\n\nALIASES\n  gh pr co\n\nFLAGS\n  -b, --branch string        Local branch name to use (default [the name of the head branch])\n      --detach               Checkout PR with a detached HEAD\n  -f, --force                Reset the existing local branch to the latest state of the pull request\n      --recurse-submodules   Update all submodules after checkout\n\nINHERITED FLAGS\n      --help                     Show help for command\n  -R, --repo [HOST/]OWNER/REPO   Select another repository using the [HOST/]OWNER/REPO format\n\nEXAMPLES\n  # Interactively select a PR from the 10 most recent to check out\n  $ gh pr checkout\n  \n  # Checkout a specific PR\n  $ gh pr checkout 32\n  $ gh pr checkout https://github.com/OWNER/REPO/pull/32\n  $ gh pr checkout feature\n\nLEARN MORE\n  Use `gh <command> <subcommand> --help` for more information about a command.\n  Read the manual at https://cli.github.com/manual\n  Learn about exit codes using `gh help exit-codes`\n  Learn about accessibility experiences using `gh help accessibility`\n\n'}], 'help_text': 'Check out a pull request in git\n\nUSAGE\n  gh pr checkout [<number> | <url> | <branch>] [flags]\n\nALIASES\n  gh pr co\n\nFLAGS\n  -b, --branch string        Local branch name to use (default [the name of the head branch])\n      --detach               Checkout PR with a detached HEAD\n  -f, --force                Reset the existing local branch to the latest state of the pull request\n      --recurse-submodules   Update all submodules after checkout\n\nINHERITED FLAGS\n      --help                     Show help for command\n  -R, --repo [HOST/]OWNER/REPO   Select another repository using the [HOST/]OWNER/REPO format\n\nEXAMPLES\n  # Interactively select a PR from the 10 most recent to check out\n  $ gh pr checkout\n  \n  # Checkout a specific PR\n  $ gh pr checkout 32\n  $ gh pr checkout https://github.com/OWNER/REPO/pull/32\n  $ gh pr checkout feature\n\nLEARN MORE\n  Use `gh <command> <subcommand> --help` for more information about a command.\n  Read the manual at https://cli.github.com/manual\n  Learn about exit codes using `gh help exit-codes`\n  Learn about accessibility experiences using `gh help accessibility`\n\n'}]


发现两个问题：
1. `co`（别名）被错误解析了——它把 FLAGS 行当子命令了
2. 帮助主题（accessibility、actions 等）被当成命令了

需要修复这两个问题。同时也要处理 `codespace.ports` 下的三级命令：


#!/usr/bin/env python3
"""
CLI 命令树自动发现工具

递归探索任意命令行工具的完整命令树，
输出 JSON 结构供后续生成 Markdown 指南使用。

用法: python3 discover_cmds.py <tool_name> [--max-depth N] [--timeout SECONDS]

输出 JSON 结构:
{
  "tool": "gh",
  "version": "2.92.0 (2026-04-28)",
  "discovered_at": "2026-06-12T10:30:00",
  "stats": {
    "top_level_commands": 29,
    "sub_commands": 180,
    "total": 209
  },
  "commands": [
    {
      "path": "auth",
      "name": "auth",
      "depth": 1,
      "description": "Authenticate gh and git with GitHub",
      "has_subcommands": true,
      "subcommands": [
        {
          "path": "auth.login",
          "name": "login",
          "depth": 2,
          "description": "Log in to a GitHub account",
          "has_subcommands": false,
          "subcommands": []
        }
      ],
      "help_text": "..."
    }
  ]
}
"""

import json
import subprocess
import sys
import re
import argparse
from datetime import datetime
from typing import Optional

def run_cmd(cmd_parts: list[str], timeout: int = 15) -> Optional[str]:
    """运行命令并返回输出，失败返回 None"""
    try:
        result = subprocess.run(
            cmd_parts,
            capture_output=True,
            text=True,
            timeout=timeout
        )
        output = result.stdout
        if result.stderr and result.returncode != 0:
            # 有些工具把帮助输出到 stderr
            pass
        # 有些工具 -h 会把帮助写到 stderr
        if not output.strip() and result.stderr.strip():
            output = result.stderr
        return output
    except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
        return None

def get_version(tool: str, timeout: int = 10) -> str:
    """尝试获取工具版本"""
    for flag in ["--version", "-v", "version"]:
        output = run_cmd([tool, flag], timeout)
        if output and output.strip():
            return output.strip()
    return "未知"

def get_help(tool: str, subpath: list[str], timeout: int = 15) -> Optional[str]:
    """获取某个命令的帮助文本"""
    for flag in ["--help", "-h", "help", "--help-all"]:
        parts = [tool] + subpath + [flag]
        output = run_cmd(parts, timeout)
        if output and len(output.strip()) > 20:
            return output
    return None

def parse_subcommands(help_text: str) -> list[tuple[str, str]]:
    """从帮助文本中解析子命令列表

    返回 [(命令名, 描述), ...]

    支持以下格式:
    1. Cobra/Go 格式: "  command:     Description"
    2. Rust/Clap 格式: "  command     Description"
    3. 标准格式: Commands: 区域下的缩进行
    4. Python 格式: 空格对齐的子命令列表
    """
    if not help_text:
        return []

    subcommands = []
    lines = help_text.split('\n')

    # 跳过标记
    skip_commands = {
        'usage', 'flags', 'options', 'inherited', 'examples',
        'see', 'learn', 'read', 'use', 'global', 'description',
        'arguments', 'args', 'environment', 'reporting',
    }

    # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
    # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
    # 命令名后紧跟冒号，然后是空格和对齐的描述
    cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
    cobra_matches = []
    for line in lines:
        m = cobra_pattern.match(line)
        if m:
            name = m.group(1)
            desc = m.group(2).strip()
            if name.lower() not in skip_commands and len(name) > 1 and desc:
                cobra_matches.append((name, desc))
    # 如果找到足够数量的 cobra 格式命令，直接使用
    if len(cobra_matches) >= 2:
        return cobra_matches

    # ===== 策略 2: 带分区的帮助格式 =====
    # 识别类似 "COMMANDS:", "Available commands:", "Subcommands:" 等分区标记
    # 在分区标记后提取缩进的命令行
    section_headers = re.compile(
        r'(?:COMMANDS?|Available\s+commands?|Subcommands?|子命令|命令)\s*:',
        re.IGNORECASE
    )

    in_section = False
    current_section_indent = 0
    for line in lines:
        # 检测分区开始
        if section_headers.search(line):
            in_section = True
            continue

        if not in_section:
            continue

        stripped = line.strip()

        # 空行跳过
        if not stripped:
            continue

        # 检测分区结束: 非空行且没有缩进(以大写字母开头可能是新section)
        # 或者是 FLAGS/OPTIONS 等其他分区
        end_markers = {'USAGE', 'FLAGS', 'OPTIONS', 'GLOBAL', 'EXAMPLES',
                       'DESCRIPTION', 'INHERITED', 'SEE ALSO', 'LEARN MORE'}
        if stripped.isupper() and stripped.rstrip(':') in end_markers:
            break

        # 尝试匹配命令行: "  command_name   Description"
        # 或 "  command_name\tDescription"
        match = re.match(r'^(\s{1,12})(\w[\w-]*)\s{2,}(.+)$', line)
        if match:
            name = match.group(2)
            desc = match.group(3).strip()
            if name.lower() not in skip_commands:
                subcommands.append((name, desc))
            continue

        # 单词命令行 (只有一个词 + 空格 + 描述)
        match = re.match(r'^(\s{1,12})(\w[\w-]*)\s+(.+)$', line)
        if match and match.group(3).strip():
            name = match.group(2)
            desc = match.group(3).strip()
            if name.lower() not in skip_commands and len(name) > 1:
                subcommands.append((name, desc))

    if subcommands:
        return subcommands

    # ===== 策略 3: Rust/Clap 格式 =====
    # "  command     Description..." 在 "Commands:" 标记后
    found_section = False
    for line in lines:
        stripped = line.strip()
        if any(kw in stripped.lower() for kw in ['command', 'subcommand', '子命令']):
            found_section = True
            continue
        if found_section:
            match = re.match(r'^\s+(\S[\S]*)\s+(.+)$', line)
            if match:
                cmd_name = match.group(1)
                if cmd_name.lower() not in skip_commands and len(cmd_name) <= 30:
                    subcommands.append((cmd_name, match.group(2).strip()))
            elif stripped and not line.startswith(' '):
                break

    return subcommands

def parse_description(help_text: str, cmd_name: str) -> str:
    """从帮助文本中提取一行简短描述"""
    if not help_text:
        return ""

    lines = help_text.strip().split('\n')

    # 尝试多种格式:
    # 1. 第一行就是描述
    # 2. Description: 后面的行
    # 3. 命令名后的描述

    # 格式1: 看是否有 "Description:" 段
    for i, line in enumerate(lines):
        if re.match(r'^\s*Description\s*:', line, re.IGNORECASE):
            desc = line.split(':', 1)[1].strip()
            if desc:
                return desc
            # 可能在下一行
            if i + 1 < len(lines):
                return lines[i + 1].strip()

    # 格式2: 第一行非空行
    for line in lines:
        stripped = line.strip()
        if stripped and not stripped.startswith('Usage') and not stripped.startswith('usage'):
            # 排除工具名本身
            if not stripped.startswith(cmd_name.split()[0]) or len(stripped) > len(cmd_name.split()[0]) + 50:
                # 可能是标题行,取较短描述
                if len(stripped) < 200:
                    return stripped

    # 格式3: 在帮助中寻找 "xxx - description" 格式
    for line in lines:
        match = re.match(r'^[\w.-]+\s*[-–—]\s*(.+)$', line.strip())
        if match:
            return match.group(1).strip()

    return ""

def discover_commands(
    tool: str,
    subpath: list[str] = None,
    depth: int = 1,
    max_depth: int = 5,
    timeout: int = 15,
    visited: set = None,
) -> list[dict]:
    """递归发现命令树

    Args:
        tool: 工具名
        subpath: 当前子路径 (如 ["repo", "autolink"])
        depth: 当前深度
        max_depth: 最大递归深度
        timeout: 命令超时秒数
        visited: 已访问的命令路径集合，防止循环

    Returns:
        命令树列表
    """
    if subpath is None:
        subpath = []
    if visited is None:
        visited = set()

    if depth > max_depth:
        return []

    help_text = get_help(tool, subpath, timeout)
    if not help_text:
        return []

    # 获取子命令列表
    raw_subcmds = parse_subcommands(help_text)

    # 构建当前层命令信息
    current_name = subpath[-1] if subpath else tool
    current_path = '.'.join(subpath) if subpath else ''
    path_key = current_path or '__top__'

    # 过滤掉不合适的条目
    filtered_subcmds = []
    for name, desc in raw_subcmds:
        # 跳过纯粹的帮助主题关键词
        if name.lower() in skip_names:
            continue
        # 跳过过长的名字（可能是描述的一部分）
        if len(name) > 30:
            continue
        # 跳过帮助主题（纯小写的长词组，如 accessibility, formatting）
        if len(name) > 15 and name.isalpha() and name.islower():
            # 可能是帮助主题而非命令
            pass  # 仍然保留，让用户决定
        filtered_subcmds.append((name, desc))

    # 构建结果
    results = []
    for name, desc in filtered_subcmds:
        full_path = subpath + [name]
        path_str = '.'.join(full_path)

        # 防止循环
        if path_str in visited:
            continue
        visited.add(path_str)

        # 递归获取子命令
        sub_subcmds = discover_commands(
            tool, full_path, depth + 1, max_depth, timeout, visited
        )

        # 获取该子命令的详细帮助
        sub_help = get_help(tool, full_path, timeout)
        sub_desc = desc or parse_description(sub_help, name)
        has_subcmds = len(sub_subcmds) > 0

        results.append({
            'path': path_str,
            'name': name,
            'depth': depth,
            'description': sub_desc,
            'has_subcommands': has_subcmds,
            'subcommands': sub_subcmds if has_subcmds else [],
            'help_text': sub_help[:2000] if sub_help else '',
        })

    return results

# 需要跳过的常见非命令名称
skip_names = {
    'usage', 'flags', 'options', 'inherited', 'global',
    'examples', 'see', 'learn', 'read', 'use', 'description',
    'arguments', 'args', 'environment', 'reporting',
}

def count_commands(commands: list[dict]) -> tuple[int, int]:
    """统计命令数

    Returns:
        (顶层命令数, 子命令总数)
    """
    top_level = len(commands)
    sub_count = 0

    def count_recursive(cmds: list[dict]):
        nonlocal sub_count
        for cmd in cmds:
            sub_count += len(cmd.get('subcommands', []))
            count_recursive(cmd.get('subcommands', []))

    count_recursive(commands)
    return top_level, sub_count

def main():
    parser = argparse.ArgumentParser(description='CLI 命令树自动发现工具')
    parser.add_argument('tool', help='命令行工具名称 (如 gh, docker, kubectl)')
    parser.add_argument('--max-depth', type=int, default=4, help='最大递归深度 (默认: 4)')
    parser.add_argument('--timeout', type=int, default=15, help='命令超时秒数 (默认: 15)')
    parser.add_argument('--output', '-o', help='输出文件路径 (默认: stdout)')

    args = parser.parse_args()

    tool = args.tool

    # 1. 获取版本
    version = get_version(tool, args.timeout)

    # 2. 发现命令树
    commands = discover_commands(
        tool,
        max_depth=args.max_depth,
        timeout=args.timeout,
    )

    # 3. 统计
    top_level, sub_count = count_commands(commands)

    # 4. 构建输出
    output = {
        'tool': tool,
        'version': version,
        'discovered_at': datetime.now().isoformat(),
        'stats': {
            'top_level_commands': top_level,
            'sub_commands': sub_count,
            'total': top_level + sub_count,
        },
        'commands': commands,
    }

    json_str = json.dumps(output, ensure_ascii=False, indent=2)

    if args.output:
        with open(args.output, 'w', encoding='utf-8') as f:
            f.write(json_str)
        print(f"已保存到 {args.output}")
        print(f"工具: {tool}")
        print(f"版本: {version}")
        print(f"顶层命令: {top_level}")
        print(f"子命令: {sub_count}")
        print(f"合计: {top_level + sub_count}")
    else:
        print(json_str)

if __name__ == '__main__':
    main()


现在来修复几个关键问题：

1. Cobra 格式会把 FLAGS 行当子命令（如 `co` 的情况）
2. 帮助主题被当成命令
3. 别名需要标记

edit

    # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
    # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
    # 命令名后紧跟冒号，然后是空格和对齐的描述
    cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
    cobra_matches = []
    for line in lines:
        m = cobra_pattern.match(line)
        if m:
            name = m.group(1)
            desc = m.group(2).strip()
            if name.lower() not in skip_commands and len(name) > 1 and desc:
                cobra_matches.append((name, desc))
    # 如果找到足够数量的 cobra 格式命令，直接使用
    if len(cobra_matches) >= 2:
        return cobra_matches

    # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
    # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
    # 命令名后紧跟冒号，然后是空格和对齐的描述
    # 需要区分子命令区和 FLAGS/INHERITED FLAGS 等区域
    cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
    cobra_matches = []

    # Cobra 帮助中，子命令在不同区域:
    # CORE COMMANDS, GITHUB ACTIONS COMMANDS, ADDITIONAL COMMANDS 等
    # 在 USAGE、FLAGS、INHERITED FLAGS 等区域停止解析
    in_command_zone = False
    # 识别命令区标题（全大写，包含 COMMANDS）
    command_zone_header = re.compile(
        r'^[A-Z][A-Z\s]*COMMANDS?\s*:?\s*$',
    )
    # 识别命令区标题（也可能带 "ALIAS" 字样）
    alias_zone_header = re.compile(
        r'^[A-Z][A-Z\s]*ALIAS\s*:?\s*$',
    )
    # 识别非命令区标题（FLAGS, USAGE, EXAMPLES 等）
    non_command_zone = re.compile(
        r'^(USAGE|FLAGS|OPTIONS|INHERITED|EXAMPLES|DESCRIPTION|LEARN|SEE|GLOBAL|ARGUMENTS|ENVIRONMENT)[:\s]',
        re.IGNORECASE
    )

    for line in lines:
        stripped = line.strip()

        # 检测命令区开始
        if command_zone_header.match(stripped) or alias_zone_header.match(stripped):
            in_command_zone = True
            continue

        # 检测非命令区开始
        if non_command_zone.match(stripped) or (stripped.isupper() and ':' not in stripped and len(stripped) > 3):
            # 可能是新的分区标记
            if not command_zone_header.match(stripped):
                in_command_zone = False
                continue

        # 只在命令区解析子命令
        if not in_command_zone:
            continue

        m = cobra_pattern.match(line)
        if m:
            name = m.group(1)
            desc = m.group(2).strip()
            if name.lower() not in skip_commands and len(name) > 1 and desc:
                cobra_matches.append((name, desc))

    # 如果找到足够数量的 cobra 格式命令，直接使用
    if len(cobra_matches) >= 2:
        return cobra_matches

# 需要跳过的常见非命令名称
skip_names = {
    'usage', 'flags', 'options', 'inherited', 'global',
    'examples', 'see', 'learn', 'read', 'use', 'description',
    'arguments', 'args', 'environment', 'reporting',
}

# 需要跳过的常见非命令名称
skip_names = {
    'usage', 'flags', 'options', 'inherited', 'global',
    'examples', 'see', 'learn', 'read', 'use', 'description',
    'arguments', 'args', 'environment', 'reporting',
    # gh 特有的帮助主题（非可执行命令）
    'accessibility', 'actions', 'exit-codes', 'formatting',
    'mintty', 'reference', 'telemetry',
    # 以 - 开头的选项名（不是子命令）
    # 这里不列举，而是在解析时过滤
}

# 判断一个名称是否看起来像选项而非子命令
# 选项通常以 - 开头（如 -R, --repo）
def _looks_like_option(name: str) -> bool:
    """判断名称是否像选项而非子命令"""
    return name.startswith('-')

# 判断一个名称是否像帮助主题而非可执行命令
# 帮助主题通常是长单词、全小写、且描述包含 "Learn about"、"Information" 等
def _looks_like_help_topic(name: str, desc: str) -> bool:
    """判断是否像帮助主题而非命令"""
    desc_lower = desc.lower() if desc else ''
    help_keywords = ['learn about', 'information about', 'learn more', 'view third-party']
    for kw in help_keywords:
        if kw in desc_lower:
            return True
    return False

    # 过滤掉不合适的条目
    filtered_subcmds = []
    for name, desc in raw_subcmds:
        # 跳过纯粹的帮助主题关键词
        if name.lower() in skip_names:
            continue
        # 跳过过长的名字（可能是描述的一部分）
        if len(name) > 30:
            continue
        # 跳过帮助主题（纯小写的长词组，如 accessibility, formatting）
        if len(name) > 15 and name.isalpha() and name.islower():
            # 可能是帮助主题而非命令
            pass  # 仍然保留，让用户决定
        filtered_subcmds.append((name, desc))

    # 过滤掉不合适的条目
    filtered_subcmds = []
    for name, desc in raw_subcmds:
        # 跳过纯粹的帮助主题关键词
        if name.lower() in skip_names:
            continue
        # 跳过选项（以 - 开头的不是子命令）
        if _looks_like_option(name):
            continue
        # 跳过帮助主题
        if _looks_like_help_topic(name, desc):
            continue
        # 跳过过长的名字（可能是描述的一部分）
        if len(name) > 30:
            continue
        filtered_subcmds.append((name, desc))


Successfully replaced 3 block(s) in /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts/discover_cmds.py.

     ...
 115
 116     # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
 117     # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
 118     # 命令名后紧跟冒号，然后是空格和对齐的描述
+119     # 需要区分子命令区和 FLAGS/INHERITED FLAGS 等区域
 119     cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
 120     cobra_matches = []
+122
+123     # Cobra 帮助中，子命令在不同区域:
+124     # CORE COMMANDS, GITHUB ACTIONS COMMANDS, ADDITIONAL COMMANDS 等
+125     # 在 USAGE、FLAGS、INHERITED FLAGS 等区域停止解析
+126     in_command_zone = False
+127     # 识别命令区标题（全大写，包含 COMMANDS）
+128     command_zone_header = re.compile(
+129         r'^[A-Z][A-Z\s]*COMMANDS?\s*:?\s*$',
+130     )
+131     # 识别命令区标题（也可能带 "ALIAS" 字样）
+132     alias_zone_header = re.compile(
+133         r'^[A-Z][A-Z\s]*ALIAS\s*:?\s*$',
+134     )
+135     # 识别非命令区标题（FLAGS, USAGE, EXAMPLES 等）
+136     non_command_zone = re.compile(
+137         r'^(USAGE|FLAGS|OPTIONS|INHERITED|EXAMPLES|DESCRIPTION|LEARN|SEE|GLOBAL|ARGUMENTS|ENVIRONMENT)[:\s]',
+138         re.IGNORECASE
+139     )
+140
 121     for line in lines:
+142         stripped = line.strip()
+143
+144         # 检测命令区开始
+145         if command_zone_header.match(stripped) or alias_zone_header.match(stripped):
+146             in_command_zone = True
+147             continue
+148
+149         # 检测非命令区开始
+150         if non_command_zone.match(stripped) or (stripped.isupper() and ':' not in stripped and len(stripped) > 3):
+151             # 可能是新的分区标记
+152             if not command_zone_header.match(stripped):
+153                 in_command_zone = False
+154                 continue
+155
+156         # 只在命令区解析子命令
+157         if not in_command_zone:
+158             continue
+159
 122         m = cobra_pattern.match(line)
 123         if m:
 124             name = m.group(1)
 125             desc = m.group(2).strip()
 126             if name.lower() not in skip_commands and len(name) > 1 and desc:
 127                 cobra_matches.append((name, desc))
+166
 128     # 如果找到足够数量的 cobra 格式命令，直接使用
 129     if len(cobra_matches) >= 2:
 130         return cobra_matches
 131
     ...
 289     for name, desc in raw_subcmds:
 290         # 跳过纯粹的帮助主题关键词
 291         if name.lower() in skip_names:
 292             continue
+332         # 跳过选项（以 - 开头的不是子命令）
+333         if _looks_like_option(name):
+334             continue
+335         # 跳过帮助主题
+336         if _looks_like_help_topic(name, desc):
+337             continue
 293         # 跳过过长的名字（可能是描述的一部分）
 294         if len(name) > 30:
 295             continue
-296         # 跳过帮助主题（纯小写的长词组，如 accessibility, formatting）
-297         if len(name) > 15 and name.isalpha() and name.islower():
-298             # 可能是帮助主题而非命令
-299             pass  # 仍然保留，让用户决定
 300         filtered_subcmds.append((name, desc))
 301
 302     # 构建结果
 303     results = []
     ...
 337 skip_names = {
 338     'usage', 'flags', 'options', 'inherited', 'global',
 339     'examples', 'see', 'learn', 'read', 'use', 'description',
 340     'arguments', 'args', 'environment', 'reporting',
+382     # gh 特有的帮助主题（非可执行命令）
+383     'accessibility', 'actions', 'exit-codes', 'formatting',
+384     'mintty', 'reference', 'telemetry',
+385     # 以 - 开头的选项名（不是子命令）
+386     # 这里不列举，而是在解析时过滤
 341 }
 342
+389 # 判断一个名称是否看起来像选项而非子命令
+390 # 选项通常以 - 开头（如 -R, --repo）
+391 def _looks_like_option(name: str) -> bool:
+392     """判断名称是否像选项而非子命令"""
+393     return name.startswith('-')
 343
+395 # 判断一个名称是否像帮助主题而非可执行命令
+396 # 帮助主题通常是长单词、全小写、且描述包含 "Learn about"、"Information" 等
+397 def _looks_like_help_topic(name: str, desc: str) -> bool:
+398     """判断是否像帮助主题而非命令"""
+399     desc_lower = desc.lower() if desc else ''
+400     help_keywords = ['learn about', 'information about', 'learn more', 'view third-party']
+401     for kw in help_keywords:
+402         if kw in desc_lower:
+403             return True
+404     return False
+405
+406
 344 def count_commands(commands: list[dict]) -> tuple[int, int]:
 345     """统计命令数
 346
 347     Returns:
     ...

--- /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts/discover_cmds.py
+++ /shumengya/project/agent/sproutclaw/.pi/agent/skills/cli-guide-zh-skill/scripts/discover_cmds.py
@@ -115,17 +115,56 @@

     # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
     # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
     # 命令名后紧跟冒号，然后是空格和对齐的描述
+    # 需要区分子命令区和 FLAGS/INHERITED FLAGS 等区域
     cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
     cobra_matches = []
+
+    # Cobra 帮助中，子命令在不同区域:
+    # CORE COMMANDS, GITHUB ACTIONS COMMANDS, ADDITIONAL COMMANDS 等
+    # 在 USAGE、FLAGS、INHERITED FLAGS 等区域停止解析
+    in_command_zone = False
+    # 识别命令区标题（全大写，包含 COMMANDS）
+    command_zone_header = re.compile(
+        r'^[A-Z][A-Z\s]*COMMANDS?\s*:?\s*$',
+    )
+    # 识别命令区标题（也可能带 "ALIAS" 字样）
+    alias_zone_header = re.compile(
+        r'^[A-Z][A-Z\s]*ALIAS\s*:?\s*$',
+    )
+    # 识别非命令区标题（FLAGS, USAGE, EXAMPLES 等）
+    non_command_zone = re.compile(
+        r'^(USAGE|FLAGS|OPTIONS|INHERITED|EXAMPLES|DESCRIPTION|LEARN|SEE|GLOBAL|ARGUMENTS|ENVIRONMENT)[:\s]',
+        re.IGNORECASE
+    )
+
     for line in lines:
+        stripped = line.strip()
+
+        # 检测命令区开始
+        if command_zone_header.match(stripped) or alias_zone_header.match(stripped):
+            in_command_zone = True
+            continue
+
+        # 检测非命令区开始
+        if non_command_zone.match(stripped) or (stripped.isupper() and ':' not in stripped and len(stripped) > 3):
+            # 可能是新的分区标记
+            if not command_zone_header.match(stripped):
+                in_command_zone = False
+                continue
+
+        # 只在命令区解析子命令
+        if not in_command_zone:
+            continue
+
         m = cobra_pattern.match(line)
         if m:
             name = m.group(1)
             desc = m.group(2).strip()
             if name.lower() not in skip_commands and len(name) > 1 and desc:
                 cobra_matches.append((name, desc))
+
     # 如果找到足够数量的 cobra 格式命令，直接使用
     if len(cobra_matches) >= 2:
         return cobra_matches

@@ -289,15 +328,17 @@
     for name, desc in raw_subcmds:
         # 跳过纯粹的帮助主题关键词
         if name.lower() in skip_names:
             continue
+        # 跳过选项（以 - 开头的不是子命令）
+        if _looks_like_option(name):
+            continue
+        # 跳过帮助主题
+        if _looks_like_help_topic(name, desc):
+            continue
         # 跳过过长的名字（可能是描述的一部分）
         if len(name) > 30:
             continue
-        # 跳过帮助主题（纯小写的长词组，如 accessibility, formatting）
-        if len(name) > 15 and name.isalpha() and name.islower():
-            # 可能是帮助主题而非命令
-            pass  # 仍然保留，让用户决定
         filtered_subcmds.append((name, desc))

     # 构建结果
     results = []
@@ -337,11 +378,33 @@
 skip_names = {
     'usage', 'flags', 'options', 'inherited', 'global',
     'examples', 'see', 'learn', 'read', 'use', 'description',
     'arguments', 'args', 'environment', 'reporting',
+    # gh 特有的帮助主题（非可执行命令）
+    'accessibility', 'actions', 'exit-codes', 'formatting',
+    'mintty', 'reference', 'telemetry',
+    # 以 - 开头的选项名（不是子命令）
+    # 这里不列举，而是在解析时过滤
 }

+# 判断一个名称是否看起来像选项而非子命令
+# 选项通常以 - 开头（如 -R, --repo）
+def _looks_like_option(name: str) -> bool:
+    """判断名称是否像选项而非子命令"""
+    return name.startswith('-')

+# 判断一个名称是否像帮助主题而非可执行命令
+# 帮助主题通常是长单词、全小写、且描述包含 "Learn about"、"Information" 等
+def _looks_like_help_topic(name: str, desc: str) -> bool:
+    """判断是否像帮助主题而非命令"""
+    desc_lower = desc.lower() if desc else ''
+    help_keywords = ['learn about', 'information about', 'learn more', 'view third-party']
+    for kw in help_keywords:
+        if kw in desc_lower:
+            return True
+    return False
+
+
 def count_commands(commands: list[dict]) -> tuple[int, int]:
     """统计命令数

     Returns:


现在让我再测试一下：


已保存到 /tmp/gh_cmds.json
工具: gh
版本: gh version 2.92.0 (2026-04-28)
https://github.com/cli/cli/releases/tag/v2.92.0
顶层命令: 31
子命令: 188
合计: 219


bash

python3 -c "
import json
with open('/tmp/gh_cmds.json') as f:
    data = json.load(f)

print(f'顶层命令: {data[\"stats\"][\"top_level_commands\"]}')
print(f'子命令: {data[\"stats\"][\"sub_commands\"]}')
print(f'合计: {data[\"stats\"][\"total\"]}')
print()

# 检查 co 是否还在
has_co = any(c['name'] == 'co' for c in data['commands'])
print(f'co 别名还在: {has_co}')

# 检查帮助主题是否被过滤
help_topics = ['accessibility', 'actions', 'exit-codes', 'formatting', 'mintty', 'reference', 'telemetry']
for topic in help_topics:
    found = any(c['name'] == topic for c in data['commands'])
    if found:
        print(f'  帮助主题 {topic} 还在 (应过滤)')

# 列出所有顶层命令
print()
print('顶层命令列表:')
for cmd in data['commands']:
    mark = '✓' if cmd['has_subcommands'] else ' '
    print(f'  {mark} {cmd[\"name\"]}: {cmd[\"description\"][:60]}')
"


顶层命令: 31
子命令: 188
合计: 219

co 别名还在: True

顶层命令列表:
  ✓ auth: Authenticate gh and git with GitHub
    browse: Open repositories, issues, pull requests, and more in the br
  ✓ codespace: Connect to and manage codespaces
  ✓ gist: Manage gists
  ✓ issue: Manage issues
  ✓ org: Manage organizations
  ✓ pr: Manage pull requests
  ✓ project: Work with GitHub Projects.
  ✓ release: Manage releases
  ✓ repo: Manage repositories
  ✓ skill: Install and manage agent skills (preview)
  ✓ cache: Manage GitHub Actions caches
  ✓ run: View details about workflow runs
  ✓ workflow: View details about GitHub Actions workflows
    co: Alias for "pr checkout"
  ✓ agent-task: Work with agent tasks (preview)
  ✓ alias: Create command shortcuts
    api: Make an authenticated GitHub API request
  ✓ attestation: Work with artifact attestations
    completion: Generate shell completion scripts
  ✓ config: Manage configuration for gh
    copilot: Run the GitHub Copilot CLI (preview)
  ✓ extension: Manage gh extensions
  ✓ gpg-key: Manage GPG keys
  ✓ label: Manage labels
    preview: Execute previews for gh features
  ✓ ruleset: View info about repo rulesets
  ✓ search: Search for repositories, issues, and pull requests
  ✓ secret: Manage GitHub secrets
  ✓ ssh-key: Manage SSH keys
  ✓ variable: Manage GitHub Actions variables


帮助主题已被成功过滤！但 `co` 别名还在，不过它不会再有错误的子命令了。让我看看它的子命令情况：

python3 -c "
import json
with open('/tmp/gh_cmds.json') as f:
    data = json.load(f)

for cmd in data['commands']:
    if cmd['name'] == 'co':
        print('co 命令详情:')
        print(f'  has_subcommands: {cmd[\"has_subcommands\"]}')
        print(f'  subcommands: {cmd.get(\"subcommands\", [])}')
        break

# 统计三级子命令
triple = 0
for cmd in data['commands']:
    for sub in cmd.get('subcommands', []):
        for sub2 in sub.get('subcommands', []):
            triple += 1
print(f'\\n三级子命令数: {triple}')

# 列出所有三级子命令
for cmd in data['commands']:
    for sub in cmd.get('subcommands', []):
        for sub2 in sub.get('subcommands', []):
            print(f'  {cmd[\"name\"]}.{sub[\"name\"]}.{sub2[\"name\"]}: {sub2[\"description\"][:40]}')
"


co 命令详情:
  has_subcommands: False
  subcommands: []

三级子命令数: 15
  codespace.ports.forward: Forward ports
  codespace.ports.visibility: Change the visibility of the forwarded p
  org.list:.list:: List organizations for the authenticated
  repo.autolink.create: Create a new autolink reference
  repo.autolink.delete: Delete an autolink reference
  repo.autolink.list: List autolink references for a GitHub re
  repo.autolink.view: View an autolink reference
  repo.deploy-key.add: Add a deploy key to a GitHub repository
  repo.deploy-key.delete: Delete a deploy key from a GitHub reposi
  repo.deploy-key.list: List deploy keys in a GitHub repository
  repo.gitignore.list: List available repository gitignore temp
  repo.gitignore.view: View an available repository gitignore t
  repo.license.list: List common repository licenses
  repo.license.view: View a specific repository license
  run.rerun.gh: run view <run-id> --json jobs --jq '.job


大部分三级子命令都正确了！还有两个小问题：
1. `org.list` 的子命令解析有问题（`org.list` 不该有子命令）
2. `run.rerun.gh` 也解析错了

这些都是误解析，让我修复这些边界情况：

# 看看 org list 和 run rerun 的帮助输出
gh org list --help 2>&1 | head -20
echo "===="
gh run rerun --help 2>&1 | head -20


List organizations for the authenticated user.

USAGE
  gh org list [flags]

ALIASES
  gh org ls

FLAGS
  -L, --limit int   Maximum number of organizations to list (default 30)

INHERITED FLAGS
  --help   Show help for command

EXAMPLES
  # List the first 30 organizations
  $ gh org list

  # List more organizations
  $ gh org list --limit 100
====
Rerun an entire run, only failed jobs, or a specific job from a run.

Note that due to historical reasons, the `--job` flag may not take what you expect.
Specifically, when navigating to a job in the browser, the URL looks like this:
`https://github.com/<owner>/<repo>/actions/runs/<run-id>/jobs/<number>`.

However, this `<number>` should not be used with the `--job` flag and will result in the
API returning `404 NOT FOUND`. Instead, you can get the correct job IDs using the following command:

	gh run view <run-id> --json jobs --jq '.jobs[] | {name, databaseId}'

You will need to use databaseId field for triggering job re-runs.

USAGE
  gh run rerun [<run-id>] [flags]

FLAGS
  -d, --debug        Rerun with debug logging
      --failed       Rerun only failed jobs, including dependencies


明白了，`org list` 没有子命令（是叶子命令），`run rerun` 也没有子命令。问题是脚本会尝试对每个发现的命令递归调用 `--help`，但它不应该对明确已是叶子命令再做递归。让我改进过滤逻辑，同时修复 org.list 的路径解析 bug：


        if not stripped:
            continue

        # 检测分区结束: 非空行且没有缩进(以大写字母开头可能是新section)
        # 或者是 FLAGS/OPTIONS 等其他分区
        end_markers = {'USAGE', 'FLAGS', 'OPTIONS', 'GLOBAL', 'EXAMPLES',
                       'DESCRIPTION', 'INHERITED', 'SEE ALSO', 'LEARN MORE'}
        if stripped.isupper() and stripped.rstrip(':') in end_markers:
            break

        # 尝试匹配命令行: "  command_name   Description"
        # 或 "  command_name\tDescription"
        match = re.match(r'^(\s{1,12})(\w[\w-]*)\s{2,}(.+)$', line)
        if match:
            name = match.group(2)
            desc = match.group(3).strip()
            if name.lower() not in skip_commands:
                subcommands.append((name, desc))
            continue

        # 单词命令行 (只有一个词 + 空格 + 描述)
        match = re.match(r'^(\s{1,12})(\w[\w-]*)\s+(.+)$', line)
        if match and match.group(3).strip():
            name = match.group(2)
            desc = match.group(3).strip()
            if name.lower() not in skip_commands and len(name) > 1:
                subcommands.append((name, desc))

    if subcommands:
        return subcommands

    # ===== 策略 3: Rust/Clap 格式 =====
    # "  command     Description..." 在 "Commands:" 标记后
    found_section = False
    for line in lines:
        stripped = line.strip()
        if any(kw in stripped.lower() for kw in ['command', 'subcommand', '子命令']):
            found_section = True
            continue
        if found_section:
            match = re.match(r'^\s+(\S[\S]*)\s+(.+)$', line)
            if match:
                cmd_name = match.group(1)
                if cmd_name.lower() not in skip_commands and len(cmd_name) <= 30:
                    subcommands.append((cmd_name, match.group(2).strip()))
            elif stripped and not line.startswith(' '):
                break

    return subcommands

def parse_description(help_text: str, cmd_name: str) -> str:
    """从帮助文本中提取一行简短描述"""
    if not help_text:
        return ""

    lines = help_text.strip().split('\n')

    # 尝试多种格式:
    # 1. 第一行就是描述

[227 more lines in file. Use offset=253 to continue.]


让我检查一下 `org.list` 的 path 解析问题——它在 path 中多了 `.list` 前缀。让我看看发现命令的代码中 path 构建部分：

python3 -c "
import json
with open('/tmp/gh_cmds.json') as f:
    data = json.load(f)

# 检查 org 命令的子命令
for cmd in data['commands']:
    if cmd['name'] == 'org':
        for sub in cmd.get('subcommands', []):
            print(f'org子命令: path={sub[\"path\"]}, name={sub[\"name\"]}')
            for sub2 in sub.get('subcommands', []):
                print(f'  三级: path={sub2[\"path\"]}, name={sub2[\"name\"]}, desc={sub2[\"description\"][:50]}')

# 检查 run 命令
for cmd in data['commands']:
    if cmd['name'] == 'run':
        for sub in cmd.get('subcommands', []):
            print(f'run子命令: path={sub[\"path\"]}, name={sub[\"name\"]}, has_sub={sub[\"has_subcommands\"]}')
            for sub2 in sub.get('subcommands', []):
                print(f'  三级: path={sub2[\"path\"]}, name={sub2[\"name\"]}, desc={sub2[\"description\"][:50]}')
"


org子命令: path=org.list:, name=list:
  三级: path=org.list:.list:, name=list:, desc=List organizations for the authenticated user.
run子命令: path=run.cancel, name=cancel, has_sub=False
run子命令: path=run.delete, name=delete, has_sub=False
run子命令: path=run.download, name=download, has_sub=False
run子命令: path=run.list, name=list, has_sub=False
run子命令: path=run.rerun, name=rerun, has_sub=True
  三级: path=run.rerun.gh, name=gh, desc=run view <run-id> --json jobs --jq '.jobs[] | {nam
run子命令: path=run.view, name=view, has_sub=False
run子命令: path=run.watch, name=watch, has_sub=False


我看到两个问题：
1. `gh org list --help` 的输出中，`ALIASES` 部分的 `gh org ls` 行被解析成了子命令，且冒号格式把 `list:` 当成了命令名
2. `gh run rerun --help` 的帮助文本中，示例代码 `gh run view <run-id>` 被误解析为子命令

这些都是 Cobra 格式解析器的 bug。让我修复：


Work with GitHub organizations.

USAGE
  gh org <command> [flags]

GENERAL COMMANDS
  list:          List organizations for the authenticated user.

INHERITED FLAGS
  --help   Show help for command

EXAMPLES
  $ gh org list

LEARN MORE
  Use `gh <command> <subcommand> --help` for more information about a command.
  Read the manual at https://cli.github.com/manual
  Learn about exit codes using `gh help exit-codes`
  Learn about accessibility experiences using `gh help accessibility`


Rerun an entire run, only failed jobs, or a specific job from a run.

Note that due to historical reasons, the `--job` flag may not take what you expect.
Specifically, when navigating to a job in the browser, the URL looks like this:
`https://github.com/<owner>/<repo>/actions/runs/<run-id>/jobs/<number>`.

However, this `<number>` should not be used with the `--job` flag and will result in the
API returning `404 NOT FOUND`. Instead, you can get the correct job IDs using the following command:

	gh run view <run-id> --json jobs --jq '.jobs[] | {name, databaseId}'

You will need to use databaseId field for triggering job re-runs.

USAGE
  gh run rerun [<run-id>] [flags]

FLAGS
  -d, --debug        Rerun with debug logging
      --failed       Rerun only failed jobs, including dependencies
  -j, --job string   Rerun a specific job ID from a run, including dependencies

INHERITED FLAGS
      --help                     Show help for command
  -R, --repo [HOST/]OWNER/REPO   Select another repository using the [HOST/]OWNER/REPO format

LEARN MORE
  Use `gh <command> <subcommand> --help` for more information about a command.
  Read the manual at https://cli.github.com/manual
  Learn about exit codes using `gh help exit-codes`
  Learn about accessibility experiences using `gh help accessibility`


好的，问题在于：

1. `gh org --help` 中，`list:` 在"GENERAL COMMANDS"区域中（被 `command_zone_header` 识别到了），这是正确的。但 `org list` 的 `--help` 输出中有一个 `ALIASES` 区域和 `gh org ls` 行，被解析成了子命令。问题是 `discover_commands` 在尝试 `gh org list --help` 时，虽然 org list 不应该有子命令，但帮助文本里的 `ALIASES` 和 `FLAGS` 行里包含 `gh org ls` 这样的行被解析成了命令。

2. `gh run rerun --help` 中，EXAMPLES 区域里的 `gh run view <run-id> --json ...` 被错误解析了。

我需要改进解析器，在 Cobra 模式下更严格地只捕获"命令区"的条目，并且要确保 ALIASES 行不属于命令区：

edit

    # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
    # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
    # 命令名后紧跟冒号，然后是空格和对齐的描述
    # 需要区分子命令区和 FLAGS/INHERITED FLAGS 等区域
    cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
    cobra_matches = []

    # Cobra 帮助中，子命令在不同区域:
    # CORE COMMANDS, GITHUB ACTIONS COMMANDS, ADDITIONAL COMMANDS 等
    # 在 USAGE、FLAGS、INHERITED FLAGS 等区域停止解析
    in_command_zone = False
    # 识别命令区标题（全大写，包含 COMMANDS 或 ALIAS）
    command_zone_header = re.compile(
        r'^[A-Z][A-Z\s]*COMMANDS?\s*:?$',
    )
    # 别名区标题（也是命令的一种展示，但应标记为别名）
    alias_zone_header = re.compile(
        r'^[A-Z][A-Z\s]*ALIAS(?:ES)?\s*:?\s*$',
    )
    # 识别非命令区标题（FLAGS, USAGE, EXAMPLES 等）
    non_command_zone = re.compile(
        r'^(USAGE|FLAGS|OPTIONS|INHERITED|EXAMPLES?|DESCRIPTION|LEARN|SEE|GLOBAL|ARGUMENTS|ENVIRONMENT)[:\s]',
        re.IGNORECASE
    )

    in_alias_zone = False
    for line in lines:
        stripped = line.strip()

        # 检测别名区（别名单独展示，不是子命令）
        if alias_zone_header.match(stripped):
            in_alias_zone = True
            in_command_zone = False
            continue

        # 如果在别名区，跳过直到下一个分区
        if in_alias_zone:
            if stripped and not line.startswith(' '):
                # 新的非缩进行 - 可能是新区
                if command_zone_header.match(stripped):
                    in_command_zone = True
                    in_alias_zone = False
                    continue
                elif not cobra_pattern.match(line) and not line.startswith(' '):
                    in_alias_zone = False
            continue

        # 检测命令区开始
        if command_zone_header.match(stripped):
            in_command_zone = True
            continue

        # 检测非命令区开始
        if non_command_zone.match(stripped) or (stripped.isupper() and ':' not in stripped and len(stripped) > 3):
            if not command_zone_header.match(stripped):
                in_command_zone = False
                continue

        # 只在命令区解析子命令
        if not in_command_zone:
            continue

        m = cobra_pattern.match(line)
        if m:
            name = m.group(1)
            desc = m.group(2).strip()
            # 跳过选项格式（如 -R, --repo）
            if name.startswith('-'):
                continue
            if name.lower() not in skip_commands and len(name) > 1 and desc:
                cobra_matches.append((name, desc))

    # 如果找到足够数量的 cobra 格式命令，直接使用
    if len(cobra_matches) >= 2:
        return cobra_matches

    # ===== 策略 1: Go/Cobra 格式 "  cmd:  Description" =====
    # 很多 Go 工具 (gh, docker, kubectl, helm) 用这种格式
    # 命令名后紧跟冒号，然后是空格和对齐的描述
    # 需要区分子命令区和 FLAGS/INHERITED FLAGS 等区域
    cobra_pattern = re.compile(r'^\s{1,12}(\w[\w-]*):\s+(.+)$')
    cobra_matches = []

    # Cobra 帮助中，子命令在不同区域:
    # CORE COMMANDS, GITHUB ACTIONS COMMANDS, ADDITIONAL COMMANDS 等
    # 在 USAGE、FLAGS、INHERITED FLAGS 等区域停止解析
    in_command_zone = False
    # 识别命令区标题（全大写，包含 COMMANDS）
    command_zone_header = re.compile(
        r'^[A-Z][A-Z\s]*COMMANDS?\s*:?\s*$',
    )
    # 识别命令区标题（也可能带 "ALIAS" 字样）
    alias_zone_header = re.compile(
        r'^[A-Z][A-Z\s]*ALIAS\s*:?\s*$',
    )
    # 识别非命令区标题（FLAGS, USAGE, EXAMPLES 等）
    non_command_zone = re.compile(
        r'^(USAGE|FLAGS|OPTIONS|INHERITED|EXAMPLES|DESCRIPTION|LEARN|SEE|GLOBAL|ARGUMENTS|ENVIRONMENT)[:\s]',
        re.IGNORECASE
    )

    for line in lines:
        stripped = line.strip()

        # 检测命令区开始
        if command_zone_header.match(stripped) or alias_zone_header.match(stripped):
            in_command_zone = True
            continue

        # 检测非命令区开始
        if non_command_zone.match(stripped) or (stripped.isupper() and ':' not in stripped and len(stripped) > 3):
            # 可能是新的分区标记
            if not command_zone_header.match(stripped):
                in_command_zone = False
                continue

        # 只在命令区解析子命令
        if not in_command_zone:
            continue

        m = cobra_pattern.match(line)
        if m:
            name = m.group(1)
            desc = m.group(2).strip()
            if name.lower() not in skip_commands and len(name) > 1 and desc:
                cobra_matches.append((name, desc))

    # 如果找到足够数量的 cobra 格式命令，直接使用
    if len(cobra_matches) >= 2:
        return cobra_matches