pygitcode 0.1.3__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {pygitcode-0.1.3/src/pygitcode.egg-info → pygitcode-0.1.4}/PKG-INFO +1 -1
  2. {pygitcode-0.1.3 → pygitcode-0.1.4}/pyproject.toml +1 -0
  3. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/__init__.py +1 -1
  4. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/adapters/capabilities.py +4 -0
  5. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/adapters/pulls.py +0 -2
  6. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/cli.py +13 -9
  7. pygitcode-0.1.4/src/gitcode_cli/commands/compat.py +89 -0
  8. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/commands/pr.py +110 -22
  9. pygitcode-0.1.4/src/gitcode_cli/compat/__init__.py +10 -0
  10. pygitcode-0.1.4/src/gitcode_cli/compat/_types.py +48 -0
  11. pygitcode-0.1.4/src/gitcode_cli/compat/entries.py +459 -0
  12. pygitcode-0.1.4/src/gitcode_cli/compat/registry.py +146 -0
  13. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/services/pulls.py +20 -1
  14. {pygitcode-0.1.3 → pygitcode-0.1.4/src/pygitcode.egg-info}/PKG-INFO +1 -1
  15. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/pygitcode.egg-info/SOURCES.txt +5 -0
  16. {pygitcode-0.1.3 → pygitcode-0.1.4}/LICENSE +0 -0
  17. {pygitcode-0.1.3 → pygitcode-0.1.4}/README.md +0 -0
  18. {pygitcode-0.1.3 → pygitcode-0.1.4}/setup.cfg +0 -0
  19. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/adapters/__init__.py +0 -0
  20. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/adapters/base.py +0 -0
  21. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/adapters/issues.py +0 -0
  22. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/cli_compat.py +0 -0
  23. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/client.py +0 -0
  24. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/commands/__init__.py +0 -0
  25. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/commands/auth.py +0 -0
  26. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/commands/issue.py +0 -0
  27. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/config.py +0 -0
  28. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/context.py +0 -0
  29. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/errors.py +0 -0
  30. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/formatters.py +0 -0
  31. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/helptext.py +0 -0
  32. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/repo.py +0 -0
  33. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/services/__init__.py +0 -0
  34. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/services/issues.py +0 -0
  35. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/services/users.py +0 -0
  36. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/gitcode_cli/utils.py +0 -0
  37. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/pygitcode.egg-info/dependency_links.txt +0 -0
  38. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/pygitcode.egg-info/entry_points.txt +0 -0
  39. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/pygitcode.egg-info/requires.txt +0 -0
  40. {pygitcode-0.1.3 → pygitcode-0.1.4}/src/pygitcode.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygitcode
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: A CLI tool for GitCode (api.gitcode.com), modeled after GitHub CLI.
5
5
  Author-email: codeasier <825770651@qq.com>
6
6
  License-Expression: MIT
