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
@@ -0,0 +1,275 @@
1
+ """CLI introspection: help payload, command metadata, JSON schema generation."""
2
+
3
+ import argparse
4
+ from typing import TypedDict
5
+
6
+ from . import __version__
7
+
8
+
9
+ def _json_safe(value: object) -> object:
10
+ if value is argparse.SUPPRESS:
11
+ return None
12
+ if value is None or isinstance(value, str | int | float | bool):
13
+ return value
14
+ if isinstance(value, list | tuple | set):
15
+ return [_json_safe(item) for item in value]
16
+ if isinstance(value, dict):
17
+ return {str(key): _json_safe(item) for key, item in value.items()}
18
+ return str(value)
19
+
20
+
21
+ def _subparsers_action(parser: argparse.ArgumentParser) -> argparse._SubParsersAction | None:
22
+ for action in parser._actions:
23
+ if isinstance(action, argparse._SubParsersAction):
24
+ return action
25
+ return None
26
+
27
+
28
+ def _serialize_actions(parser: argparse.ArgumentParser) -> list[dict[str, object]]:
29
+ items: list[dict[str, object]] = []
30
+ for action in parser._actions:
31
+ if action.dest in {"help"}:
32
+ continue
33
+ if isinstance(action, argparse._SubParsersAction):
34
+ continue
35
+ option_strings = list(action.option_strings)
36
+ items.append(
37
+ {
38
+ "kind": "option" if option_strings else "argument",
39
+ "name": action.dest,
40
+ "flags": option_strings,
41
+ "required": bool(getattr(action, "required", False)),
42
+ "nargs": _json_safe(action.nargs),
43
+ "default": _json_safe(action.default),
44
+ "choices": _json_safe(list(action.choices) if action.choices else []),
45
+ "help": "" if action.help is argparse.SUPPRESS else (action.help or ""),
46
+ }
47
+ )
48
+ return items
49
+
50
+
51
+ def extract_command_token(argv: list[str]) -> str | None:
52
+ skip_next = False
53
+ for token in argv:
54
+ if skip_next:
55
+ skip_next = False
56
+ continue
57
+ if token in {"--lang", "--theme"}:
58
+ skip_next = True
59
+ continue
60
+ if token.startswith("-"):
61
+ continue
62
+ return token
63
+ return None
64
+
65
+
66
+ def help_payload(parser: argparse.ArgumentParser, command: str | None = None) -> dict[str, object]:
67
+ payload: dict[str, object] = {
68
+ "v": 2,
69
+ "ok": True,
70
+ "kind": "help",
71
+ "schema": "gitwise/help/v1",
72
+ "version": __version__,
73
+ }
74
+
75
+ sub_action = _subparsers_action(parser)
76
+ if command is None or sub_action is None or command not in sub_action.choices:
77
+ commands: list[dict[str, object]] = []
78
+ if sub_action is not None:
79
+ help_by_parser_id: dict[int, str] = {}
80
+ for pseudo in sub_action._choices_actions:
81
+ parser_name = str(pseudo.dest)
82
+ parser_obj = sub_action.choices.get(parser_name)
83
+ if parser_obj is None:
84
+ continue
85
+ parser_id = id(parser_obj)
86
+ if parser_id not in help_by_parser_id:
87
+ help_by_parser_id[parser_id] = pseudo.help or ""
88
+
89
+ seen_parsers: set[int] = set()
90
+ for name, choice_parser in sorted(sub_action.choices.items()):
91
+ parser_id = id(choice_parser)
92
+ if parser_id in seen_parsers:
93
+ continue
94
+ seen_parsers.add(parser_id)
95
+ aliases = [
96
+ alias
97
+ for alias, alias_parser in sub_action.choices.items()
98
+ if alias_parser is choice_parser and alias != name
99
+ ]
100
+ commands.append(
101
+ {
102
+ "name": name,
103
+ "help": help_by_parser_id.get(parser_id, choice_parser.description or ""),
104
+ "aliases": sorted(aliases),
105
+ }
106
+ )
107
+
108
+ payload.update(
109
+ {
110
+ "scope": "root",
111
+ "usage": parser.format_usage().strip(),
112
+ "description": parser.description or "",
113
+ "options": _serialize_actions(parser),
114
+ "commands": sorted(commands, key=lambda item: str(item["name"])),
115
+ }
116
+ )
117
+ return payload
118
+
119
+ command_parser = sub_action.choices[command]
120
+ payload.update(
121
+ {
122
+ "scope": "command",
123
+ "command": command,
124
+ "usage": command_parser.format_usage().strip(),
125
+ "description": command_parser.description or "",
126
+ "options": _serialize_actions(command_parser),
127
+ }
128
+ )
129
+ return payload
130
+
131
+
132
+ class CommandMetadata(TypedDict):
133
+ name: str
134
+ help: str
135
+ aliases: list[str]
136
+ supports_json: bool
137
+
138
+
139
+ def canonical_command_name(command_parser: argparse.ArgumentParser) -> str:
140
+ prog = command_parser.prog.strip()
141
+ if not prog:
142
+ return ""
143
+ parts = prog.split()
144
+ return parts[-1] if parts else ""
145
+
146
+
147
+ def commands_metadata(parser: argparse.ArgumentParser) -> list[CommandMetadata]:
148
+ sub_action = _subparsers_action(parser)
149
+ if sub_action is None:
150
+ return []
151
+
152
+ help_by_parser_id: dict[int, str] = {}
153
+ for pseudo in sub_action._choices_actions:
154
+ parser_name = str(pseudo.dest)
155
+ parser_obj = sub_action.choices.get(parser_name)
156
+ if parser_obj is None:
157
+ continue
158
+ parser_id = id(parser_obj)
159
+ if parser_id not in help_by_parser_id:
160
+ help_by_parser_id[parser_id] = pseudo.help or ""
161
+
162
+ entries: list[CommandMetadata] = []
163
+ seen_parser_ids: set[int] = set()
164
+ for command_parser in sub_action.choices.values():
165
+ parser_id = id(command_parser)
166
+ if parser_id in seen_parser_ids:
167
+ continue
168
+ seen_parser_ids.add(parser_id)
169
+
170
+ name = canonical_command_name(command_parser)
171
+ aliases = sorted(
172
+ [
173
+ n
174
+ for n, candidate in sub_action.choices.items()
175
+ if candidate is command_parser and n != name
176
+ ]
177
+ )
178
+
179
+ entries.append(
180
+ {
181
+ "name": name,
182
+ "help": help_by_parser_id.get(parser_id, command_parser.description or ""),
183
+ "aliases": aliases,
184
+ "supports_json": True,
185
+ }
186
+ )
187
+
188
+ return sorted(entries, key=lambda item: str(item["name"]))
189
+
190
+
191
+ def _json_type_for_action(action: argparse.Action) -> str:
192
+ if isinstance(action, argparse._StoreTrueAction | argparse._StoreFalseAction):
193
+ return "boolean"
194
+
195
+ action_type = getattr(action, "type", None)
196
+ if action_type is int:
197
+ return "integer"
198
+ if action_type is float:
199
+ return "number"
200
+ return "string"
201
+
202
+
203
+ def _action_property_schema(action: argparse.Action) -> dict[str, object]:
204
+ value_schema: dict[str, object] = {"type": _json_type_for_action(action)}
205
+
206
+ if action.choices:
207
+ value_schema["enum"] = [_json_safe(choice) for choice in action.choices]
208
+
209
+ description = "" if action.help is argparse.SUPPRESS else (action.help or "")
210
+ if description:
211
+ value_schema["description"] = description
212
+
213
+ if action.default is not argparse.SUPPRESS and action.default is not None:
214
+ value_schema["default"] = _json_safe(action.default)
215
+
216
+ nargs = action.nargs
217
+ if nargs in ("*", "+") or (isinstance(nargs, int) and nargs > 1):
218
+ array_schema: dict[str, object] = {
219
+ "type": "array",
220
+ "items": value_schema,
221
+ }
222
+ if nargs == "+":
223
+ array_schema["minItems"] = 1
224
+ if isinstance(nargs, int) and nargs > 1:
225
+ array_schema["minItems"] = nargs
226
+ array_schema["maxItems"] = nargs
227
+ return array_schema
228
+
229
+ return value_schema
230
+
231
+
232
+ def _action_required(action: argparse.Action) -> bool:
233
+ if action.option_strings:
234
+ return bool(getattr(action, "required", False))
235
+
236
+ nargs = action.nargs
237
+ if nargs in (None, "+"):
238
+ return True
239
+ if isinstance(nargs, int):
240
+ return nargs > 0
241
+ return False
242
+
243
+
244
+ def command_input_schema(command_parser: argparse.ArgumentParser) -> dict[str, object]:
245
+ properties: dict[str, object] = {}
246
+ required: list[str] = []
247
+
248
+ for action in command_parser._actions:
249
+ if action.dest == "help" or isinstance(action, argparse._SubParsersAction):
250
+ continue
251
+ properties[action.dest] = _action_property_schema(action)
252
+ if _action_required(action):
253
+ required.append(action.dest)
254
+
255
+ schema: dict[str, object] = {
256
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
257
+ "$id": f"https://gitwise.dev/schemas/v1/input/{canonical_command_name(command_parser)}.json",
258
+ "title": f"gitwise {canonical_command_name(command_parser)} cli input",
259
+ "type": "object",
260
+ "additionalProperties": False,
261
+ "properties": properties,
262
+ }
263
+ if required:
264
+ schema["required"] = sorted(set(required))
265
+
266
+ return schema
267
+
268
+
269
+ def resolve_command_parser(
270
+ *, parser: argparse.ArgumentParser, name: str
271
+ ) -> argparse.ArgumentParser | None:
272
+ sub_action = _subparsers_action(parser)
273
+ if sub_action is None:
274
+ return None
275
+ return sub_action.choices.get(name)
gitwise/_cli_parser.py ADDED
@@ -0,0 +1,345 @@
1
+ """Argparse parser builder for all gitwise subcommands."""
2
+
3
+ import argparse
4
+
5
+ from . import __version__
6
+ from .design import GitwiseRichHelpFormatter
7
+ from .i18n import t
8
+
9
+
10
+ def _root_help_epilog() -> str:
11
+ return t("help_root_environment_epilog")
12
+
13
+
14
+ def build_parser() -> argparse.ArgumentParser:
15
+ parent = argparse.ArgumentParser(add_help=False)
16
+ parent.add_argument(
17
+ "--lang",
18
+ choices=["es", "en"],
19
+ default=None,
20
+ help="output language (default: auto-detect from locale)",
21
+ )
22
+ parent.add_argument(
23
+ "--theme",
24
+ choices=["dark", "light", "auto"],
25
+ default=None,
26
+ help="color theme: dark, light, or auto-detect (default: auto)",
27
+ )
28
+ parent.add_argument("--json", action="store_true", help="output JSON")
29
+ parent.add_argument(
30
+ "--json-pretty",
31
+ "--pretty",
32
+ dest="json_pretty",
33
+ action="store_true",
34
+ help="pretty-print JSON output",
35
+ )
36
+
37
+ parser = argparse.ArgumentParser(
38
+ prog="gitwise",
39
+ description="CLI for optimizing git workflows and coding agents integration",
40
+ formatter_class=GitwiseRichHelpFormatter,
41
+ epilog=_root_help_epilog(),
42
+ parents=[parent],
43
+ )
44
+ parser.add_argument("--version", action="version", version=f"gitwise {__version__}")
45
+
46
+ sub = parser.add_subparsers(dest="command", metavar="COMMAND")
47
+
48
+ p = sub.add_parser("doctor", help="check requirements and environment", parents=[parent])
49
+
50
+ p = sub.add_parser(
51
+ "setup-agents",
52
+ help="install canonical agents layout + optional providers globally or locally",
53
+ parents=[parent],
54
+ )
55
+ p.add_argument(
56
+ "--local",
57
+ action="store_true",
58
+ help="install into current repo instead of global home",
59
+ )
60
+ p.add_argument(
61
+ "--no-skills",
62
+ action="store_true",
63
+ dest="no_skills",
64
+ help="skip skills installation (global mode only)",
65
+ )
66
+ p.add_argument("--dry-run", action="store_true", help="show actions without executing")
67
+ p.add_argument("--yes", "-y", action="store_true", help="skip confirmation")
68
+ p.add_argument(
69
+ "--no-symlinks",
70
+ action="store_true",
71
+ help="force @AGENTS.md import fallback (no symlinks) — --local only",
72
+ )
73
+ p.add_argument(
74
+ "--strict",
75
+ action="store_true",
76
+ help="treat warnings as errors (CI) — --local only",
77
+ )
78
+ p.add_argument(
79
+ "--replace-claude-with-symlink",
80
+ action="store_true",
81
+ dest="replace_claude_with_symlink",
82
+ help="bucket 4: replace CLAUDE.md with symlink to AGENTS.md — --local only",
83
+ )
84
+ p.add_argument(
85
+ "--migrate-legacy-claude",
86
+ action="store_true",
87
+ dest="migrate_legacy_claude",
88
+ help="migrate legacy Claude-only layout to canonical AGENTS/.agents — --local only",
89
+ )
90
+ p.add_argument(
91
+ "--frozen-time",
92
+ action="store_true",
93
+ dest="frozen_time",
94
+ help="freeze snapshot timestamp — --local only",
95
+ )
96
+ p.add_argument(
97
+ "--no-git-files",
98
+ action="store_true",
99
+ dest="no_git_files",
100
+ help="don't touch .gitignore or .gitattributes — --local only",
101
+ )
102
+ p.add_argument(
103
+ "--providers",
104
+ nargs="*",
105
+ default=None,
106
+ dest="providers",
107
+ help="install config for coding providers (comma-separated: claude,cursor or multiple: --providers claude cursor)",
108
+ )
109
+ p.add_argument(
110
+ "--adapters",
111
+ nargs="*",
112
+ default=None,
113
+ dest="adapters",
114
+ help=argparse.SUPPRESS,
115
+ )
116
+ p.add_argument(
117
+ "--list-providers",
118
+ action="store_true",
119
+ dest="list_providers",
120
+ help="list available providers and exit",
121
+ )
122
+ p.add_argument(
123
+ "--list-adapters",
124
+ action="store_true",
125
+ dest="list_adapters",
126
+ help=argparse.SUPPRESS,
127
+ )
128
+
129
+ p = sub.add_parser("setup", help="apply modern git defaults", parents=[parent])
130
+ p.add_argument("--dry-run", action="store_true")
131
+ p.add_argument("--yes", "-y", action="store_true")
132
+ p.add_argument(
133
+ "--hooks-mode",
134
+ choices=["preserve", "native", "legacy", "skip"],
135
+ default="preserve",
136
+ help="hooks strategy: preserve (default), native, legacy, or skip",
137
+ )
138
+
139
+ p = sub.add_parser("audit", help="repository diagnostics", parents=[parent])
140
+ p.add_argument("--quick", action="store_true")
141
+
142
+ p = sub.add_parser("summarize", help="compact status + log", parents=[parent])
143
+ p.add_argument("--diff", action="store_true")
144
+ p.add_argument("--max-commits", type=int, default=10, dest="max_commits")
145
+
146
+ p = sub.add_parser("snapshot", help="generate .claude/git-snapshot.md", parents=[parent])
147
+
148
+ p = sub.add_parser(
149
+ "clean",
150
+ help="clean up stale branches and refs",
151
+ aliases=["branch-clean"],
152
+ parents=[parent],
153
+ )
154
+ p.add_argument("--branches", action="store_true")
155
+ p.add_argument("--refs", action="store_true")
156
+ p.add_argument("--dry-run", action="store_true")
157
+ p.add_argument("--yes", "-y", action="store_true")
158
+
159
+ p = sub.add_parser("optimize", help="optimize the repository", parents=[parent])
160
+ p.add_argument("--dry-run", action="store_true")
161
+ p.add_argument("--yes", "-y", action="store_true")
162
+
163
+ p = sub.add_parser("worktree", help="worktree helpers for Claude agents", parents=[parent])
164
+ p.add_argument("action", choices=["new", "clean"], nargs="?", metavar="new|clean")
165
+ p.add_argument("branch", nargs="?")
166
+ p.add_argument("--dry-run", action="store_true")
167
+
168
+ p = sub.add_parser(
169
+ "diff",
170
+ help="changed files with diffstat (default), or patch view",
171
+ parents=[parent],
172
+ )
173
+ diff_group = p.add_mutually_exclusive_group()
174
+ diff_group.add_argument("--staged", action="store_true", help="show staged changes only")
175
+ diff_group.add_argument("--name-only", action="store_true", help="show only file names")
176
+ diff_group.add_argument(
177
+ "--full", "--patch", action="store_true", help="show full patch with delta integration"
178
+ )
179
+ p.add_argument("--stat", action="store_true", help="show diffstat (default behavior)")
180
+
181
+ p = sub.add_parser("log", help="pretty git log with filters", parents=[parent])
182
+ p.add_argument("--oneline", action="store_true", help="one line per commit")
183
+ p.add_argument("--graph", action="store_true", help="show branch topology graph")
184
+ p.add_argument("--author", type=str, default=None, help="filter by author")
185
+ p.add_argument("--grep", type=str, default=None, help="filter by message pattern")
186
+ p.add_argument("--since", type=str, default=None, help="show commits since date")
187
+ p.add_argument("--until", type=str, default=None, help="show commits until date")
188
+ p.add_argument("--file", type=str, default=None, help="show commits for file")
189
+ p.add_argument("--max-count", type=int, default=20, dest="max_count", help="max commits")
190
+
191
+ p = sub.add_parser("show", help="commit inspector", parents=[parent])
192
+ p.add_argument("ref", nargs="?", default="HEAD", help="commit ref (default: HEAD)")
193
+ p.add_argument("--stat", action="store_true", help="show diffstat")
194
+
195
+ p = sub.add_parser("commit", help="smart conventional commit", parents=[parent])
196
+ p.add_argument("-m", "--message", type=str, default=None, help="commit message")
197
+ p.add_argument("--type", type=str, default=None, help="commit type (feat/fix/etc)")
198
+ p.add_argument("--scope", type=str, default=None, help="commit scope")
199
+ p.add_argument("--breaking", action="store_true", help="breaking change (!)")
200
+ p.add_argument("--amend", action="store_true", help="amend last commit")
201
+ p.add_argument("--dry-run", action="store_true", help="show without committing")
202
+
203
+ p = sub.add_parser("branches", help="branch intelligence dashboard", parents=[parent])
204
+ p.add_argument("--stale", action="store_true", help="show stale [gone] branches only")
205
+ p.add_argument("--remote", action="store_true", help="show remote branches")
206
+ p.add_argument(
207
+ "--sort",
208
+ type=str,
209
+ default="refname",
210
+ help="sort field: refname, committerdate, -committerdate",
211
+ )
212
+
213
+ p = sub.add_parser("sync", help="remote fetch, safe pull/push", parents=[parent])
214
+ p.add_argument("--pull", action="store_true", help="pull --ff-only after fetch")
215
+ p.add_argument("--push", action="store_true", help="push unpushed commits")
216
+ p.add_argument("--remote", type=str, default=None, help="specific remote (default: all)")
217
+ p.add_argument("--dry-run", action="store_true", help="show planned actions")
218
+
219
+ p = sub.add_parser("pr", help="GitHub PR wrapper (requires gh)", parents=[parent])
220
+ p.add_argument(
221
+ "action",
222
+ nargs="?",
223
+ default="list",
224
+ choices=["list", "checks", "view", "comments"],
225
+ help="pr action",
226
+ )
227
+ p.add_argument(
228
+ "selector",
229
+ nargs="?",
230
+ default=None,
231
+ help="PR number/url/branch (default: current branch PR)",
232
+ )
233
+
234
+ p = sub.add_parser("undo", help="reflog-based undo", parents=[parent])
235
+ p.add_argument("ref", nargs="?", default=None, help="target ref (default: HEAD~1)")
236
+ p.add_argument("--soft", action="store_true", help="soft reset (keep working tree)")
237
+ p.add_argument("--steps", type=int, default=1, help="number of steps back")
238
+ p.add_argument("--dry-run", action="store_true", help="show without resetting")
239
+ p.add_argument("--yes", "-y", action="store_true", help="skip confirmation for --hard")
240
+
241
+ p = sub.add_parser("context", help="enriched repo snapshot for LLMs", parents=[parent])
242
+
243
+ p = sub.add_parser("health", help="repo health score (0-100)", parents=[parent])
244
+
245
+ p = sub.add_parser(
246
+ "stash",
247
+ help="manage stashes (list/show/pop/drop/clear)",
248
+ parents=[parent],
249
+ )
250
+ p.add_argument(
251
+ "action",
252
+ nargs="?",
253
+ default="list",
254
+ choices=["list", "show", "pop", "drop", "clear", "clean"],
255
+ )
256
+ p.add_argument("--index", type=int, default=0, help="stash index (default: 0)")
257
+ p.add_argument("--dry-run", action="store_true", help="dry run (clear only)")
258
+ p.add_argument("--yes", "-y", action="store_true", help="skip confirmation")
259
+ p.add_argument("--patch", action="store_true", help="show full patch (show only)")
260
+
261
+ p = sub.add_parser("tag", help="semver-aware tag management", parents=[parent])
262
+ p.add_argument(
263
+ "action", nargs="?", default="list", choices=["list", "latest", "create", "delete"]
264
+ )
265
+ p.add_argument("name", nargs="?", help="tag name (for create/delete)")
266
+ p.add_argument("--bump", choices=["major", "minor", "patch"], help="bump semver part")
267
+ p.add_argument("-m", "--message", type=str, help="annotated tag message")
268
+ p.add_argument("--dry-run", action="store_true", help="show without executing")
269
+ p.add_argument("--yes", "-y", action="store_true", help="skip confirmation")
270
+
271
+ p = sub.add_parser("merge", help="merge/rebase with pre-flight checks", parents=[parent])
272
+ p.add_argument("branch", help="branch to merge/rebase from")
273
+ p.add_argument("--rebase", action="store_true", help="rebase instead of merge")
274
+ p.add_argument("--no-ff", action="store_true", help="force no-fast-forward")
275
+ p.add_argument("--dry-run", action="store_true", help="show checks without merging")
276
+ p.add_argument("--yes", "-y", action="store_true", help="skip confirmation")
277
+
278
+ p = sub.add_parser(
279
+ "conflicts", help="conflict detection and resolution helper", parents=[parent]
280
+ )
281
+ p.add_argument("--ours", action="store_true", help="resolve all conflicts using ours")
282
+ p.add_argument("--theirs", action="store_true", help="resolve all conflicts using theirs")
283
+
284
+ p = sub.add_parser(
285
+ "suggest",
286
+ help="suggest commit message from staged diff",
287
+ aliases=["commit-suggest"],
288
+ parents=[parent],
289
+ )
290
+
291
+ p = sub.add_parser(
292
+ "pick", help="cherry-pick or revert commits", aliases=["cherry-pick"], parents=[parent]
293
+ )
294
+ p.add_argument("refs", nargs="*", help="commit refs to pick/revert")
295
+ p.add_argument("--revert", action="store_true", help="revert instead of cherry-pick")
296
+ p.add_argument(
297
+ "--continue", dest="continue_", action="store_true", help="continue after conflict"
298
+ )
299
+ p.add_argument("--abort", action="store_true", help="abort in-progress pick/revert")
300
+ p.add_argument("--dry-run", action="store_true", help="show without executing")
301
+
302
+ p = sub.add_parser(
303
+ "update", help="update gitwise (git pull in install directory)", parents=[parent]
304
+ )
305
+ p.add_argument("--dry-run", action="store_true")
306
+
307
+ p = sub.add_parser("status", help="enhanced git status for humans and AI", parents=[parent])
308
+
309
+ p = sub.add_parser(
310
+ "commands",
311
+ help="list available subcommands and metadata",
312
+ parents=[parent],
313
+ )
314
+
315
+ p = sub.add_parser(
316
+ "schema",
317
+ help="print JSON schema for a command",
318
+ parents=[parent],
319
+ )
320
+ p.add_argument("name", help="command name to inspect")
321
+ p.add_argument(
322
+ "--version",
323
+ default="v1",
324
+ help="schema catalog version (default: v1)",
325
+ )
326
+
327
+ p = sub.add_parser(
328
+ "completions",
329
+ help="print shell completion script (bash/zsh/fish)",
330
+ parents=[parent],
331
+ )
332
+ p.add_argument(
333
+ "shell",
334
+ choices=["bash", "zsh", "fish"],
335
+ default="bash",
336
+ nargs="?",
337
+ help="target shell for completion script",
338
+ )
339
+ p.add_argument(
340
+ "--prog",
341
+ default="gitwise",
342
+ help="command name used inside completion script (default: gitwise)",
343
+ )
344
+
345
+ return parser