gitwise-cli 0.24.2__py3-none-any.whl

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 (125) hide show
  1. gitwise/__init__.py +11 -0
  2. gitwise/__main__.py +113 -0
  3. gitwise/_cli_completions.py +88 -0
  4. gitwise/_cli_dispatch.py +469 -0
  5. gitwise/_cli_introspection.py +275 -0
  6. gitwise/_cli_parser.py +345 -0
  7. gitwise/_cli_setup_agents.py +439 -0
  8. gitwise/_i18n_data.json +1934 -0
  9. gitwise/_paths.py +22 -0
  10. gitwise/_runtime_config.py +246 -0
  11. gitwise/audit.py +338 -0
  12. gitwise/branches.py +183 -0
  13. gitwise/clean.py +197 -0
  14. gitwise/commit.py +142 -0
  15. gitwise/conflicts.py +112 -0
  16. gitwise/context.py +163 -0
  17. gitwise/design.py +383 -0
  18. gitwise/diff.py +309 -0
  19. gitwise/doctor.py +116 -0
  20. gitwise/git.py +254 -0
  21. gitwise/health.py +345 -0
  22. gitwise/i18n.py +99 -0
  23. gitwise/log.py +329 -0
  24. gitwise/merge.py +193 -0
  25. gitwise/optimize.py +212 -0
  26. gitwise/output.py +652 -0
  27. gitwise/pick.py +102 -0
  28. gitwise/pr.py +543 -0
  29. gitwise/py.typed +0 -0
  30. gitwise/schema.py +49 -0
  31. gitwise/setup.py +551 -0
  32. gitwise/setup_agents/__init__.py +36 -0
  33. gitwise/setup_agents/adapters/__init__.py +17 -0
  34. gitwise/setup_agents/adapters/aider.py +5 -0
  35. gitwise/setup_agents/adapters/base.py +5 -0
  36. gitwise/setup_agents/adapters/codex.py +5 -0
  37. gitwise/setup_agents/adapters/continue_adapter.py +5 -0
  38. gitwise/setup_agents/adapters/cursor.py +5 -0
  39. gitwise/setup_agents/adapters/opencode.py +5 -0
  40. gitwise/setup_agents/adapters/pi.py +5 -0
  41. gitwise/setup_agents/exec.py +449 -0
  42. gitwise/setup_agents/format.py +164 -0
  43. gitwise/setup_agents/plan.py +254 -0
  44. gitwise/setup_agents/plan_gitfiles.py +167 -0
  45. gitwise/setup_agents/plan_skills.py +256 -0
  46. gitwise/setup_agents/providers/__init__.py +96 -0
  47. gitwise/setup_agents/providers/aider.py +11 -0
  48. gitwise/setup_agents/providers/base.py +79 -0
  49. gitwise/setup_agents/providers/claude.py +408 -0
  50. gitwise/setup_agents/providers/codex.py +11 -0
  51. gitwise/setup_agents/providers/continue_adapter.py +11 -0
  52. gitwise/setup_agents/providers/cursor.py +11 -0
  53. gitwise/setup_agents/providers/opencode.py +11 -0
  54. gitwise/setup_agents/providers/pi.py +11 -0
  55. gitwise/setup_agents/state.py +141 -0
  56. gitwise/setup_agents/types.py +48 -0
  57. gitwise/share/agents/skills/git-audit/SKILL.md +25 -0
  58. gitwise/share/agents/skills/git-clean/SKILL.md +22 -0
  59. gitwise/share/agents/skills/git-optimize/SKILL.md +21 -0
  60. gitwise/share/aider/CONVENTIONS.md.template +8 -0
  61. gitwise/share/aider/aider.conf.yml.template +4 -0
  62. gitwise/share/claude/CLAUDE.md.template +9 -0
  63. gitwise/share/claude/rules/gitwise.md +16 -0
  64. gitwise/share/claude/settings.json.template +47 -0
  65. gitwise/share/claude/skills/git-audit/SKILL.md +25 -0
  66. gitwise/share/claude/skills/git-clean/SKILL.md +22 -0
  67. gitwise/share/claude/skills/git-optimize/SKILL.md +21 -0
  68. gitwise/share/codex/agents/gitwise.toml.template +18 -0
  69. gitwise/share/continue/rules/gitwise.md.template +14 -0
  70. gitwise/share/cursor/rules/gitwise.mdc.template +16 -0
  71. gitwise/share/git-config-modern.txt +48 -0
  72. gitwise/share/hooks/commit-msg +22 -0
  73. gitwise/share/hooks/pre-commit +19 -0
  74. gitwise/share/opencode/agents/gitwise.md.template +14 -0
  75. gitwise/share/pi/skills/gitwise.md.template +14 -0
  76. gitwise/share/schemas/v1/input/audit.json +40 -0
  77. gitwise/share/schemas/v1/input/branches.json +51 -0
  78. gitwise/share/schemas/v1/input/clean.json +52 -0
  79. gitwise/share/schemas/v1/input/commands.json +36 -0
  80. gitwise/share/schemas/v1/input/commit.json +63 -0
  81. gitwise/share/schemas/v1/input/completions.json +51 -0
  82. gitwise/share/schemas/v1/input/conflicts.json +46 -0
  83. gitwise/share/schemas/v1/input/context.json +36 -0
  84. gitwise/share/schemas/v1/input/diff.json +56 -0
  85. gitwise/share/schemas/v1/input/doctor.json +36 -0
  86. gitwise/share/schemas/v1/input/health.json +36 -0
  87. gitwise/share/schemas/v1/input/log.json +71 -0
  88. gitwise/share/schemas/v1/input/merge.json +63 -0
  89. gitwise/share/schemas/v1/input/optimize.json +44 -0
  90. gitwise/share/schemas/v1/input/pick.json +63 -0
  91. gitwise/share/schemas/v1/input/pr.json +51 -0
  92. gitwise/share/schemas/v1/input/schema.json +48 -0
  93. gitwise/share/schemas/v1/input/setup-agents.json +108 -0
  94. gitwise/share/schemas/v1/input/setup.json +55 -0
  95. gitwise/share/schemas/v1/input/show.json +46 -0
  96. gitwise/share/schemas/v1/input/snapshot.json +36 -0
  97. gitwise/share/schemas/v1/input/stash.json +68 -0
  98. gitwise/share/schemas/v1/input/status.json +36 -0
  99. gitwise/share/schemas/v1/input/suggest.json +36 -0
  100. gitwise/share/schemas/v1/input/summarize.json +44 -0
  101. gitwise/share/schemas/v1/input/sync.json +55 -0
  102. gitwise/share/schemas/v1/input/tag.json +73 -0
  103. gitwise/share/schemas/v1/input/undo.json +60 -0
  104. gitwise/share/schemas/v1/input/update.json +40 -0
  105. gitwise/share/schemas/v1/input/worktree.json +50 -0
  106. gitwise/show.py +118 -0
  107. gitwise/snapshot.py +110 -0
  108. gitwise/stash.py +188 -0
  109. gitwise/status.py +93 -0
  110. gitwise/suggest.py +148 -0
  111. gitwise/summarize.py +202 -0
  112. gitwise/sync.py +257 -0
  113. gitwise/tag.py +252 -0
  114. gitwise/undo.py +145 -0
  115. gitwise/update.py +42 -0
  116. gitwise/utils/__init__.py +1 -0
  117. gitwise/utils/git_output.py +51 -0
  118. gitwise/utils/json_envelope.py +58 -0
  119. gitwise/utils/parsing.py +34 -0
  120. gitwise/worktree.py +182 -0
  121. gitwise_cli-0.24.2.dist-info/METADATA +151 -0
  122. gitwise_cli-0.24.2.dist-info/RECORD +125 -0
  123. gitwise_cli-0.24.2.dist-info/WHEEL +4 -0
  124. gitwise_cli-0.24.2.dist-info/entry_points.txt +2 -0
  125. gitwise_cli-0.24.2.dist-info/licenses/LICENSE +21 -0