@@ -95,6 +95,7 @@ select = [
95
95
  ignore = [
96
96
  "PLR2004", # Magic value used in comparison
97
97
  "PLR0913", # Too many arguments in function definition (common for Click commands and API methods)
98
+ "PLR0917", # Too many positional arguments (common for Click commands and API methods)
98
99
  ]
99
100
 
100
101
  [tool.ruff.lint.per-file-ignores]
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.1.3"
3
+ __version__ = "0.1.4"
@@ -17,6 +17,10 @@ CAPABILITY_MESSAGES = {
17
17
  ),
18
18
  "PR_MERGE_AUTHOR_EMAIL": "GitCode merge API does not support --author-email.",
19
19
  "PR_MERGE_AUTO": "GitCode merge API does not support --auto.",
20
+ "PR_EDIT_BASE": (
21
+ "GitCode's pull request update API does not support changing the base branch; "
22
+ "-B/--base cannot be used with 'gc pr edit'."
23
+ ),
20
24
  "PR_REVIEW_REQUEST_CHANGES": (
21
25
  "GitCode review API does not support request-changes reviews; the pull request comment was posted instead."
22
26
  ),
@@ -161,7 +161,6 @@ class PullRequestAdapter:
161
161
  *,
162
162
  title: str | None,
163
163
  body: str | None,
164
- base: str | None,
165
164
  add_assignee: str | None,
166
165
  add_label: str | None,
167
166
  add_reviewer: str | None,
@@ -176,7 +175,6 @@ class PullRequestAdapter:
176
175
  for k, v in {
177
176
  "title": title,
178
177
  "body": body,
179
- "base": base,
180
178
  "assignee": add_assignee,
181
179
  "labels": add_label,
182
180
  "reviewer": add_reviewer,
@@ -9,6 +9,7 @@ from click.shell_completion import get_completion_class
9
9
 
10
10
  from . import __version__
11
11
  from .commands.auth import auth_group
12
+ from .commands.compat import compat_group
12
13
  from .commands.issue import issue_group
13
14
  from .commands.pr import pr_group
14
15
  from .config import get_token
@@ -67,12 +68,13 @@ set_gc_help(
67
68
  gc_usage="gc <command> <subcommand> [flags]",
68
69
  gc_command_sections=[
69
70
  ("CORE COMMANDS", ["auth", "issue", "pr"]),
70
- ("ADDITIONAL COMMANDS", ["completion", "version"]),
71
+ ("ADDITIONAL COMMANDS", ["compat", "completion", "version"]),
71
72
  ],
72
73
  gc_examples=[
73
74
  "gc issue create",
74
75
  "gc pr list -R owner/repo",
75
76
  "gc auth login",
77
+ "gc compat report --stdout",
76
78
  ],
77
79
  gc_learn_more=[
78
80
  "Use `gc <command> <subcommand> --help` for more information about a command.",
@@ -80,17 +82,18 @@ set_gc_help(
80
82
  )
81
83
 
82
84
 
83
- auth_group.short_help = "Authenticate gc with GitCode"
84
- issue_group.short_help = "Manage issues"
85
- pr_group.short_help = "Manage pull requests"
85
+ auth_group.short_help = "Authenticate gc with GitCode" # type: ignore[attr-defined]
86
+ issue_group.short_help = "Manage issues" # type: ignore[attr-defined]
87
+ pr_group.short_help = "Manage pull requests" # type: ignore[attr-defined]
88
+ compat_group.short_help = "Inspect gc ↔ gh compatibility" # type: ignore[attr-defined]
86
89
 
87
90
 
88
- @main.command("version", short_help="Show gc version", help="Show gc version.")
91
+ @main.command("version", short_help="Show gc version", help="Show gc version.") # type: ignore[attr-defined]
89
92
  def version_command() -> None:
90
93
  safe_echo(f"gitcode version {_get_version()}")
91
94
 
92
95
 
93
- @main.command("completion", short_help="Generate shell completion scripts", help="Generate shell completion scripts.")
96
+ @main.command("completion", short_help="Generate shell completion scripts", help="Generate shell completion scripts.") # type: ignore[attr-defined]
94
97
  @click.argument("shell", type=click.Choice(["bash", "zsh", "fish"]))
95
98
  def completion_command(shell: str) -> None:
96
99
  comp_class = get_completion_class(shell)
@@ -100,9 +103,10 @@ def completion_command(shell: str) -> None:
100
103
  safe_echo(comp.source())
101
104
 
102
105
 
103
- main.add_command(auth_group)
104
- main.add_command(issue_group)
105
- main.add_command(pr_group)
106
+ main.add_command(auth_group) # type: ignore[attr-defined]
107
+ main.add_command(issue_group) # type: ignore[attr-defined]
108
+ main.add_command(pr_group) # type: ignore[attr-defined]
109
+ main.add_command(compat_group) # type: ignore[attr-defined]
106
110
 
107
111
 
108
112
  if __name__ == "__main__": # pragma: no cover
@@ -0,0 +1,89 @@
1
+ """``gc compat`` subcommand — render the gh-compatibility matrix."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+
8
+ import click
9
+
10
+ from ..compat import CompatStatus, iter_entries, render_markdown_report, statistics
11
+ from ..helptext import set_gc_help
12
+ from ..utils import safe_echo
13
+
14
+
15
+ @click.group(
16
+ "compat",
17
+ short_help="Inspect gc ↔ gh compatibility status",
18
+ help="Inspect the authoritative gh-compatibility registry used to track issue #68.",
19
+ )
20
+ def compat_group() -> None:
21
+ pass
22
+
23
+
24
+ @compat_group.command( # type: ignore[attr-defined]
25
+ "report",
26
+ short_help="Render the compatibility matrix",
27
+ help=(
28
+ "Render the compatibility matrix as Markdown. The output mirrors the "
29
+ "structure of issue #68 and is regenerated from the registry on every "
30
+ "run; do not edit it by hand."
31
+ ),
32
+ )
33
+ @click.option("--stdout", "to_stdout", is_flag=True, help="Write Markdown to stdout instead of disk.")
34
+ def compat_report(to_stdout: bool) -> None:
35
+ markdown = render_markdown_report()
36
+ if to_stdout:
37
+ safe_echo(markdown)
38
+ return
39
+ safe_echo(markdown)
40
+ safe_echo(
41
+ "\n(hint: re-run with --stdout to capture the report, e.g. `gc compat report --stdout > COMPATIBILITY.md`)",
42
+ err=True,
43
+ )
44
+
45
+
46
+ @compat_group.command( # type: ignore[attr-defined]
47
+ "stats",
48
+ short_help="Print registry statistics as JSON",
49
+ help=("Print aggregated status counts per top-level group as JSON on stdout."),
50
+ )
51
+ def compat_stats() -> None:
52
+ json.dump(statistics(), sys.stdout, indent=2, sort_keys=True)
53
+ sys.stdout.write("\n")
54
+ sys.stdout.flush()
55
+
56
+
57
+ @compat_group.command( # type: ignore[attr-defined]
58
+ "list",
59
+ short_help="List entries (one per line)",
60
+ help=(
61
+ "List all registry entries. By default only pending and unsupported "
62
+ "entries are shown; pass --all to include everything."
63
+ ),
64
+ )
65
+ @click.option("--all", "show_all", is_flag=True, help="Show every entry, not just open work.")
66
+ @click.option("--status", "status_filter", multiple=True, help="Filter by status (repeatable).")
67
+ def compat_list(show_all: bool, status_filter: tuple[str, ...]) -> None:
68
+ statuses = tuple(CompatStatus(s) for s in status_filter) if status_filter else None
69
+ if statuses is None and not show_all:
70
+ statuses = (CompatStatus.PENDING, CompatStatus.UNSUPPORTED, CompatStatus.NOT_IN_CLI)
71
+ for entry in iter_entries(statuses=statuses):
72
+ safe_echo(f"{entry.command:<22} {entry.flag or '-':<28} {entry.status.value:<13} {entry.note}")
73
+
74
+
75
+ set_gc_help(
76
+ compat_group,
77
+ gc_usage="gc compat <command>",
78
+ gc_command_sections=[
79
+ ("COMMANDS", ["report", "stats", "list"]),
80
+ ],
81
+ gc_examples=[
82
+ "gc compat report --stdout",
83
+ "gc compat stats",
84
+ "gc compat list --status pending",
85
+ ],
86
+ gc_arguments_help=[
87
+ "The registry replaces the hand-maintained table in issue #68.",
88
+ ],
89
+ )
@@ -46,6 +46,66 @@ def _normalize_pr_fields(item: dict) -> dict:
46
46
  return result
47
47
 
48
48
 
49
+ def _normalize_closing_issue(item: dict) -> dict:
50
+ number = item.get("number")
51
+ if isinstance(number, str) and number.isdigit():
52
+ number = int(number)
53
+
54
+ state = item.get("state")
55
+ if isinstance(state, str):
56
+ state = state.upper()
57
+ if state == "OPENED":
58
+ state = "OPEN"
59
+
60
+ return {
61
+ "number": number,
62
+ "title": item.get("title"),
63
+ "state": state,
64
+ "url": item.get("html_url") or item.get("url"),
65
+ }
66
+
67
+
68
+ def _normalize_pr_actor(value: object) -> object:
69
+ if not isinstance(value, dict):
70
+ return value
71
+ login = value.get("login") or value.get("username") or value.get("name")
72
+ return {
73
+ "id": value.get("id"),
74
+ "is_bot": bool(value.get("is_bot", False)),
75
+ "login": login,
76
+ "name": value.get("name") or login,
77
+ }
78
+
79
+
80
+ def _pr_ref_name(value: object) -> object:
81
+ return value.get("ref") if isinstance(value, dict) else value
82
+
83
+
84
+ def _normalize_pr_view_fields(item: dict) -> dict:
85
+ result = _normalize_pr_fields(item)
86
+ assignees = item.get("assignees")
87
+ result.update(
88
+ {
89
+ "author": _normalize_pr_actor(item.get("author") or item.get("user") or item.get("creator")),
90
+ "assignees": (
91
+ [_normalize_pr_actor(assignee) for assignee in assignees] if isinstance(assignees, list) else assignees
92
+ ),
93
+ "baseRefName": item.get("baseRefName") or _pr_ref_name(item.get("base")),
94
+ "createdAt": item.get("createdAt") or item.get("created_at"),
95
+ "headRefName": item.get("headRefName") or _pr_ref_name(item.get("head")),
96
+ "updatedAt": item.get("updatedAt") or item.get("updated_at"),
97
+ "url": item.get("html_url") or item.get("url"),
98
+ }
99
+ )
100
+ if isinstance(result.get("state"), str):
101
+ result["state"] = result["state"].upper()
102
+ return result
103
+
104
+
105
+ def _json_field_requested(json_fields: str | None, field: str) -> bool:
106
+ return json_fields is not None and field in {value.strip() for value in json_fields.split(",")}
107
+
108
+
49
109
  def _split_merged_range(value: str) -> tuple[str | None, str | None]:
50
110
  if ".." in value:
51
111
  after, before = value.split("..", 1)
@@ -362,7 +422,7 @@ def pr_reopen(ctx: click.Context, repo_name: str | None, identifier: str | None,
362
422
  @click.option("-t", "--title")
363
423
  @click.option("-b", "--body")
364
424
  @click.option("-F", "--body-file")
365
- @click.option("-B", "--base")
425
+ @click.option("-B", "--base", help="Unsupported: GitCode does not allow changing a pull request's base branch.")
366
426
  @click.option("-a", "--add-assignee")
367
427
  @click.option("-l", "--add-label")
368
428
  @click.option("-r", "--add-reviewer")
@@ -389,6 +449,8 @@ def pr_edit(
389
449
  milestone: str | None,
390
450
  remove_milestone: bool,
391
451
  ) -> None:
452
+ if base is not None:
453
+ raise unsupported("PR_EDIT_BASE")
392
454
  app = ctx.obj["app"]
393
455
  owner, repo = resolve_repo(repo_name or app.repo)
394
456
  service = PullRequestService(app.client())
@@ -401,7 +463,6 @@ def pr_edit(
401
463
  [
402
464
  title is not None,
403
465
  body is not None,
404
- base is not None,
405
466
  add_assignee is not None,
406
467
  add_label is not None,
407
468
  add_reviewer is not None,
@@ -420,7 +481,6 @@ def pr_edit(
420
481
  number,
421
482
  title=title,
422
483
  body=body,
423
- base=base,
424
484
  add_assignee=add_assignee,
425
485
  add_label=add_label,
426
486
  add_reviewer=add_reviewer,
@@ -535,28 +595,36 @@ def pr_view(
535
595
  if web:
536
596
  open_in_browser(item["html_url"])
537
597
  return
538
- if comments:
539
- comment_items = service.list_comments(owner, repo, number)
540
- data = dict(item) if item else {}
598
+ output_item = _normalize_pr_view_fields(item) if item and (json_fields or jq_query or template) else item
599
+ if _json_field_requested(json_fields, "closingIssuesReferences"):
600
+ output_item = dict(output_item or {})
601
+ related_issues = service.list_issues(owner, repo, number) or []
602
+ output_item["closingIssuesReferences"] = [_normalize_closing_issue(issue) for issue in related_issues]
603
+ if comments or _json_field_requested(json_fields, "comments"):
604
+ comment_items = service.list_comments(owner, repo, number) or []
605
+ data = dict(output_item) if output_item else {}
541
606
  data["comments"] = comment_items
542
607
 
543
- def default_formatter(data: dict) -> None:
544
- safe_echo(format_pr_detail(data))
545
- if comment_items:
546
- safe_echo("\nComments:")
547
- for comment in comment_items:
548
- safe_echo(f"- {comment.get('body') or ''}")
549
-
550
- output_result(
551
- data,
552
- json_fields,
553
- jq_query,
554
- template,
555
- default_formatter=default_formatter,
556
- )
557
- return
608
+ if comments:
609
+
610
+ def default_formatter(data: dict) -> None:
611
+ safe_echo(format_pr_detail(data))
612
+ if comment_items:
613
+ safe_echo("\nComments:")
614
+ for comment in comment_items:
615
+ safe_echo(f"- {comment.get('body') or ''}")
616
+
617
+ output_result(
618
+ data,
619
+ json_fields,
620
+ jq_query,
621
+ template,
622
+ default_formatter=default_formatter,
623
+ )
624
+ return
625
+ output_item = data
558
626
  output_result(
559
- item,
627
+ output_item,
560
628
  json_fields,
561
629
  jq_query,
562
630
  template,
@@ -896,6 +964,26 @@ set_gc_help(
896
964
  )
897
965
  pr_view.short_help = "View a pull request"
898
966
  pr_view.help = "View a pull request."
967
+ set_gc_help(
968
+ pr_view,
969
+ gc_json_fields=[
970
+ "author",
971
+ "assignees",
972
+ "baseRefName",
973
+ "body",
974
+ "closingIssuesReferences",
975
+ "comments",
976
+ "createdAt",
977
+ "headRefName",
978
+ "labels",
979
+ "mergedAt",
980
+ "number",
981
+ "state",
982
+ "title",
983
+ "updatedAt",
984
+ "url",
985
+ ],
986
+ )
899
987
  pr_create.short_help = "Create a pull request"
900
988
  pr_create.help = "Create a pull request."
901
989
  set_gc_help(pr_create, gc_aliases=["new"])
@@ -0,0 +1,10 @@
1
+ from ._types import CompatEntry, CompatStatus
2
+ from .registry import iter_entries, render_markdown_report, statistics
3
+
4
+ __all__ = [
5
+ "CompatEntry",
6
+ "CompatStatus",
7
+ "iter_entries",
8
+ "render_markdown_report",
9
+ "statistics",
10
+ ]
@@ -0,0 +1,48 @@
1
+ """Shared types and constants for the compat registry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+
8
+
9
+ class CompatStatus(str, Enum):
10
+ """Classification of a ``gc`` command/flag against the ``gh`` baseline."""
11
+
12
+ IMPLEMENTED = "implemented"
13
+ PENDING = "pending"
14
+ UNSUPPORTED = "unsupported"
15
+ NOT_IN_CLI = "not_in_cli"
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class CompatEntry:
20
+ """One row of the compatibility matrix.
21
+
22
+ ``command`` is a space-separated path without the leading ``gc`` (e.g.
23
+ ``"issue list"``). ``flag`` is the long-form flag (``"--app"``) or ``None``
24
+ when the entry describes a whole subcommand.
25
+
26
+ ``pending_key`` must match the literal string passed to
27
+ ``_pending_gh_compat()`` for ``PENDING`` entries. ``unsupported_key`` must
28
+ match the capability key passed to ``unsupported()`` for ``UNSUPPORTED``
29
+ entries (and the key must exist in ``CAPABILITY_MESSAGES``).
30
+ """
31
+
32
+ command: str
33
+ flag: str | None
34
+ status: CompatStatus
35
+ note: str = ""
36
+ pending_key: str | None = None
37
+ unsupported_key: str | None = None
38
+ gc_extension: bool = False
39
+ aliases: tuple[str, ...] = field(default_factory=tuple)
40
+
41
+ def label(self) -> str:
42
+ """Human-readable label used in the rendered report."""
43
+ if self.flag is None:
44
+ return self.command
45
+ aliases = "/".join((self.aliases or ()) + ((self.flag,) if self.flag else ()))
46
+ if not aliases:
47
+ return f"{self.command} (no flag)"
48
+ return f"{self.command} {aliases}"
@@ -0,0 +1,459 @@
1
+ """Registry entries — declarative gh-compatibility status.
2
+
3
+ Each tuple is ``(command, flag, status, note, **kwargs)`` where ``flag is None``
4
+ denotes a subcommand-level row. ``pending_key`` and ``unsupported_key`` link the
5
+ entry to runtime guards (enforced by ``test_registry_consistency``).
6
+
7
+ The data was transcribed from issue #68 (last revised 2026-05-06) and
8
+ re-validated against ``origin/main`` (2026-08-08, commit de31598). Subsequent
9
+ PRs that change command code MUST update this file in lockstep.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from ._types import CompatEntry, CompatStatus
15
+
16
+
17
+ def _e(
18
+ command: str,
19
+ flag: str | None,
20
+ status: CompatStatus,
21
+ note: str = "",
22
+ *,
23
+ pending_key: str | None = None,
24
+ unsupported_key: str | None = None,
25
+ gc_extension: bool = False,
26
+ aliases: tuple[str, ...] = (),
27
+ ) -> CompatEntry:
28
+ return CompatEntry(
29
+ command=command,
30
+ flag=flag,
31
+ status=status,
32
+ note=note,
33
+ pending_key=pending_key,
34
+ unsupported_key=unsupported_key,
35
+ gc_extension=gc_extension,
36
+ aliases=aliases,
37
+ )
38
+
39
+
40
+ ENTRIES: list[CompatEntry] = [
41
+ # --- auth subcommands ---
42
+ _e("auth login", None, CompatStatus.IMPLEMENTED, "Supports --with-token and interactive input"),
43
+ _e("auth logout", None, CompatStatus.IMPLEMENTED),
44
+ _e("auth status", None, CompatStatus.IMPLEMENTED, "Simplified: token mask only"),
45
+ _e("auth token", None, CompatStatus.IMPLEMENTED),
46
+ # --- auth flags ---
47
+ _e(
48
+ "auth login",
49
+ "--with-token",
50
+ CompatStatus.IMPLEMENTED,
51
+ ),
52
+ _e("auth login", "-c/--clipboard", CompatStatus.NOT_IN_CLI),
53
+ _e("auth login", "-p/--git-protocol", CompatStatus.NOT_IN_CLI),
54
+ _e("auth login", "-h/--hostname", CompatStatus.NOT_IN_CLI),
55
+ _e("auth login", "--insecure-storage", CompatStatus.NOT_IN_CLI),
56
+ _e("auth login", "-s/--scopes", CompatStatus.NOT_IN_CLI),
57
+ _e("auth login", "--skip-ssh-key", CompatStatus.NOT_IN_CLI),
58
+ _e("auth login", "-w/--web", CompatStatus.NOT_IN_CLI),
59
+ _e("auth logout", "-h/--hostname", CompatStatus.NOT_IN_CLI),
60
+ _e("auth logout", "-u/--user", CompatStatus.NOT_IN_CLI),
61
+ _e("auth status", "-a/--active", CompatStatus.NOT_IN_CLI),
62
+ _e("auth status", "-h/--hostname", CompatStatus.NOT_IN_CLI),
63
+ _e(
64
+ "auth status",
65
+ "--json",
66
+ CompatStatus.PENDING,
67
+ "Click guard added; structured output not yet implemented",
68
+ pending_key="auth status --json",
69
+ ),
70
+ _e(
71
+ "auth status",
72
+ "--jq",
73
+ CompatStatus.PENDING,
74
+ "Click guard added; structured output not yet implemented",
75
+ pending_key="auth status --jq",
76
+ ),
77
+ _e(
78
+ "auth status",
79
+ "--template",
80
+ CompatStatus.PENDING,
81
+ "Click guard added; structured output not yet implemented",
82
+ pending_key="auth status --template",
83
+ ),
84
+ _e("auth status", "--show-token", CompatStatus.NOT_IN_CLI),
85
+ _e("auth token", "-h/--hostname", CompatStatus.NOT_IN_CLI),
86
+ _e("auth token", "-u/--user", CompatStatus.NOT_IN_CLI),
87
+ # --- issue subcommands ---
88
+ _e("issue list", None, CompatStatus.IMPLEMENTED, "Alias: ls"),
89
+ _e("issue view", None, CompatStatus.IMPLEMENTED),
90
+ _e("issue create", None, CompatStatus.IMPLEMENTED, "Alias: new"),
91
+ _e("issue close", None, CompatStatus.IMPLEMENTED),
92
+ _e("issue comment", None, CompatStatus.IMPLEMENTED),
93
+ _e("issue reopen", None, CompatStatus.IMPLEMENTED),
94
+ _e("issue edit", None, CompatStatus.IMPLEMENTED),
95
+ _e(
96
+ "issue status",
97
+ None,
98
+ CompatStatus.IMPLEMENTED,
99
+ "Approximate: lacks gh user-specific filtering (ISSUE_STATUS_GH_SEMANTICS)",
100
+ ),
101
+ _e(
102
+ "issue develop",
103
+ None,
104
+ CompatStatus.IMPLEMENTED,
105
+ "Degraded: opens GitCode issue page, does not create/checkout local branch",
106
+ ),
107
+ _e(
108
+ "issue delete",
109
+ None,
110
+ CompatStatus.IMPLEMENTED,
111
+ "Uses unofficial GitCode endpoint; unstable (PR #83)",
112
+ ),
113
+ _e("issue lock", None, CompatStatus.NOT_IN_CLI),
114
+ _e("issue pin", None, CompatStatus.NOT_IN_CLI),
115
+ _e("issue transfer", None, CompatStatus.NOT_IN_CLI),
116
+ _e("issue unlock", None, CompatStatus.NOT_IN_CLI),
117
+ _e("issue unpin", None, CompatStatus.NOT_IN_CLI),
118
+ # --- issue flags ---
119
+ _e("issue list", "-R/--repo", CompatStatus.IMPLEMENTED),
120
+ _e("issue list", "-s/--state", CompatStatus.IMPLEMENTED),
121
+ _e("issue list", "-l/--label", CompatStatus.IMPLEMENTED),
122
+ _e("issue list", "-A/--author", CompatStatus.IMPLEMENTED),
123
+ _e("issue list", "-a/--assignee", CompatStatus.IMPLEMENTED),
124
+ _e("issue list", "--milestone", CompatStatus.IMPLEMENTED),
125
+ _e("issue list", "--mention", CompatStatus.IMPLEMENTED),
126
+ _e(
127
+ "issue list",
128
+ "--app",
129
+ CompatStatus.UNSUPPORTED,
130
+ "GitCode issue API does not support application filtering",
131
+ unsupported_key="ISSUE_LIST_APP",
132
+ ),
133
+ _e("issue list", "-S/--search", CompatStatus.IMPLEMENTED),
134
+ _e("issue list", "-L/--limit", CompatStatus.IMPLEMENTED),
135
+ _e("issue list", "-w/--web", CompatStatus.IMPLEMENTED),
136
+ _e("issue list", "--json", CompatStatus.IMPLEMENTED),
137
+ _e("issue list", "-q/--jq", CompatStatus.IMPLEMENTED),
138
+ _e("issue list", "-t/--template", CompatStatus.IMPLEMENTED),
139
+ _e(
140
+ "issue list",
141
+ "-m",
142
+ CompatStatus.NOT_IN_CLI,
143
+ "gh short alias clashes with gc; gc exposes --milestone only",
144
+ ),
145
+ _e("issue view", "-R/--repo", CompatStatus.IMPLEMENTED),
146
+ _e("issue view", "-w/--web", CompatStatus.IMPLEMENTED),
147
+ _e("issue view", "-c/--comments", CompatStatus.IMPLEMENTED),
148
+ _e("issue view", "--json", CompatStatus.IMPLEMENTED),
149
+ _e("issue view", "-q/--jq", CompatStatus.IMPLEMENTED),
150
+ _e("issue view", "-t/--template", CompatStatus.IMPLEMENTED),
151
+ _e("issue create", "-R/--repo", CompatStatus.IMPLEMENTED),
152
+ _e("issue create", "-t/--title", CompatStatus.IMPLEMENTED),
153
+ _e("issue create", "-b/--body", CompatStatus.IMPLEMENTED),
154
+ _e("issue create", "-a/--assignee", CompatStatus.IMPLEMENTED),
155
+ _e("issue create", "-e/--editor", CompatStatus.IMPLEMENTED),
156
+ _e("issue create", "-l/--label", CompatStatus.IMPLEMENTED),
157
+ _e("issue create", "-m/--milestone", CompatStatus.IMPLEMENTED),
158
+ _e("issue create", "-F/--body-file", CompatStatus.IMPLEMENTED),
159
+ _e("issue create", "-w/--web", CompatStatus.IMPLEMENTED),
160
+ _e("issue create", "--json", CompatStatus.IMPLEMENTED),
161
+ _e("issue create", "-q/--jq", CompatStatus.IMPLEMENTED),
162
+ _e(
163
+ "issue create",
164
+ "-T/--template",
165
+ CompatStatus.IMPLEMENTED,
166
+ "Treated as body template file (PR #91)",
167
+ ),
168
+ _e("issue create", "-p/--project", CompatStatus.NOT_IN_CLI, "Project association not implemented"),
169
+ _e("issue create", "--recover", CompatStatus.NOT_IN_CLI, "Draft recovery not implemented"),
170
+ _e("issue close", "-R/--repo", CompatStatus.IMPLEMENTED),
171
+ _e("issue close", "-c/--comment", CompatStatus.IMPLEMENTED),
172
+ _e(
173
+ "issue close",
174
+ "-r/--reason",
175
+ CompatStatus.IMPLEMENTED,
176
+ "Supports completed/not_planned; gh additionally supports duplicate",
177
+ ),
178
+ _e(
179
+ "issue close",
180
+ "--duplicate-of",
181
+ CompatStatus.IMPLEMENTED,
182
+ "GitCode-limited approximation: posts duplicate notice then closes (PR #89)",
183
+ ),
184
+ _e("issue comment", "-R/--repo", CompatStatus.IMPLEMENTED),
185
+ _e("issue comment", "-b/--body", CompatStatus.IMPLEMENTED),
186
+ _e("issue comment", "-F/--body-file", CompatStatus.IMPLEMENTED),
187
+ _e("issue comment", "-e/--editor", CompatStatus.IMPLEMENTED),
188
+ _e("issue comment", "-w/--web", CompatStatus.IMPLEMENTED),
189
+ _e("issue comment", "--create-if-none", CompatStatus.IMPLEMENTED),
190
+ _e("issue comment", "--delete-last", CompatStatus.IMPLEMENTED),
191
+ _e("issue comment", "--edit-last", CompatStatus.IMPLEMENTED),
192
+ _e("issue comment", "--yes", CompatStatus.IMPLEMENTED),
193
+ _e("issue edit", "-R/--repo", CompatStatus.IMPLEMENTED),
194
+ _e("issue edit", "-t/--title", CompatStatus.IMPLEMENTED),
195
+ _e("issue edit", "-b/--body", CompatStatus.IMPLEMENTED),
196
+ _e("issue edit", "-F/--body-file", CompatStatus.IMPLEMENTED),
197
+ _e("issue edit", "-a/--add-assignee", CompatStatus.IMPLEMENTED),
198
+ _e("issue edit", "-l/--add-label", CompatStatus.IMPLEMENTED),
199
+ _e("issue edit", "-m/--milestone", CompatStatus.IMPLEMENTED),
200
+ _e("issue edit", "--remove-milestone", CompatStatus.IMPLEMENTED),
201
+ _e("issue edit", "--remove-assignee", CompatStatus.IMPLEMENTED, "Implemented (PR #120)"),
202
+ _e("issue edit", "--remove-label", CompatStatus.IMPLEMENTED, "Implemented (PR #120)"),
203
+ _e("issue edit", "--add-project", CompatStatus.NOT_IN_CLI),
204
+ _e("issue edit", "--remove-project", CompatStatus.NOT_IN_CLI),
205
+ _e("issue reopen", "-R/--repo", CompatStatus.IMPLEMENTED),
206
+ _e("issue reopen", "-c/--comment", CompatStatus.IMPLEMENTED, "Implemented (PR #122)"),
207
+ _e("issue status", "-R/--repo", CompatStatus.IMPLEMENTED),
208
+ _e("issue status", "--json", CompatStatus.IMPLEMENTED, "Implemented (PR #113)"),
209
+ _e("issue status", "-q/--jq", CompatStatus.IMPLEMENTED, "Implemented (PR #113)"),
210
+ _e("issue status", "-t/--template", CompatStatus.IMPLEMENTED, "Implemented (PR #113)"),
211
+ _e("issue develop", "-R/--repo", CompatStatus.IMPLEMENTED),
212
+ _e(
213
+ "issue develop",
214
+ "-b/--base",
215
+ CompatStatus.UNSUPPORTED,
216
+ "GitCode API does not provide issue develop branch API",
217
+ unsupported_key="ISSUE_DEVELOP_BASE",
218
+ ),
219
+ _e(
220
+ "issue develop",
221
+ "-n/--name",
222
+ CompatStatus.UNSUPPORTED,
223
+ "GitCode API does not provide issue develop branch API",
224
+ unsupported_key="ISSUE_DEVELOP_NAME",
225
+ ),
226
+ _e("issue develop", "-c/--checkout", CompatStatus.NOT_IN_CLI),
227
+ _e("issue develop", "-l/--list", CompatStatus.NOT_IN_CLI),
228
+ _e("issue develop", "--branch-repo", CompatStatus.NOT_IN_CLI),
229
+ _e("issue delete", "-R/--repo", CompatStatus.IMPLEMENTED),
230
+ _e("issue delete", "--yes", CompatStatus.IMPLEMENTED),
231
+ # --- pr subcommands ---
232
+ _e("pr list", None, CompatStatus.IMPLEMENTED, "Alias: ls"),
233
+ _e("pr view", None, CompatStatus.IMPLEMENTED),
234
+ _e("pr create", None, CompatStatus.IMPLEMENTED, "Alias: new"),
235
+ _e("pr close", None, CompatStatus.IMPLEMENTED),
236
+ _e("pr merge", None, CompatStatus.IMPLEMENTED),
237
+ _e("pr comment", None, CompatStatus.IMPLEMENTED),
238
+ _e(
239
+ "pr review",
240
+ None,
241
+ CompatStatus.IMPLEMENTED,
242
+ "Degraded: --request-changes falls back to plain comment (PR_REVIEW_REQUEST_CHANGES)",
243
+ ),
244
+ _e("pr reopen", None, CompatStatus.IMPLEMENTED),
245
+ _e("pr edit", None, CompatStatus.IMPLEMENTED),
246
+ _e("pr diff", None, CompatStatus.IMPLEMENTED),
247
+ _e("pr checkout", None, CompatStatus.IMPLEMENTED),
248
+ _e("pr ready", None, CompatStatus.IMPLEMENTED),
249
+ _e(
250
+ "pr status",
251
+ None,
252
+ CompatStatus.IMPLEMENTED,
253
+ "Approximate: lacks gh user-specific filtering (PR_STATUS_GH_SEMANTICS)",
254
+ ),
255
+ _e("pr checks", None, CompatStatus.NOT_IN_CLI),
256
+ _e("pr lock", None, CompatStatus.NOT_IN_CLI),
257
+ _e("pr revert", None, CompatStatus.NOT_IN_CLI),
258
+ _e("pr unlock", None, CompatStatus.NOT_IN_CLI),
259
+ _e("pr update-branch", None, CompatStatus.NOT_IN_CLI),
260
+ # --- pr flags ---
261
+ _e("pr list", "-R/--repo", CompatStatus.IMPLEMENTED),
262
+ _e("pr list", "-s/--state", CompatStatus.IMPLEMENTED),
263
+ _e("pr list", "-A/--author", CompatStatus.IMPLEMENTED),
264
+ _e("pr list", "-B/--base", CompatStatus.IMPLEMENTED),
265
+ _e("pr list", "-a/--assignee", CompatStatus.IMPLEMENTED),
266
+ _e("pr list", "-d/--draft", CompatStatus.IMPLEMENTED),
267
+ _e("pr list", "-H/--head", CompatStatus.IMPLEMENTED),
268
+ _e("pr list", "-l/--label", CompatStatus.IMPLEMENTED),
269
+ _e("pr list", "-S/--search", CompatStatus.IMPLEMENTED),
270
+ _e("pr list", "-L/--limit", CompatStatus.IMPLEMENTED),
271
+ _e("pr list", "-w/--web", CompatStatus.IMPLEMENTED),
272
+ _e("pr list", "--json", CompatStatus.IMPLEMENTED),
273
+ _e("pr list", "-q/--jq", CompatStatus.IMPLEMENTED),
274
+ _e("pr list", "-t/--template", CompatStatus.IMPLEMENTED),
275
+ _e("pr list", "--app", CompatStatus.NOT_IN_CLI, "App filtering not implemented"),
276
+ _e("pr view", "-R/--repo", CompatStatus.IMPLEMENTED),
277
+ _e("pr view", "-w/--web", CompatStatus.IMPLEMENTED),
278
+ _e("pr view", "-c/--comments", CompatStatus.IMPLEMENTED),
279
+ _e("pr view", "--json", CompatStatus.IMPLEMENTED),
280
+ _e("pr view", "-q/--jq", CompatStatus.IMPLEMENTED),
281
+ _e("pr view", "-t/--template", CompatStatus.IMPLEMENTED),
282
+ _e("pr create", "-R/--repo", CompatStatus.IMPLEMENTED),
283
+ _e("pr create", "-t/--title", CompatStatus.IMPLEMENTED),
284
+ _e("pr create", "-b/--body", CompatStatus.IMPLEMENTED),
285
+ _e("pr create", "-F/--body-file", CompatStatus.IMPLEMENTED),
286
+ _e("pr create", "-e/--editor", CompatStatus.IMPLEMENTED),
287
+ _e("pr create", "-f/--fill", CompatStatus.IMPLEMENTED),
288
+ _e("pr create", "--fill-first", CompatStatus.IMPLEMENTED),
289
+ _e("pr create", "--fill-verbose", CompatStatus.IMPLEMENTED),
290
+ _e("pr create", "--dry-run", CompatStatus.IMPLEMENTED),
291
+ _e("pr create", "-B/--base", CompatStatus.IMPLEMENTED),
292
+ _e("pr create", "-H/--head", CompatStatus.IMPLEMENTED),
293
+ _e("pr create", "-d/--draft", CompatStatus.IMPLEMENTED),
294
+ _e("pr create", "-m/--milestone", CompatStatus.IMPLEMENTED),
295
+ _e("pr create", "-l/--label", CompatStatus.IMPLEMENTED),
296
+ _e("pr create", "-r/--reviewer", CompatStatus.IMPLEMENTED),
297
+ _e("pr create", "-a/--assignee", CompatStatus.IMPLEMENTED),
298
+ _e("pr create", "-w/--web", CompatStatus.IMPLEMENTED),
299
+ _e("pr create", "--json", CompatStatus.IMPLEMENTED),
300
+ _e("pr create", "-q/--jq", CompatStatus.IMPLEMENTED),
301
+ _e(
302
+ "pr create",
303
+ "-T/--template",
304
+ CompatStatus.IMPLEMENTED,
305
+ "Treated as body template file (PR #91)",
306
+ ),
307
+ _e("pr create", "-p/--project", CompatStatus.NOT_IN_CLI, "Project association not implemented"),
308
+ _e("pr create", "--recover", CompatStatus.NOT_IN_CLI, "Draft recovery not implemented"),
309
+ _e("pr create", "--no-maintainer-edit", CompatStatus.NOT_IN_CLI, "Maintainer-edit controls not implemented"),
310
+ _e("pr close", "-R/--repo", CompatStatus.IMPLEMENTED),
311
+ _e("pr close", "-c/--comment", CompatStatus.IMPLEMENTED),
312
+ _e("pr close", "-d/--delete-branch", CompatStatus.IMPLEMENTED),
313
+ _e("pr merge", "-R/--repo", CompatStatus.IMPLEMENTED),
314
+ _e("pr merge", "-m/--merge", CompatStatus.IMPLEMENTED),
315
+ _e("pr merge", "-s/--squash", CompatStatus.IMPLEMENTED),
316
+ _e("pr merge", "-r/--rebase", CompatStatus.IMPLEMENTED),
317
+ _e("pr merge", "-d/--delete-branch", CompatStatus.IMPLEMENTED),
318
+ _e("pr merge", "-b/--body", CompatStatus.IMPLEMENTED),
319
+ _e("pr merge", "-F/--body-file", CompatStatus.IMPLEMENTED),
320
+ _e("pr merge", "-t/--subject", CompatStatus.IMPLEMENTED),
321
+ _e(
322
+ "pr merge",
323
+ "-A/--author-email",
324
+ CompatStatus.UNSUPPORTED,
325
+ "GitCode merge API does not support author-email override (PR #88)",
326
+ unsupported_key="PR_MERGE_AUTHOR_EMAIL",
327
+ ),
328
+ _e(
329
+ "pr merge",
330
+ "--auto",
331
+ CompatStatus.UNSUPPORTED,
332
+ "GitCode merge API does not support auto-merge/merge-queue (PR #88)",
333
+ unsupported_key="PR_MERGE_AUTO",
334
+ ),
335
+ _e("pr merge", "--admin", CompatStatus.IMPLEMENTED),
336
+ _e("pr merge", "--disable-auto", CompatStatus.NOT_IN_CLI),
337
+ _e("pr merge", "--match-head-commit", CompatStatus.NOT_IN_CLI),
338
+ _e("pr comment", "-R/--repo", CompatStatus.IMPLEMENTED),
339
+ _e("pr comment", "-b/--body", CompatStatus.IMPLEMENTED),
340
+ _e("pr comment", "-F/--body-file", CompatStatus.IMPLEMENTED),
341
+ _e("pr comment", "-e/--editor", CompatStatus.IMPLEMENTED),
342
+ _e("pr comment", "-w/--web", CompatStatus.IMPLEMENTED),
343
+ _e("pr comment", "--path", CompatStatus.IMPLEMENTED, "gc extension; not in gh", gc_extension=True),
344
+ _e("pr comment", "--position", CompatStatus.IMPLEMENTED, "gc extension; not in gh", gc_extension=True),
345
+ _e("pr comment", "--line", CompatStatus.IMPLEMENTED, "gc extension; not in gh", gc_extension=True),
346
+ _e("pr comment", "--side", CompatStatus.IMPLEMENTED, "gc extension; not in gh", gc_extension=True),
347
+ _e("pr comment", "--commit-id", CompatStatus.IMPLEMENTED, "gc extension; not in gh", gc_extension=True),
348
+ _e(
349
+ "pr comment",
350
+ "--create-if-none",
351
+ CompatStatus.PENDING,
352
+ "Click guard added; history management not yet implemented",
353
+ pending_key="pr comment --create-if-none",
354
+ ),
355
+ _e(
356
+ "pr comment",
357
+ "--delete-last",
358
+ CompatStatus.PENDING,
359
+ "Click guard added; history management not yet implemented",
360
+ pending_key="pr comment --delete-last",
361
+ ),
362
+ _e(
363
+ "pr comment",
364
+ "--edit-last",
365
+ CompatStatus.PENDING,
366
+ "Click guard added; history management not yet implemented",
367
+ pending_key="pr comment --edit-last",
368
+ ),
369
+ _e(
370
+ "pr comment",
371
+ "--yes",
372
+ CompatStatus.PENDING,
373
+ "Click guard added; confirmation skip not yet implemented",
374
+ pending_key="pr comment --yes",
375
+ ),
376
+ _e("pr review", "-R/--repo", CompatStatus.IMPLEMENTED),
377
+ _e("pr review", "-a/--approve", CompatStatus.IMPLEMENTED),
378
+ _e("pr review", "-b/--body", CompatStatus.IMPLEMENTED),
379
+ _e("pr review", "-c/--comment", CompatStatus.IMPLEMENTED),
380
+ _e(
381
+ "pr review",
382
+ "-r/--request-changes",
383
+ CompatStatus.IMPLEMENTED,
384
+ "Degraded: posts plain comment (PR_REVIEW_REQUEST_CHANGES)",
385
+ ),
386
+ _e("pr review", "--force", CompatStatus.IMPLEMENTED, "gc extension", gc_extension=True),
387
+ _e("pr review", "-F/--body-file", CompatStatus.IMPLEMENTED),
388
+ _e("pr reopen", "-R/--repo", CompatStatus.IMPLEMENTED),
389
+ _e(
390
+ "pr reopen",
391
+ "-c/--comment",
392
+ CompatStatus.PENDING,
393
+ "Click guard added; gh supports reopening with comment",
394
+ pending_key="pr reopen --comment",
395
+ ),
396
+ _e("pr edit", "-R/--repo", CompatStatus.IMPLEMENTED),
397
+ _e("pr edit", "-t/--title", CompatStatus.IMPLEMENTED),
398
+ _e("pr edit", "-b/--body", CompatStatus.IMPLEMENTED),
399
+ _e("pr edit", "-F/--body-file", CompatStatus.IMPLEMENTED),
400
+ _e(
401
+ "pr edit",
402
+ "-B/--base",
403
+ CompatStatus.UNSUPPORTED,
404
+ "GitCode PR update API does not support base branch changes (PR #137)",
405
+ unsupported_key="PR_EDIT_BASE",
406
+ ),
407
+ _e("pr edit", "-a/--add-assignee", CompatStatus.IMPLEMENTED),
408
+ _e("pr edit", "-l/--add-label", CompatStatus.IMPLEMENTED),
409
+ _e("pr edit", "-r/--add-reviewer", CompatStatus.IMPLEMENTED),
410
+ _e("pr edit", "--remove-assignee", CompatStatus.IMPLEMENTED),
411
+ _e("pr edit", "--remove-label", CompatStatus.IMPLEMENTED),
412
+ _e("pr edit", "--remove-reviewer", CompatStatus.IMPLEMENTED),
413
+ _e("pr edit", "-m/--milestone", CompatStatus.IMPLEMENTED),
414
+ _e("pr edit", "--remove-milestone", CompatStatus.IMPLEMENTED),
415
+ _e("pr edit", "--add-project", CompatStatus.NOT_IN_CLI),
416
+ _e("pr edit", "--remove-project", CompatStatus.NOT_IN_CLI),
417
+ _e("pr diff", "-R/--repo", CompatStatus.IMPLEMENTED),
418
+ _e("pr diff", "--color", CompatStatus.NOT_IN_CLI),
419
+ _e("pr diff", "-e/--exclude", CompatStatus.NOT_IN_CLI),
420
+ _e(
421
+ "pr diff",
422
+ "--name-only",
423
+ CompatStatus.PENDING,
424
+ "Click guard added; behaviour not yet implemented",
425
+ pending_key="pr diff --name-only",
426
+ ),
427
+ _e(
428
+ "pr diff",
429
+ "--patch",
430
+ CompatStatus.PENDING,
431
+ "Click guard added; behaviour not yet implemented",
432
+ pending_key="pr diff --patch",
433
+ ),
434
+ _e("pr diff", "-w/--web", CompatStatus.NOT_IN_CLI),
435
+ _e("pr checkout", "-R/--repo", CompatStatus.IMPLEMENTED),
436
+ _e("pr checkout", "-b/--branch", CompatStatus.IMPLEMENTED),
437
+ _e(
438
+ "pr checkout",
439
+ "--detach",
440
+ CompatStatus.PENDING,
441
+ "Click guard added; behaviour not yet implemented",
442
+ pending_key="pr checkout --detach",
443
+ ),
444
+ _e(
445
+ "pr checkout",
446
+ "-f/--force",
447
+ CompatStatus.PENDING,
448
+ "Click guard added; behaviour not yet implemented",
449
+ pending_key="pr checkout --force",
450
+ ),
451
+ _e("pr checkout", "--recurse-submodules", CompatStatus.NOT_IN_CLI),
452
+ _e("pr ready", "-R/--repo", CompatStatus.IMPLEMENTED),
453
+ _e("pr ready", "--undo", CompatStatus.IMPLEMENTED),
454
+ _e("pr status", "-R/--repo", CompatStatus.IMPLEMENTED),
455
+ _e("pr status", "-c/--conflict-status", CompatStatus.NOT_IN_CLI),
456
+ _e("pr status", "--json", CompatStatus.NOT_IN_CLI),
457
+ _e("pr status", "-q/--jq", CompatStatus.NOT_IN_CLI),
458
+ _e("pr status", "-t/--template", CompatStatus.NOT_IN_CLI),
459
+ ]
@@ -0,0 +1,146 @@
1
+ """Authoritative registry of ``gc`` gh-compatibility status.
2
+
3
+ This module is the single source of truth for the ``gc`` ↔ ``gh`` compatibility
4
+ matrix that previously lived in GitCode issue #68. Tracking the matrix in the
5
+ issue body drifted every time a flag was implemented, marked unsupported, or
6
+ reclassified; CI now enforces that the registry, the Click command tree, and the
7
+ runtime guards stay in lockstep.
8
+
9
+ Status taxonomy
10
+ ---------------
11
+ * ``implemented`` — Click option exists, behaviour matches ``gh`` semantics
12
+ (or a clearly documented approximation).
13
+ * ``pending`` — Click option exists; invoking it raises a
14
+ ``_pending_gh_compat("<pending_key>")`` ClickException. The optional
15
+ ``pending_key`` field is the literal string passed to that helper.
16
+ * ``unsupported`` — Click option exists; invoking it raises
17
+ ``unsupported("<unsupported_key>")``. The key must be present in
18
+ ``adapters.capabilities.CAPABILITY_MESSAGES``.
19
+ * ``not_in_cli`` — ``gh`` exposes the flag but ``gc`` does not. Invoking it
20
+ surfaces a Click "no such option" error.
21
+
22
+ Entries with ``flag is None`` describe a subcommand (e.g. ``issue lock``); the
23
+ status applies to the subcommand as a whole.
24
+
25
+ Adding or reclassifying an entry
26
+ --------------------------------
27
+ Edit this file in the same PR that changes the corresponding command code. The
28
+ ``tests/unit/compat/test_registry_consistency.py`` suite enforces that every
29
+ ``pending`` entry has a matching runtime guard, every ``unsupported`` entry has a
30
+ matching capability message, every ``implemented``/``pending``/``unsupported``
31
+ flag is actually registered with Click, and every ``not_in_cli`` flag is
32
+ absent.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ from collections.abc import Iterable, Iterator
38
+ from typing import Final
39
+
40
+ from ._types import CompatEntry, CompatStatus
41
+ from .entries import ENTRIES
42
+
43
+ # A tuple of ``CompatEntry`` keyed by ``(command, flag)``. ``flag is None``
44
+ # represents a subcommand-level entry. The tuple is the registry; do not mutate.
45
+ _ENTRIES: Final[tuple[CompatEntry, ...]] = tuple(ENTRIES)
46
+
47
+
48
+ def iter_entries(
49
+ *,
50
+ groups: Iterable[str] | None = None,
51
+ statuses: Iterable[CompatStatus] | None = None,
52
+ ) -> Iterator[CompatEntry]:
53
+ """Iterate entries, optionally filtered by top-level group and/or status."""
54
+ group_set = set(groups) if groups is not None else None
55
+ status_set = set(statuses) if statuses is not None else None
56
+ for entry in _ENTRIES:
57
+ if group_set is not None:
58
+ top = entry.command.split(" ", 1)[0]
59
+ if top not in group_set:
60
+ continue
61
+ if status_set is not None and entry.status not in status_set:
62
+ continue
63
+ yield entry
64
+
65
+
66
+ def statistics() -> dict[str, dict[str, int]]:
67
+ """Aggregate counts per top-level command group, row kind, and status.
68
+
69
+ Returns ``{group: {status_label: count, ...}, ...}``. ``group`` is one of
70
+ ``auth``/``issue``/``pr``. Subcommand rows (``flag is None``) are counted
71
+ separately from flag rows so the totals match the structure of issue #68.
72
+ """
73
+
74
+ counts: dict[str, dict[str, int]] = {}
75
+ for entry in _ENTRIES:
76
+ top = entry.command.split(" ", 1)[0]
77
+ bucket = counts.setdefault(top, {})
78
+ key = f"{entry.status.value}_{'subcommand' if entry.flag is None else 'flag'}"
79
+ bucket[key] = bucket.get(key, 0) + 1
80
+ return counts
81
+
82
+
83
+ def render_markdown_report() -> str:
84
+ """Render the registry as Markdown mirroring the issue #68 layout."""
85
+ lines: list[str] = [
86
+ "# gc ↔ gh compatibility matrix",
87
+ "",
88
+ "Auto-generated from `gitcode_cli.compat.registry`. Do not edit by hand; "
89
+ "edit `src/gitcode_cli/compat/entries.py` and run `gc compat report`.",
90
+ "",
91
+ "## Status legend",
92
+ "",
93
+ "| Status | Meaning |",
94
+ "|---|---|",
95
+ "| **implemented** | CLI placeholder + behaviour implemented |",
96
+ "| **pending** | CLI placeholder + flag parses but raises `_pending_gh_compat()` |",
97
+ "| **unsupported** | CLI placeholder + adapter marks it unsupported (GitCode API limit) |",
98
+ "| **not_in_cli** | `gh` baseline exposes it but `gc` does not |",
99
+ "",
100
+ ]
101
+ for group in ("auth", "issue", "pr"):
102
+ sub_entries = [e for e in iter_entries(groups=[group]) if e.flag is None]
103
+ flag_entries = [e for e in iter_entries(groups=[group]) if e.flag is not None]
104
+ if not sub_entries and not flag_entries:
105
+ continue
106
+ lines.append(f"## {group}")
107
+ lines.append("")
108
+ lines.append("### Subcommands")
109
+ lines.append("")
110
+ lines.append("| Subcommand | Status | Notes |")
111
+ lines.append("|---|---|---|")
112
+ for entry in sub_entries:
113
+ note = entry.note.replace("|", "\\|")
114
+ lines.append(f"| `{entry.command}` | {entry.status.value} | {note} |")
115
+ lines.append("")
116
+ lines.append("### Flags")
117
+ lines.append("")
118
+ lines.append("| Command | Flag | Status | Notes |")
119
+ lines.append("|---|---|---|---|")
120
+ for entry in flag_entries:
121
+ note = entry.note.replace("|", "\\|")
122
+ lines.append(f"| `{entry.command}` | `{entry.flag}` | {entry.status.value} | {note} |")
123
+ lines.append("")
124
+
125
+ lines.append("## Statistics")
126
+ lines.append("")
127
+ lines.append("| Group | implemented | pending | unsupported | not_in_cli | Total |")
128
+ lines.append("|---|---|---|---|---|---|")
129
+ grand = {"implemented": 0, "pending": 0, "unsupported": 0, "not_in_cli": 0}
130
+ for group in ("auth", "issue", "pr"):
131
+ bucket = statistics().get(group, {})
132
+ impl = bucket.get("implemented", 0)
133
+ pend = bucket.get("pending", 0)
134
+ unsup = bucket.get("unsupported", 0)
135
+ ninc = bucket.get("not_in_cli", 0)
136
+ total = impl + pend + unsup + ninc
137
+ lines.append(f"| {group} | {impl} | {pend} | {unsup} | {ninc} | {total} |")
138
+ for k, v in (("implemented", impl), ("pending", pend), ("unsupported", unsup), ("not_in_cli", ninc)):
139
+ grand[k] += v
140
+ gtotal = sum(grand.values())
141
+ lines.append(
142
+ f"| **Total** | **{grand['implemented']}** | **{grand['pending']}** | "
143
+ f"**{grand['unsupported']}** | **{grand['not_in_cli']}** | **{gtotal}** |"
144
+ )
145
+ lines.append("")
146
+ return "\n".join(lines)
@@ -15,6 +15,9 @@ class PullRequestService:
15
15
  def get(self, owner: str, repo: str, number: int) -> Any | None:
16
16
  return self.client.get(f"/repos/{owner}/{repo}/pulls/{number}")
17
17
 
18
+ def list_issues(self, owner: str, repo: str, number: int) -> Any | None:
19
+ return self.client.get(f"/repos/{owner}/{repo}/pulls/{number}/issues")
20
+
18
21
  def create(self, owner: str, repo: str, **data: Any) -> Any | None:
19
22
  return self.client.post(f"/repos/{owner}/{repo}/pulls", json={k: v for k, v in data.items() if v is not None})
20
23
 
@@ -42,7 +45,23 @@ class PullRequestService:
42
45
  return self.client.post(f"/repos/{owner}/{repo}/pulls/{number}/review", json=filtered_payload)
43
46
 
44
47
  def list_comments(self, owner: str, repo: str, number: int) -> Any | None:
45
- return self.client.get(f"/repos/{owner}/{repo}/pulls/{number}/comments")
48
+ per_page = 100
49
+ page = 1
50
+ comments: list[Any] = []
51
+
52
+ while True:
53
+ result = self.client.get(
54
+ f"/repos/{owner}/{repo}/pulls/{number}/comments",
55
+ params={"page": page, "per_page": per_page},
56
+ )
57
+ if not isinstance(result, list):
58
+ return comments if comments else result
59
+
60
+ comments.extend(result)
61
+ if len(result) < per_page:
62
+ return comments
63
+
64
+ page += 1
46
65
 
47
66
  def diff(self, owner: str, repo: str, number: int) -> str:
48
67
  response = self.client.request(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygitcode
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: A CLI tool for GitCode (api.gitcode.com), modeled after GitHub CLI.
5
5
  Author-email: codeasier <825770651@qq.com>
6
6
  License-Expression: MIT
@@ -19,8 +19,13 @@ src/gitcode_cli/adapters/issues.py
19
19
  src/gitcode_cli/adapters/pulls.py
20
20
  src/gitcode_cli/commands/__init__.py
21
21
  src/gitcode_cli/commands/auth.py
22
+ src/gitcode_cli/commands/compat.py
22
23
  src/gitcode_cli/commands/issue.py
23
24
  src/gitcode_cli/commands/pr.py
25
+ src/gitcode_cli/compat/__init__.py
26
+ src/gitcode_cli/compat/_types.py
27
+ src/gitcode_cli/compat/entries.py
28
+ src/gitcode_cli/compat/registry.py
24
29
  src/gitcode_cli/services/__init__.py
25
30
  src/gitcode_cli/services/issues.py
26
31
  src/gitcode_cli/services/pulls.py
File without changes
File without changes
File without changes