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,439 @@
1
+ """Injects AGENTS.md awareness, CLAUDE.md, settings.json, slash-commands, and initial snapshot."""
2
+
3
+ from pathlib import Path
4
+
5
+ from gitwise.git import config as git_config
6
+ from gitwise.git import is_repo, repo_root
7
+ from gitwise.i18n import t
8
+ from gitwise.output import confirm, error, info, ok, print_json, warn
9
+ from gitwise.setup_agents import (
10
+ _SKILLS,
11
+ PlanExecutionError,
12
+ _execute_actions,
13
+ _plan_actions,
14
+ _plan_actions_global,
15
+ )
16
+ from gitwise.setup_agents.format import (
17
+ format_json_output_global,
18
+ format_json_output_global_error,
19
+ format_json_output_local,
20
+ format_json_output_local_error,
21
+ )
22
+ from gitwise.setup_agents.providers import detect_global_skills
23
+ from gitwise.setup_agents.providers.base import AdapterContext
24
+ from gitwise.setup_agents.state import _AGENTS_MD, _gpg_ready
25
+ from gitwise.setup_agents.types import StateDict
26
+
27
+
28
+ def _run_setup_global(
29
+ home: Path,
30
+ *,
31
+ dry_run: bool = False,
32
+ yes: bool = False,
33
+ as_json: bool = False,
34
+ no_skills: bool = False,
35
+ providers: list[str] | None = None,
36
+ no_symlinks: bool = False,
37
+ strict: bool = False,
38
+ migrate_legacy_claude: bool = False,
39
+ ) -> int:
40
+ """Installs global setup-agents artifacts to home config dirs. No git repo required."""
41
+ agents_dir = home / ".agents"
42
+ has_agents_dir = agents_dir.is_dir() and not agents_dir.is_symlink()
43
+ has_errors = False
44
+ errors: list[str] = []
45
+ all_warnings: list[str] = []
46
+ try:
47
+ actions, warnings, _ = _plan_actions_global(home, no_skills=no_skills)
48
+ except FileNotFoundError as e:
49
+ if as_json:
50
+ print_json(
51
+ format_json_output_global_error(
52
+ home=home,
53
+ warnings=[],
54
+ errors=[str(e)],
55
+ has_agents_dir=has_agents_dir,
56
+ dry_run=dry_run,
57
+ )
58
+ )
59
+ return 1
60
+ error(str(e))
61
+ return 1
62
+
63
+ all_warnings.extend(warnings)
64
+
65
+ if providers:
66
+ from gitwise.setup_agents.providers import plan_adapter_actions
67
+
68
+ expanded = []
69
+ for adapter in providers:
70
+ expanded.extend(part.strip() for part in adapter.split(",") if part.strip())
71
+ if "claude-only" in expanded:
72
+ all_warnings.append(
73
+ t("adapter_alias_deprecated", alias="claude-only", target="claude")
74
+ )
75
+ state: StateDict = {
76
+ "a_state": "absent",
77
+ "c_state": "regular",
78
+ "agents_dir": has_agents_dir,
79
+ "skills_state": "regular",
80
+ "skills_target": None,
81
+ "supports_symlinks": True,
82
+ "errors": [],
83
+ "rules_warnings": [],
84
+ }
85
+ adapter_context: AdapterContext = {
86
+ "state": state,
87
+ "canonical_doc_path": _AGENTS_MD,
88
+ "global_skills": detect_global_skills(home),
89
+ "supports_symlinks": True,
90
+ "gpg_ready": False,
91
+ "flags": {
92
+ "no_symlinks": no_symlinks,
93
+ "replace_claude_with_symlink": False,
94
+ "migrate_legacy_claude": migrate_legacy_claude,
95
+ "frozen_time": False,
96
+ "no_git_files": False,
97
+ "core_claude_planned": True,
98
+ },
99
+ }
100
+ adapter_actions, adapter_errors, adapter_warnings = plan_adapter_actions(
101
+ expanded,
102
+ home,
103
+ context=adapter_context,
104
+ )
105
+ if adapter_errors:
106
+ errors.extend(adapter_errors)
107
+ has_errors = True
108
+ actions.extend(adapter_actions)
109
+ all_warnings.extend(adapter_warnings)
110
+
111
+ if strict and all_warnings:
112
+ errors.append(t("strict_warnings"))
113
+ has_errors = True
114
+
115
+ if as_json:
116
+ if has_errors:
117
+ print_json(
118
+ format_json_output_global_error(
119
+ home=home,
120
+ warnings=all_warnings,
121
+ errors=errors,
122
+ has_agents_dir=has_agents_dir,
123
+ dry_run=dry_run,
124
+ )
125
+ )
126
+ return 1
127
+ print_json(
128
+ format_json_output_global(
129
+ home=home,
130
+ actions=actions,
131
+ warnings=all_warnings,
132
+ has_agents_dir=has_agents_dir,
133
+ dry_run=dry_run,
134
+ )
135
+ )
136
+ return 0
137
+
138
+ info(t("configuring_agents_global", path=str(home / ".claude")))
139
+ info("")
140
+
141
+ for w in all_warnings:
142
+ warn(w)
143
+ if all_warnings:
144
+ info("")
145
+
146
+ if has_errors:
147
+ for err_text in errors:
148
+ error(err_text)
149
+ return 1
150
+
151
+ if dry_run:
152
+ info(t("dry_run_nothing"))
153
+ info("")
154
+ for a in actions:
155
+ verb = a["action"].upper()
156
+ reason = f" ({a['reason']})" if "reason" in a else ""
157
+ info(f" [{verb}] {a['file']}{reason}")
158
+ return 0
159
+
160
+ if not yes:
161
+ info(t("actions_to_perform"))
162
+ for a in actions:
163
+ if a["action"] not in ("skip", "symlink-skip", "managed-block-skip"):
164
+ info(f" [{a['action'].upper()}] {a['file']}")
165
+ info("")
166
+ if not confirm(t("continue_prompt")):
167
+ info(t("cancelled"))
168
+ return 0
169
+ info("")
170
+
171
+ try:
172
+ _execute_actions(home, actions)
173
+ except PlanExecutionError as e:
174
+ error(t("setup_agents_global_failed", error=str(e)))
175
+ return 1
176
+
177
+ info("")
178
+ ok(t("setup_agents_global_complete"))
179
+ return 0
180
+
181
+
182
+ def _run_setup_local(
183
+ target: Path | None = None,
184
+ *,
185
+ dry_run: bool = False,
186
+ yes: bool = False,
187
+ as_json: bool = False,
188
+ no_symlinks: bool = False,
189
+ strict: bool = False,
190
+ replace_claude_with_symlink: bool = False,
191
+ migrate_legacy_claude: bool = False,
192
+ frozen_time: bool = False,
193
+ no_git_files: bool = False,
194
+ providers: list[str] | None = None,
195
+ adapters_legacy_used: bool = False,
196
+ ) -> int:
197
+ cwd = target or Path.cwd()
198
+
199
+ if not is_repo(cwd):
200
+ error(t("not_a_git_repo"))
201
+ return 1
202
+
203
+ root = repo_root(cwd)
204
+ if root is None:
205
+ error(t("no_repo_root"))
206
+ return 1
207
+
208
+ gpgsign = git_config("commit.gpgsign", cwd=root)
209
+ gpg_warnings: list[str] = []
210
+ if gpgsign == "true":
211
+ signing_key = git_config("user.signingkey", cwd=root)
212
+ if not signing_key:
213
+ gpg_warnings.append(t("gpg_active_no_key_repo"))
214
+
215
+ provider_tokens: list[str] = []
216
+ if providers:
217
+ for provider in providers:
218
+ provider_tokens.extend(part.strip() for part in provider.split(",") if part.strip())
219
+ force_claude_core = any(token in ("claude", "claude-only") for token in provider_tokens)
220
+
221
+ try:
222
+ actions, warnings, plan_errors, bucket, state = _plan_actions(
223
+ root,
224
+ no_symlinks=no_symlinks,
225
+ replace_claude_with_symlink=replace_claude_with_symlink,
226
+ migrate_legacy_claude=migrate_legacy_claude,
227
+ force_claude_core=force_claude_core,
228
+ no_git_files=no_git_files,
229
+ frozen_time=frozen_time,
230
+ )
231
+ except FileNotFoundError as e:
232
+ error(str(e))
233
+ return 1
234
+
235
+ has_errors = bool(plan_errors)
236
+ all_warnings = gpg_warnings + warnings
237
+ global_skills = detect_global_skills()
238
+
239
+ if adapters_legacy_used:
240
+ all_warnings.append(
241
+ t("adapter_alias_deprecated", alias="--adapters", target="--providers")
242
+ )
243
+
244
+ if providers:
245
+ from gitwise.setup_agents.providers import plan_adapter_actions
246
+
247
+ expanded = provider_tokens
248
+ if "claude-only" in expanded:
249
+ all_warnings.append(
250
+ t("adapter_alias_deprecated", alias="claude-only", target="claude")
251
+ )
252
+ adapter_context: AdapterContext = {
253
+ "state": state,
254
+ "canonical_doc_path": _AGENTS_MD,
255
+ "global_skills": global_skills,
256
+ "supports_symlinks": state["supports_symlinks"],
257
+ "gpg_ready": _gpg_ready(root),
258
+ "flags": {
259
+ "no_symlinks": no_symlinks,
260
+ "replace_claude_with_symlink": replace_claude_with_symlink,
261
+ "migrate_legacy_claude": migrate_legacy_claude,
262
+ "frozen_time": frozen_time,
263
+ "no_git_files": no_git_files,
264
+ "core_claude_planned": force_claude_core,
265
+ },
266
+ }
267
+ adapter_actions, adapter_errors, adapter_warnings = plan_adapter_actions(
268
+ expanded, root, context=adapter_context
269
+ )
270
+ if adapter_errors:
271
+ plan_errors.extend({"reason": e, "file": ""} for e in adapter_errors)
272
+ has_errors = True
273
+ actions.extend(adapter_actions)
274
+ all_warnings.extend(adapter_warnings)
275
+
276
+ if strict and all_warnings and as_json:
277
+ plan_errors.append({"reason": t("strict_warnings"), "file": ""})
278
+ has_errors = True
279
+
280
+ if as_json:
281
+ if has_errors:
282
+ print_json(
283
+ format_json_output_local_error(
284
+ root=root,
285
+ dry_run=dry_run,
286
+ plan_errors=plan_errors,
287
+ all_warnings=all_warnings,
288
+ migrate_legacy_claude=migrate_legacy_claude,
289
+ )
290
+ )
291
+ return 1
292
+
293
+ print_json(
294
+ format_json_output_local(
295
+ root=root,
296
+ dry_run=dry_run,
297
+ bucket=bucket,
298
+ actions=actions,
299
+ all_warnings=all_warnings,
300
+ rules_warnings=state["rules_warnings"],
301
+ state=state,
302
+ migrate_legacy_claude=migrate_legacy_claude,
303
+ )
304
+ )
305
+ return 0
306
+
307
+ if has_errors:
308
+ for e in plan_errors:
309
+ error(e["reason"])
310
+ return 1
311
+
312
+ info(t("configuring_agents_in", root=str(root)))
313
+ info("")
314
+
315
+ for w in all_warnings:
316
+ warn(w)
317
+
318
+ if strict and all_warnings:
319
+ error(t("strict_warnings"))
320
+ return 2
321
+
322
+ if all_warnings:
323
+ info("")
324
+
325
+ if dry_run:
326
+ info(t("dry_run_nothing"))
327
+ info("")
328
+ for a in actions:
329
+ verb = a["action"].upper()
330
+ reason = f" ({a['reason']})" if "reason" in a else ""
331
+ info(f" [{verb}] {a['file']}{reason}")
332
+ return 0
333
+
334
+ if not yes:
335
+ info(t("actions_to_perform"))
336
+ for a in actions:
337
+ if a["action"] not in ("skip", "symlink-skip", "managed-block-skip"):
338
+ info(f" [{a['action'].upper()}] {a['file']}")
339
+ info("")
340
+ if not confirm(t("continue_prompt")):
341
+ info(t("cancelled"))
342
+ return 0
343
+ info("")
344
+
345
+ try:
346
+ _execute_actions(root, actions)
347
+ except PlanExecutionError as e:
348
+ error(t("setup_agents_failed", error=str(e)))
349
+ return 1
350
+
351
+ skills_skipped = sum(
352
+ 1
353
+ for a in actions
354
+ if a.get("action") == "skip"
355
+ and a.get("file", "").startswith(".claude/skills/")
356
+ and a.get("file", "").endswith("/SKILL.md")
357
+ )
358
+ if skills_skipped == len(_SKILLS):
359
+ ok(t("skills_already_configured", count=str(len(_SKILLS))))
360
+
361
+ info("")
362
+ ok(t("setup_agents_complete"))
363
+
364
+ if strict and all_warnings:
365
+ return 2
366
+
367
+ return 0
368
+
369
+
370
+ def run_setup_agents(
371
+ target: Path | None = None,
372
+ *,
373
+ local: bool = False,
374
+ no_skills: bool = False,
375
+ dry_run: bool = False,
376
+ yes: bool = False,
377
+ as_json: bool = False,
378
+ no_symlinks: bool = False,
379
+ strict: bool = False,
380
+ replace_claude_with_symlink: bool = False,
381
+ migrate_legacy_claude: bool = False,
382
+ frozen_time: bool = False,
383
+ no_git_files: bool = False,
384
+ providers: list[str] | None = None,
385
+ adapters_legacy_used: bool = False,
386
+ ) -> int:
387
+ """Dispatcher: global mode (default) or per-repo mode (--local)."""
388
+ if local:
389
+ return _run_setup_local(
390
+ target,
391
+ dry_run=dry_run,
392
+ yes=yes,
393
+ as_json=as_json,
394
+ no_symlinks=no_symlinks,
395
+ strict=strict,
396
+ replace_claude_with_symlink=replace_claude_with_symlink,
397
+ migrate_legacy_claude=migrate_legacy_claude,
398
+ frozen_time=frozen_time,
399
+ no_git_files=no_git_files,
400
+ providers=providers,
401
+ adapters_legacy_used=adapters_legacy_used,
402
+ )
403
+ if migrate_legacy_claude:
404
+ if as_json:
405
+ print_json(
406
+ format_json_output_global_error(
407
+ home=Path.home(),
408
+ warnings=[],
409
+ errors=[t("migrate_requires_local")],
410
+ has_agents_dir=False,
411
+ dry_run=dry_run,
412
+ )
413
+ )
414
+ return 1
415
+ error(t("migrate_requires_local"))
416
+ return 1
417
+ if providers:
418
+ return _run_setup_global(
419
+ Path.home(),
420
+ dry_run=dry_run,
421
+ yes=yes,
422
+ as_json=as_json,
423
+ no_skills=no_skills,
424
+ providers=providers,
425
+ no_symlinks=no_symlinks,
426
+ strict=strict,
427
+ migrate_legacy_claude=migrate_legacy_claude,
428
+ )
429
+ return _run_setup_global(
430
+ Path.home(),
431
+ dry_run=dry_run,
432
+ yes=yes,
433
+ as_json=as_json,
434
+ no_skills=no_skills,
435
+ providers=None,
436
+ no_symlinks=no_symlinks,
437
+ strict=strict,
438
+ migrate_legacy_claude=migrate_legacy_claude,
439
+ )