gitwise/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ __version__ = "0.24.2"
2
+
3
+
4
+ def get_version() -> str:
5
+ from importlib.metadata import PackageNotFoundError
6
+ from importlib.metadata import version as _version
7
+
8
+ try:
9
+ return _version("gitwise")
10
+ except PackageNotFoundError:
11
+ return __version__
gitwise/__main__.py ADDED
@@ -0,0 +1,113 @@
1
+ """CLI entry point — argparse router for all gitwise subcommands."""
2
+
3
+ import sys
4
+ import time
5
+
6
+ from ._cli_dispatch import DISPATCH
7
+ from ._cli_introspection import extract_command_token, help_payload
8
+ from ._cli_parser import build_parser
9
+ from .i18n import t
10
+ from .output import print_dim, print_json, set_json_pretty
11
+
12
+
13
+ def _is_log_json_enabled() -> bool:
14
+ import os
15
+
16
+ return os.environ.get("GITWISE_LOG_JSON", "").lower() in ("1", "true")
17
+
18
+
19
+ def _should_show_rich_traceback() -> bool:
20
+ return (not _is_log_json_enabled()) and sys.stderr.isatty()
21
+
22
+
23
+ def _install_rich_traceback() -> None:
24
+ if not _should_show_rich_traceback():
25
+ return
26
+ try:
27
+ import importlib
28
+
29
+ rich_traceback_install = importlib.import_module("rich.traceback").install
30
+ rich_traceback_install(show_locals=False)
31
+ except ImportError:
32
+ return
33
+
34
+
35
+ def main() -> int:
36
+ import os
37
+
38
+ from ._runtime_config import reset_runtime_config
39
+ from .i18n import set_locale
40
+
41
+ _install_rich_traceback()
42
+
43
+ parser = build_parser()
44
+ raw_argv = sys.argv[1:]
45
+ wants_json_pretty = "--json-pretty" in raw_argv or "--pretty" in raw_argv
46
+ if wants_json_pretty:
47
+ set_json_pretty(True)
48
+
49
+ wants_json_help = ("--json" in raw_argv or wants_json_pretty) and (
50
+ "--help" in raw_argv or "-h" in raw_argv
51
+ )
52
+ if wants_json_help:
53
+ command = extract_command_token(raw_argv)
54
+ print_json(help_payload(parser, command))
55
+ return 0
56
+
57
+ args = parser.parse_args()
58
+ if args.json_pretty:
59
+ args.json = True
60
+
61
+ if args.command is None:
62
+ if args.json:
63
+ print_json(
64
+ {
65
+ **help_payload(parser),
66
+ "ok": False,
67
+ "error": "missing_command",
68
+ }
69
+ )
70
+ return 1
71
+ parser.print_usage(sys.stderr)
72
+ return 1
73
+
74
+ if args.theme and args.theme != "auto":
75
+ os.environ["GITWISE_THEME"] = args.theme
76
+ reset_runtime_config()
77
+
78
+ set_json_pretty(args.json_pretty)
79
+
80
+ if args.lang:
81
+ set_locale(args.lang)
82
+
83
+ start = time.monotonic()
84
+
85
+ handler = DISPATCH.get(args.command)
86
+ if handler is not None:
87
+ try:
88
+ ret = handler(args)
89
+ except KeyboardInterrupt:
90
+ ret = 130
91
+ except SystemExit:
92
+ raise
93
+ except Exception:
94
+ if _should_show_rich_traceback():
95
+ raise
96
+ from .output import error as _error
97
+
98
+ _error(t("unexpected_error"))
99
+ ret = 1
100
+ else:
101
+ parser.print_help(sys.stderr)
102
+ ret = 1
103
+
104
+ elapsed = time.monotonic() - start
105
+ as_json = getattr(args, "json", False)
106
+ if not as_json and elapsed > 0.2 and args.command not in ("doctor",):
107
+ print_dim(t("completed_in", elapsed=f"{elapsed:.1f}"))
108
+
109
+ return ret
110
+
111
+
112
+ if __name__ == "__main__":
113
+ sys.exit(main())
@@ -0,0 +1,88 @@
1
+ """Shell completions generation: bash, zsh, fish."""
2
+
3
+ import argparse
4
+
5
+ from ._cli_introspection import _subparsers_action
6
+ from ._cli_parser import build_parser
7
+
8
+
9
+ def build_completions_script(*, shell: str, prog: str) -> str:
10
+ parser = build_parser()
11
+ parser.prog = prog
12
+
13
+ if shell in ("bash", "zsh"):
14
+ import importlib
15
+
16
+ shtab_complete = importlib.import_module("shtab").complete
17
+ return shtab_complete(parser, shell=shell)
18
+
19
+ if shell == "fish":
20
+ return _build_fish_completions_script(parser=parser, prog=prog)
21
+
22
+ raise ValueError(f"unsupported shell: {shell}")
23
+
24
+
25
+ def _fish_escape(text: str) -> str:
26
+ return text.replace("'", "\\'")
27
+
28
+
29
+ def _build_fish_option_line(*, prog: str, condition: str, flag: str, help_text: str) -> str:
30
+ escaped_help = _fish_escape(help_text)
31
+ escaped_prog = _fish_escape(prog)
32
+ escaped_condition = _fish_escape(condition)
33
+ if flag.startswith("--"):
34
+ return (
35
+ f"complete -c '{escaped_prog}' -n \"{escaped_condition}\" "
36
+ f"-l {flag[2:]} -d '{escaped_help}'"
37
+ )
38
+ if flag.startswith("-") and len(flag) == 2:
39
+ return (
40
+ f"complete -c '{escaped_prog}' -n \"{escaped_condition}\" "
41
+ f"-s {flag[1:]} -d '{escaped_help}'"
42
+ )
43
+ return ""
44
+
45
+
46
+ def _build_fish_completions_script(*, parser: argparse.ArgumentParser, prog: str) -> str:
47
+ escaped_prog = _fish_escape(prog)
48
+ lines: list[str] = [f"# fish completion for {escaped_prog}"]
49
+
50
+ sub_action = _subparsers_action(parser)
51
+ command_names = sorted(sub_action.choices.keys()) if sub_action else []
52
+
53
+ if command_names:
54
+ joined = " ".join(command_names)
55
+ lines.append(
56
+ f"complete -c '{escaped_prog}' -f -n \"__fish_use_subcommand\" -a '{_fish_escape(joined)}'"
57
+ )
58
+
59
+ for action in parser._actions:
60
+ if action.dest == "help" or isinstance(action, argparse._SubParsersAction):
61
+ continue
62
+ for flag in action.option_strings:
63
+ line = _build_fish_option_line(
64
+ prog=prog,
65
+ condition="__fish_use_subcommand",
66
+ flag=flag,
67
+ help_text="" if action.help is argparse.SUPPRESS else (action.help or ""),
68
+ )
69
+ if line:
70
+ lines.append(line)
71
+
72
+ if sub_action is not None:
73
+ for command, command_parser in sorted(sub_action.choices.items()):
74
+ condition = f"__fish_seen_subcommand_from {command}"
75
+ for action in command_parser._actions:
76
+ if action.dest == "help" or isinstance(action, argparse._SubParsersAction):
77
+ continue
78
+ for flag in action.option_strings:
79
+ line = _build_fish_option_line(
80
+ prog=prog,
81
+ condition=condition,
82
+ flag=flag,
83
+ help_text="" if action.help is argparse.SUPPRESS else (action.help or ""),
84
+ )
85
+ if line:
86
+ lines.append(line)
87
+
88
+ return "\n".join(lines) + "\n"
@@ -0,0 +1,469 @@
1
+ """Command dispatchers: thin wrappers that delegate to subcommand modules."""
2
+
3
+ import argparse
4
+ from collections.abc import Callable
5
+
6
+ from . import __version__
7
+ from ._cli_completions import build_completions_script
8
+ from ._cli_introspection import (
9
+ canonical_command_name,
10
+ commands_metadata,
11
+ resolve_command_parser,
12
+ )
13
+ from ._cli_parser import build_parser
14
+ from .i18n import t
15
+ from .output import print_json
16
+ from .utils.json_envelope import error_envelope
17
+
18
+
19
+ def _run_update(args: argparse.Namespace) -> int:
20
+ from .update import run_update
21
+
22
+ return run_update(dry_run=args.dry_run, as_json=args.json)
23
+
24
+
25
+ def _run_doctor(args: argparse.Namespace) -> int:
26
+ from .doctor import run_doctor
27
+
28
+ return run_doctor(as_json=args.json)
29
+
30
+
31
+ def _run_setup_agents(args: argparse.Namespace) -> int:
32
+ if getattr(args, "list_providers", False) or getattr(args, "list_adapters", False):
33
+ from .i18n import t as _t
34
+ from .setup_agents.providers import list_providers
35
+
36
+ adapter_list = list_providers()
37
+ if args.json:
38
+ print_json({"providers": adapter_list, "adapters": adapter_list})
39
+ else:
40
+ from .output import info
41
+
42
+ info(_t("providers_available", list=", ".join(adapter_list)))
43
+ return 0
44
+
45
+ providers: list[str] | None = args.providers
46
+ adapters_legacy_used = False
47
+ if args.adapters is not None:
48
+ adapters_legacy_used = True
49
+ providers = args.adapters if providers is None else providers + args.adapters
50
+
51
+ from ._cli_setup_agents import run_setup_agents
52
+
53
+ return run_setup_agents(
54
+ local=args.local,
55
+ no_skills=args.no_skills,
56
+ dry_run=args.dry_run,
57
+ yes=args.yes,
58
+ as_json=args.json,
59
+ no_symlinks=args.no_symlinks,
60
+ strict=args.strict,
61
+ replace_claude_with_symlink=args.replace_claude_with_symlink,
62
+ migrate_legacy_claude=args.migrate_legacy_claude,
63
+ frozen_time=args.frozen_time,
64
+ no_git_files=args.no_git_files,
65
+ providers=providers,
66
+ adapters_legacy_used=adapters_legacy_used,
67
+ )
68
+
69
+
70
+ def _run_setup(args: argparse.Namespace) -> int:
71
+ from .setup import run_setup
72
+
73
+ return run_setup(
74
+ dry_run=args.dry_run,
75
+ yes=args.yes,
76
+ as_json=args.json,
77
+ hooks_mode=args.hooks_mode,
78
+ )
79
+
80
+
81
+ def _run_audit(args: argparse.Namespace) -> int:
82
+ from .audit import run_audit
83
+
84
+ return run_audit(quick=args.quick, as_json=args.json)
85
+
86
+
87
+ def _run_summarize(args: argparse.Namespace) -> int:
88
+ from .summarize import run_summarize
89
+
90
+ return run_summarize(as_json=args.json, diff=args.diff, max_commits=args.max_commits)
91
+
92
+
93
+ def _run_snapshot(args: argparse.Namespace) -> int:
94
+ from .snapshot import run_snapshot
95
+
96
+ return run_snapshot(as_json=args.json)
97
+
98
+
99
+ def _run_clean(args: argparse.Namespace) -> int:
100
+ from .clean import run_clean
101
+
102
+ return run_clean(
103
+ branches=args.branches,
104
+ refs=args.refs,
105
+ dry_run=args.dry_run,
106
+ yes=args.yes,
107
+ as_json=args.json,
108
+ )
109
+
110
+
111
+ def _run_optimize(args: argparse.Namespace) -> int:
112
+ from .optimize import run_optimize
113
+
114
+ return run_optimize(dry_run=args.dry_run, yes=args.yes, as_json=args.json)
115
+
116
+
117
+ def _run_worktree(args: argparse.Namespace) -> int:
118
+ from .worktree import run_worktree
119
+
120
+ return run_worktree(
121
+ args.action, getattr(args, "branch", None), dry_run=args.dry_run, as_json=args.json
122
+ )
123
+
124
+
125
+ def _run_diff(args: argparse.Namespace) -> int:
126
+ from .diff import run_diff
127
+
128
+ return run_diff(
129
+ staged=args.staged,
130
+ stat=args.stat,
131
+ name_only=args.name_only,
132
+ full=args.full,
133
+ as_json=args.json,
134
+ )
135
+
136
+
137
+ def _run_log(args: argparse.Namespace) -> int:
138
+ from .log import run_log
139
+
140
+ return run_log(
141
+ as_json=args.json,
142
+ oneline=args.oneline,
143
+ graph=args.graph,
144
+ author=args.author,
145
+ grep=args.grep,
146
+ since=args.since,
147
+ until=args.until,
148
+ file=args.file,
149
+ max_count=args.max_count,
150
+ )
151
+
152
+
153
+ def _run_show(args: argparse.Namespace) -> int:
154
+ from .show import run_show
155
+
156
+ return run_show(ref=args.ref, stat=args.stat, as_json=args.json)
157
+
158
+
159
+ def _run_commit(args: argparse.Namespace) -> int:
160
+ from .commit import run_commit
161
+
162
+ return run_commit(
163
+ message=args.message,
164
+ type=args.type,
165
+ scope=args.scope,
166
+ breaking=args.breaking,
167
+ amend=args.amend,
168
+ dry_run=args.dry_run,
169
+ as_json=args.json,
170
+ )
171
+
172
+
173
+ def _run_branches(args: argparse.Namespace) -> int:
174
+ from .branches import run_branches
175
+
176
+ return run_branches(stale=args.stale, remote=args.remote, sort=args.sort, as_json=args.json)
177
+
178
+
179
+ def _run_sync(args: argparse.Namespace) -> int:
180
+ from .sync import run_sync
181
+
182
+ return run_sync(
183
+ pull=args.pull,
184
+ push=args.push,
185
+ remote=args.remote,
186
+ dry_run=args.dry_run,
187
+ as_json=args.json,
188
+ )
189
+
190
+
191
+ def _run_pr(args: argparse.Namespace) -> int:
192
+ from .pr import run_pr
193
+
194
+ return run_pr(action=args.action, selector=args.selector, as_json=args.json)
195
+
196
+
197
+ def _run_undo(args: argparse.Namespace) -> int:
198
+ from .undo import run_undo
199
+
200
+ return run_undo(
201
+ ref=args.ref,
202
+ soft=args.soft,
203
+ steps=args.steps,
204
+ dry_run=args.dry_run,
205
+ yes=args.yes,
206
+ as_json=args.json,
207
+ )
208
+
209
+
210
+ def _run_context(args: argparse.Namespace) -> int:
211
+ from .context import run_context
212
+
213
+ return run_context(as_json=args.json)
214
+
215
+
216
+ def _run_health(args: argparse.Namespace) -> int:
217
+ from .health import run_health
218
+
219
+ return run_health(as_json=args.json)
220
+
221
+
222
+ def _run_stash(args: argparse.Namespace) -> int:
223
+ from .stash import run_stash
224
+
225
+ return run_stash(
226
+ action=args.action,
227
+ index=args.index,
228
+ as_json=args.json,
229
+ yes=args.yes,
230
+ dry_run=args.dry_run,
231
+ patch=args.patch,
232
+ )
233
+
234
+
235
+ def _run_tag(args: argparse.Namespace) -> int:
236
+ from .tag import run_tag
237
+
238
+ return run_tag(
239
+ action=args.action,
240
+ name=getattr(args, "name", None),
241
+ bump=args.bump,
242
+ message=args.message,
243
+ dry_run=args.dry_run,
244
+ yes=args.yes,
245
+ as_json=args.json,
246
+ )
247
+
248
+
249
+ def _run_merge(args: argparse.Namespace) -> int:
250
+ from .merge import run_merge
251
+
252
+ return run_merge(
253
+ args.branch,
254
+ rebase=args.rebase,
255
+ no_ff=args.no_ff,
256
+ dry_run=args.dry_run,
257
+ yes=args.yes,
258
+ as_json=args.json,
259
+ )
260
+
261
+
262
+ def _run_conflicts(args: argparse.Namespace) -> int:
263
+ from .conflicts import run_conflicts
264
+
265
+ return run_conflicts(ours=args.ours, theirs=args.theirs, as_json=args.json)
266
+
267
+
268
+ def _run_suggest(args: argparse.Namespace) -> int:
269
+ from .suggest import run_suggest
270
+
271
+ return run_suggest(as_json=args.json)
272
+
273
+
274
+ def _run_pick(args: argparse.Namespace) -> int:
275
+ from .pick import run_pick
276
+
277
+ return run_pick(
278
+ args.refs,
279
+ revert=args.revert,
280
+ continue_=args.continue_,
281
+ abort=args.abort,
282
+ dry_run=args.dry_run,
283
+ as_json=args.json,
284
+ )
285
+
286
+
287
+ def _run_status(args: argparse.Namespace) -> int:
288
+ from .status import run_status
289
+
290
+ return run_status(as_json=args.json)
291
+
292
+
293
+ def _run_completions(args: argparse.Namespace) -> int:
294
+ shell = args.shell
295
+ prog = args.prog
296
+ try:
297
+ script = build_completions_script(shell=shell, prog=prog)
298
+ except ModuleNotFoundError:
299
+ from .output import error as _error
300
+
301
+ hint = t("missing_dependency_hint")
302
+ message = t("missing_dependency_completions_shtab")
303
+ if args.json:
304
+ print_json(error_envelope(error=message, code="missing_dependency", hint=hint))
305
+ else:
306
+ _error(message, hint=hint)
307
+ return 1
308
+ except RuntimeError as e:
309
+ message = str(e)
310
+ if args.json:
311
+ print_json(error_envelope(error=message, code="runtime_error"))
312
+ else:
313
+ from .output import error as _error
314
+
315
+ _error(message)
316
+ return 1
317
+ except ValueError:
318
+ message = t("completions_unsupported_shell", shell=shell)
319
+ if args.json:
320
+ print_json(error_envelope(error=message, code="unsupported_shell"))
321
+ else:
322
+ from .output import error as _error
323
+
324
+ _error(message)
325
+ return 1
326
+
327
+ if args.json:
328
+ print_json(
329
+ {
330
+ "v": 2,
331
+ "ok": True,
332
+ "kind": "completions",
333
+ "schema": "gitwise/completions/v1",
334
+ "version": __version__,
335
+ "shell": shell,
336
+ "prog": prog,
337
+ "script": script,
338
+ }
339
+ )
340
+ return 0
341
+
342
+ print(script)
343
+ return 0
344
+
345
+
346
+ def _run_commands(args: argparse.Namespace) -> int:
347
+ parser = build_parser()
348
+ commands = commands_metadata(parser)
349
+ payload = {
350
+ "v": 2,
351
+ "ok": True,
352
+ "kind": "commands",
353
+ "schema": "gitwise/commands/v1",
354
+ "version": __version__,
355
+ "commands": commands,
356
+ }
357
+
358
+ if args.json:
359
+ print_json(payload)
360
+ return 0
361
+
362
+ aliases_label = t("aliases_label")
363
+ for item in commands:
364
+ aliases_list = item["aliases"]
365
+ alias_text = (
366
+ f" ({aliases_label}: {', '.join(str(alias) for alias in aliases_list)})"
367
+ if aliases_list
368
+ else ""
369
+ )
370
+ print(f"{item['name']}: {item['help']}{alias_text}")
371
+ return 0
372
+
373
+
374
+ def _run_schema(args: argparse.Namespace) -> int:
375
+ from .schema import load_command_input_schema
376
+
377
+ parser = build_parser()
378
+ command_parser = resolve_command_parser(parser=parser, name=args.name)
379
+ if command_parser is None:
380
+ message = t("schema_unknown_command", name=args.name)
381
+ hint = t("schema_unknown_command_hint")
382
+ if args.json:
383
+ print_json(
384
+ error_envelope(
385
+ error=message,
386
+ code="unknown_command",
387
+ hint=hint,
388
+ schema="gitwise/schema/v1",
389
+ kind="schema",
390
+ )
391
+ )
392
+ else:
393
+ from .output import error as _error
394
+
395
+ _error(message, hint=hint)
396
+ return 1
397
+
398
+ name = canonical_command_name(command_parser)
399
+ schema = load_command_input_schema(command=name, version=args.version)
400
+ if schema is None:
401
+ message = t("schema_file_missing", command=name, version=args.version)
402
+ hint = t("schema_file_missing_hint")
403
+ if args.json:
404
+ print_json(
405
+ error_envelope(
406
+ error=message,
407
+ code="schema_not_found",
408
+ hint=hint,
409
+ schema="gitwise/schema/v1",
410
+ kind="schema",
411
+ )
412
+ )
413
+ else:
414
+ from .output import error as _error
415
+
416
+ _error(message, hint=hint)
417
+ return 1
418
+
419
+ payload = {
420
+ "v": 2,
421
+ "ok": True,
422
+ "kind": "schema",
423
+ "schema": "gitwise/schema/v1",
424
+ "version": __version__,
425
+ "schema_version": args.version,
426
+ "command": name,
427
+ "schema_kind": "cli_input",
428
+ "json_schema": schema,
429
+ }
430
+
431
+ print_json(payload)
432
+ return 0
433
+
434
+
435
+ DISPATCH: dict[str, Callable[[argparse.Namespace], int]] = {
436
+ "doctor": _run_doctor,
437
+ "setup-agents": _run_setup_agents,
438
+ "setup": _run_setup,
439
+ "audit": _run_audit,
440
+ "summarize": _run_summarize,
441
+ "snapshot": _run_snapshot,
442
+ "clean": _run_clean,
443
+ "branch-clean": _run_clean,
444
+ "optimize": _run_optimize,
445
+ "worktree": _run_worktree,
446
+ "diff": _run_diff,
447
+ "log": _run_log,
448
+ "show": _run_show,
449
+ "commit": _run_commit,
450
+ "branches": _run_branches,
451
+ "sync": _run_sync,
452
+ "pr": _run_pr,
453
+ "undo": _run_undo,
454
+ "context": _run_context,
455
+ "health": _run_health,
456
+ "stash": _run_stash,
457
+ "tag": _run_tag,
458
+ "merge": _run_merge,
459
+ "conflicts": _run_conflicts,
460
+ "suggest": _run_suggest,
461
+ "commit-suggest": _run_suggest,
462
+ "pick": _run_pick,
463
+ "cherry-pick": _run_pick,
464
+ "update": _run_update,
465
+ "status": _run_status,
466
+ "completions": _run_completions,
467
+ "commands": _run_commands,
468
+ "schema": _run_schema,
469
+ }