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/setup.py ADDED
@@ -0,0 +1,551 @@
1
+ """Applies modern git defaults. NEVER modifies GPG-related config."""
2
+
3
+ import os
4
+ import platform
5
+ from pathlib import Path
6
+ from typing import Literal, TypedDict
7
+
8
+ from ._paths import share_dir as _share_dir
9
+ from .git import config as git_config
10
+ from .git import config_all as git_config_all
11
+ from .git import git_dir, require_root, supports_config_hooks
12
+ from .git import run as git_run
13
+ from .git import version as git_version
14
+ from .i18n import t
15
+ from .output import (
16
+ confirm,
17
+ info,
18
+ ok,
19
+ print_blank,
20
+ print_header,
21
+ print_json,
22
+ print_kv,
23
+ print_status_line,
24
+ warn,
25
+ )
26
+ from .utils.json_envelope import error_envelope, ok_envelope
27
+
28
+ HookMode = Literal["preserve", "native", "legacy", "skip"]
29
+
30
+
31
+ class SetupChange(TypedDict):
32
+ op: Literal["set", "add", "unset"]
33
+ key: str
34
+ desired: str
35
+ current: str | None
36
+ note: str | None
37
+
38
+
39
+ # Modern git defaults (GitButler list, Chacon feb 2025)
40
+ _BASE_CONFIGS: list[tuple[str, str]] = [
41
+ ("fetch.prune", "true"),
42
+ ("fetch.prunetags", "true"),
43
+ ("fetch.all", "true"),
44
+ ("merge.conflictstyle", "zdiff3"),
45
+ ("diff.algorithm", "histogram"),
46
+ ("diff.colorMoved", "default"),
47
+ ("rerere.enabled", "true"),
48
+ ("rerere.autoupdate", "true"),
49
+ ("branch.sort", "-committerdate"),
50
+ ("tag.sort", "-version:refname"),
51
+ ("push.default", "current"),
52
+ ("push.autoSetupRemote", "true"),
53
+ ("commit.verbose", "true"),
54
+ ("maintenance.auto", "false"),
55
+ ("maintenance.strategy", "incremental"),
56
+ ("core.untrackedCache", "true"),
57
+ ("core.preloadindex", "true"),
58
+ ]
59
+
60
+ # These keys are NEVER modified by gitwise setup
61
+ _PROTECTED_KEYS = {"commit.gpgsign", "user.signingkey", "user.email", "user.name"}
62
+
63
+ _NATIVE_HOOKS: tuple[tuple[str, str], ...] = (
64
+ ("gitwise-gpg", "pre-commit"),
65
+ ("gitwise-conventional-commit", "commit-msg"),
66
+ )
67
+
68
+
69
+ def _check_gpg_state(cwd: Path) -> list[str]:
70
+ """Returns warnings about GPG state. Never modifies anything."""
71
+ warnings: list[str] = []
72
+ gpgsign = git_config("commit.gpgsign", cwd=cwd)
73
+ signingkey = git_config("user.signingkey", cwd=cwd)
74
+
75
+ if gpgsign == "true":
76
+ if not signingkey:
77
+ warnings.append(t("gpg_signing_active_no_key"))
78
+ elif gpgsign is None:
79
+ warnings.append(t("gpg_signing_not_configured"))
80
+ return warnings
81
+
82
+
83
+ def _plan_base_changes(cwd: Path) -> list[SetupChange]:
84
+ changes: list[SetupChange] = []
85
+ for key, desired in _BASE_CONFIGS:
86
+ if key in _PROTECTED_KEYS:
87
+ raise ValueError(t("protected_key", name=key))
88
+ current = git_config(key, cwd=cwd)
89
+ if current != desired:
90
+ changes.append(
91
+ {
92
+ "op": "set",
93
+ "key": key,
94
+ "desired": desired,
95
+ "current": current,
96
+ "note": None,
97
+ }
98
+ )
99
+ return changes
100
+
101
+
102
+ def _plan_platform_feature_changes(cwd: Path) -> list[SetupChange]:
103
+ changes: list[SetupChange] = []
104
+
105
+ # fsmonitor: macOS only, requires git >= 2.36 (built-in FSEvents) or watchman
106
+ if platform.system() == "Darwin":
107
+ import shutil
108
+
109
+ fsmonitor_ok = git_version() >= (2, 36, 0) or bool(shutil.which("watchman"))
110
+ if fsmonitor_ok:
111
+ current = git_config("core.fsmonitor", cwd=cwd)
112
+ if current != "true":
113
+ changes.append(
114
+ {
115
+ "op": "set",
116
+ "key": "core.fsmonitor",
117
+ "desired": "true",
118
+ "current": current,
119
+ "note": t("setup_note_fsmonitor"),
120
+ }
121
+ )
122
+
123
+ # feature.manyFiles: git >= 2.40 only (can break older clients)
124
+ if git_version() >= (2, 40, 0):
125
+ current = git_config("feature.manyFiles", cwd=cwd)
126
+ if current != "true":
127
+ changes.append(
128
+ {
129
+ "op": "set",
130
+ "key": "feature.manyFiles",
131
+ "desired": "true",
132
+ "current": current,
133
+ "note": t("setup_note_manyfiles"),
134
+ }
135
+ )
136
+
137
+ return changes
138
+
139
+
140
+ def _detect_hook_managers(cwd: Path) -> list[str]:
141
+ managers: list[str] = []
142
+ if (cwd / "lefthook.yml").exists() or (cwd / ".lefthook").exists():
143
+ managers.append("lefthook")
144
+ if (cwd / ".husky").exists():
145
+ managers.append("husky")
146
+ return managers
147
+
148
+
149
+ def _same_path(left: Path, right: Path) -> bool:
150
+ return os.path.realpath(str(left)) == os.path.realpath(str(right))
151
+
152
+
153
+ def _active_hooks_dir(repo_root: Path) -> Path | None:
154
+ configured = git_config("core.hooksPath", cwd=repo_root)
155
+ if configured:
156
+ configured_path = Path(configured)
157
+ if configured_path.is_absolute():
158
+ return configured_path
159
+ return repo_root / configured_path
160
+
161
+ repository_git_dir = git_dir(repo_root)
162
+ if repository_git_dir is None:
163
+ return None
164
+ return repository_git_dir / "hooks"
165
+
166
+
167
+ def _detect_existing_hook_events(repo_root: Path, hooks_dir: Path) -> list[str]:
168
+ active_dir = _active_hooks_dir(repo_root)
169
+ if active_dir is None:
170
+ return []
171
+ if _same_path(active_dir, hooks_dir):
172
+ return []
173
+
174
+ existing_events: list[str] = []
175
+ for _, event in _NATIVE_HOOKS:
176
+ hook_file = active_dir / event
177
+ if hook_file.exists() or hook_file.is_symlink():
178
+ existing_events.append(event)
179
+ return existing_events
180
+
181
+
182
+ def _plan_native_hooks(cwd: Path, hooks_dir: Path) -> list[SetupChange]:
183
+ changes: list[SetupChange] = []
184
+
185
+ current_hookspath = git_config("core.hooksPath", cwd=cwd)
186
+ if current_hookspath == str(hooks_dir):
187
+ changes.append(
188
+ {
189
+ "op": "unset",
190
+ "key": "core.hooksPath",
191
+ "current": current_hookspath,
192
+ "desired": "",
193
+ "note": t("setup_note_hooks_native_migrate"),
194
+ }
195
+ )
196
+
197
+ for name, event in _NATIVE_HOOKS:
198
+ hook_script = str(hooks_dir / event)
199
+ command_key = f"hook.{name}.command"
200
+ event_key = f"hook.{name}.event"
201
+
202
+ current_command = git_config(command_key, cwd=cwd)
203
+ if current_command != hook_script:
204
+ changes.append(
205
+ {
206
+ "op": "set",
207
+ "key": command_key,
208
+ "desired": hook_script,
209
+ "current": current_command,
210
+ "note": t("setup_note_hooks_native"),
211
+ }
212
+ )
213
+
214
+ current_events = set(git_config_all(event_key, cwd=cwd))
215
+ if event not in current_events:
216
+ changes.append(
217
+ {
218
+ "op": "add",
219
+ "key": event_key,
220
+ "desired": event,
221
+ "current": None,
222
+ "note": t("setup_note_hooks_native_event"),
223
+ }
224
+ )
225
+
226
+ return changes
227
+
228
+
229
+ def _plan_legacy_hooks(cwd: Path, hooks_dir: Path) -> list[SetupChange]:
230
+ current = git_config("core.hooksPath", cwd=cwd)
231
+ desired = str(hooks_dir)
232
+ if current == desired:
233
+ return []
234
+ return [
235
+ {
236
+ "op": "set",
237
+ "key": "core.hooksPath",
238
+ "desired": desired,
239
+ "current": current,
240
+ "note": t("setup_note_hooks_legacy"),
241
+ }
242
+ ]
243
+
244
+
245
+ def _choose_hooks_backend(
246
+ *,
247
+ cwd: Path,
248
+ hooks_mode: HookMode,
249
+ hooks_dir: Path,
250
+ managers: list[str],
251
+ existing_events: list[str],
252
+ ) -> tuple[Literal["native", "legacy", "skip"], list[str]]:
253
+ warnings: list[str] = []
254
+ native_supported = supports_config_hooks(cwd=cwd)
255
+ current = git_config("core.hooksPath", cwd=cwd)
256
+
257
+ if hooks_mode == "skip":
258
+ return "skip", warnings
259
+
260
+ if hooks_mode == "native":
261
+ if native_supported:
262
+ return "native", warnings
263
+ warnings.append(t("setup_hook_warning_native_unsupported"))
264
+ return "skip", warnings
265
+
266
+ if hooks_mode == "legacy":
267
+ if current and current != str(hooks_dir):
268
+ warnings.append(t("setup_hook_warning_legacy_overwrite", current=current))
269
+ return "legacy", warnings
270
+
271
+ if current == str(hooks_dir):
272
+ return "legacy", warnings
273
+
274
+ if managers:
275
+ warnings.append(t("setup_hook_warning_managers_preserve", managers=", ".join(managers)))
276
+ return "skip", warnings
277
+
278
+ if current and current != str(hooks_dir):
279
+ warnings.append(t("setup_hook_warning_legacy_conflict", current=current))
280
+ return "skip", warnings
281
+
282
+ if existing_events:
283
+ warnings.append(
284
+ t("setup_hook_warning_existing_scripts", hooks=", ".join(sorted(existing_events)))
285
+ )
286
+ return "skip", warnings
287
+
288
+ if native_supported:
289
+ return "native", warnings
290
+
291
+ return "legacy", warnings
292
+
293
+
294
+ def _plan_hook_changes(
295
+ *,
296
+ repo_root: Path,
297
+ hooks_mode: HookMode,
298
+ ) -> tuple[list[SetupChange], list[str], list[str], Literal["native", "legacy", "skip"]]:
299
+ hooks_dir = _share_dir() / "hooks"
300
+ managers = _detect_hook_managers(repo_root)
301
+ existing_events = _detect_existing_hook_events(repo_root, hooks_dir)
302
+ backend, warnings = _choose_hooks_backend(
303
+ cwd=repo_root,
304
+ hooks_mode=hooks_mode,
305
+ hooks_dir=hooks_dir,
306
+ managers=managers,
307
+ existing_events=existing_events,
308
+ )
309
+
310
+ if backend == "native":
311
+ return _plan_native_hooks(repo_root, hooks_dir), warnings, managers, backend
312
+ if backend == "legacy":
313
+ return _plan_legacy_hooks(repo_root, hooks_dir), warnings, managers, backend
314
+ return [], warnings, managers, backend
315
+
316
+
317
+ def _plan_changes(
318
+ *,
319
+ repo_root: Path,
320
+ hooks_mode: HookMode,
321
+ ) -> tuple[list[SetupChange], list[str], list[str], Literal["native", "legacy", "skip"]]:
322
+ changes: list[SetupChange] = []
323
+ changes.extend(_plan_base_changes(repo_root))
324
+ changes.extend(_plan_platform_feature_changes(repo_root))
325
+ hook_changes, hook_warnings, managers, backend = _plan_hook_changes(
326
+ repo_root=repo_root,
327
+ hooks_mode=hooks_mode,
328
+ )
329
+ changes.extend(hook_changes)
330
+ return changes, hook_warnings, managers, backend
331
+
332
+
333
+ def _format_desired(change: SetupChange) -> str:
334
+ op = change["op"]
335
+ if op == "add":
336
+ return f"+ {change['desired']}"
337
+ if op == "unset":
338
+ return t("unset_value")
339
+ return change["desired"]
340
+
341
+
342
+ def _apply_change(change: SetupChange, cwd: Path) -> bool:
343
+ op = change["op"]
344
+ key = change["key"]
345
+
346
+ if op == "add":
347
+ result = git_run(["config", "--add", key, change["desired"]], cwd=cwd, check=False)
348
+ elif op == "unset":
349
+ result = git_run(["config", "--unset-all", key], cwd=cwd, check=False)
350
+ else:
351
+ result = git_run(["config", key, change["desired"]], cwd=cwd, check=False)
352
+ return result.returncode == 0
353
+
354
+
355
+ def _json_report(
356
+ *,
357
+ dry_run: bool,
358
+ root: Path,
359
+ changes: list[SetupChange],
360
+ warnings: list[str],
361
+ managers: list[str],
362
+ hooks_mode: HookMode,
363
+ hooks_backend: Literal["native", "legacy", "skip"],
364
+ ) -> dict[str, object]:
365
+ return {
366
+ "dry_run": dry_run,
367
+ "root": str(root),
368
+ "changes": changes,
369
+ "warnings": warnings,
370
+ "hook_managers": managers,
371
+ "hooks_mode_requested": hooks_mode,
372
+ "hooks_backend": hooks_backend,
373
+ }
374
+
375
+
376
+ def _print_setup_context(
377
+ *,
378
+ gpg_warnings: list[str],
379
+ hook_warnings: list[str],
380
+ managers: list[str],
381
+ hooks_backend: Literal["native", "legacy", "skip"],
382
+ hooks_mode: HookMode,
383
+ ) -> None:
384
+ for warning_text in gpg_warnings + hook_warnings:
385
+ warn(warning_text)
386
+ info(t("setup_hook_backend_selected", backend=hooks_backend, requested=hooks_mode))
387
+ if managers:
388
+ info(t("setup_hook_managers_detected", managers=", ".join(managers)))
389
+ if gpg_warnings or hook_warnings or managers:
390
+ print_blank()
391
+
392
+
393
+ def _print_change_plan(changes: list[SetupChange]) -> None:
394
+ print_header(t("planned_changes", count=str(len(changes))))
395
+ for change in changes:
396
+ note = f" [{change['note']}]" if change["note"] else ""
397
+ current = change["current"]
398
+ current_str = (
399
+ t("current_value", current=current) if current is not None else t("not_configured")
400
+ )
401
+ print_kv(change["key"], f"{_format_desired(change)} {current_str}{note}")
402
+
403
+
404
+ def _apply_changes(
405
+ changes: list[SetupChange], cwd: Path, *, quiet: bool = False
406
+ ) -> list[dict[str, object]]:
407
+ results: list[dict[str, object]] = []
408
+ for change in changes:
409
+ desired_text = _format_desired(change)
410
+ applied = _apply_change(change, cwd)
411
+ results.append({"key": change["key"], "applied": applied})
412
+ if quiet:
413
+ continue
414
+ if applied:
415
+ print_status_line("✓", change["key"], desired_text)
416
+ else:
417
+ print_status_line(
418
+ "✗",
419
+ change["key"],
420
+ t("config_failed", name=change["key"]),
421
+ ok_flag=False,
422
+ )
423
+ return results
424
+
425
+
426
+ def run_setup(
427
+ *,
428
+ dry_run: bool = False,
429
+ yes: bool = False,
430
+ as_json: bool = False,
431
+ hooks_mode: HookMode = "preserve",
432
+ ) -> int:
433
+ root, err = require_root()
434
+ if err:
435
+ return err
436
+ if root is None:
437
+ return 1
438
+ cwd = root
439
+
440
+ gpg_warnings = _check_gpg_state(cwd)
441
+ changes, hook_warnings, managers, hooks_backend = _plan_changes(
442
+ repo_root=cwd,
443
+ hooks_mode=hooks_mode,
444
+ )
445
+
446
+ if dry_run:
447
+ if as_json:
448
+ print_json(
449
+ ok_envelope(
450
+ payload=_json_report(
451
+ dry_run=True,
452
+ root=cwd,
453
+ changes=changes,
454
+ warnings=gpg_warnings + hook_warnings,
455
+ managers=managers,
456
+ hooks_mode=hooks_mode,
457
+ hooks_backend=hooks_backend,
458
+ ),
459
+ applied=False,
460
+ )
461
+ )
462
+ return 0
463
+ _print_setup_context(
464
+ gpg_warnings=gpg_warnings,
465
+ hook_warnings=hook_warnings,
466
+ managers=managers,
467
+ hooks_backend=hooks_backend,
468
+ hooks_mode=hooks_mode,
469
+ )
470
+ if not changes:
471
+ ok(t("config_up_to_date"))
472
+ return 0
473
+ _print_change_plan(changes)
474
+ return 0
475
+
476
+ if as_json and not yes:
477
+ print_json(
478
+ error_envelope(
479
+ error=t("yes_required_with_json"),
480
+ code="yes_required",
481
+ hint=t("yes_required_hint"),
482
+ )
483
+ )
484
+ return 2
485
+
486
+ if not as_json:
487
+ _print_setup_context(
488
+ gpg_warnings=gpg_warnings,
489
+ hook_warnings=hook_warnings,
490
+ managers=managers,
491
+ hooks_backend=hooks_backend,
492
+ hooks_mode=hooks_mode,
493
+ )
494
+ if not changes:
495
+ ok(t("config_up_to_date"))
496
+ return 0
497
+ _print_change_plan(changes)
498
+ if not yes:
499
+ if not confirm(t("confirm_setup_changes")):
500
+ info(t("cancelled"))
501
+ return 0
502
+ print_blank()
503
+
504
+ if not changes:
505
+ if as_json:
506
+ print_json(
507
+ ok_envelope(
508
+ payload=_json_report(
509
+ dry_run=False,
510
+ root=cwd,
511
+ changes=[],
512
+ warnings=gpg_warnings + hook_warnings,
513
+ managers=managers,
514
+ hooks_mode=hooks_mode,
515
+ hooks_backend=hooks_backend,
516
+ ),
517
+ applied=True,
518
+ results=[],
519
+ )
520
+ )
521
+ return 0
522
+
523
+ results = _apply_changes(changes, cwd, quiet=as_json)
524
+
525
+ if as_json:
526
+ report = _json_report(
527
+ dry_run=False,
528
+ root=cwd,
529
+ changes=changes,
530
+ warnings=gpg_warnings + hook_warnings,
531
+ managers=managers,
532
+ hooks_mode=hooks_mode,
533
+ hooks_backend=hooks_backend,
534
+ )
535
+ report["applied"] = True
536
+ report["results"] = results
537
+ all_ok = all(bool(r.get("applied")) for r in results)
538
+ if all_ok:
539
+ print_json(ok_envelope(payload=report))
540
+ return 0
541
+ print_json(
542
+ error_envelope(
543
+ error=t("setup_partial_failure"),
544
+ code="setup_partial_failure",
545
+ payload=report,
546
+ )
547
+ )
548
+ return 1
549
+
550
+ ok(t("setup_complete"))
551
+ return 0
@@ -0,0 +1,36 @@
1
+ """setup-agents sub-package: planning, state detection, and execution."""
2
+
3
+ from gitwise.setup_agents.exec import PlanExecutionError, SymlinkConflict, _execute_actions
4
+ from gitwise.setup_agents.format import (
5
+ format_json_output_global,
6
+ format_json_output_global_error,
7
+ format_json_output_local,
8
+ format_json_output_local_error,
9
+ )
10
+ from gitwise.setup_agents.plan import _plan_actions, _plan_actions_global
11
+ from gitwise.setup_agents.plan_skills import _SKILLS
12
+ from gitwise.setup_agents.types import (
13
+ ActionDict,
14
+ ActionSummary,
15
+ PathState,
16
+ StateDict,
17
+ build_action_summary,
18
+ )
19
+
20
+ __all__ = [
21
+ "_SKILLS",
22
+ "ActionDict",
23
+ "ActionSummary",
24
+ "PathState",
25
+ "PlanExecutionError",
26
+ "StateDict",
27
+ "SymlinkConflict",
28
+ "_execute_actions",
29
+ "_plan_actions",
30
+ "_plan_actions_global",
31
+ "build_action_summary",
32
+ "format_json_output_global",
33
+ "format_json_output_global_error",
34
+ "format_json_output_local",
35
+ "format_json_output_local_error",
36
+ ]
@@ -0,0 +1,17 @@
1
+ """Backward-compatible shim for provider registry imports."""
2
+
3
+ from gitwise.setup_agents.providers import (
4
+ ADAPTERS,
5
+ list_adapters,
6
+ plan_adapter_actions,
7
+ resolve_adapter_selection,
8
+ )
9
+ from gitwise.setup_agents.providers.base import AdapterConfig
10
+
11
+ __all__ = [
12
+ "ADAPTERS",
13
+ "AdapterConfig",
14
+ "list_adapters",
15
+ "plan_adapter_actions",
16
+ "resolve_adapter_selection",
17
+ ]
@@ -0,0 +1,5 @@
1
+ """Backward-compatible shim for Aider provider."""
2
+
3
+ from gitwise.setup_agents.providers.aider import ADAPTER
4
+
5
+ __all__ = ["ADAPTER"]
@@ -0,0 +1,5 @@
1
+ """Backward-compatible shim for provider base types."""
2
+
3
+ from gitwise.setup_agents.providers.base import AdapterConfig
4
+
5
+ __all__ = ["AdapterConfig"]
@@ -0,0 +1,5 @@
1
+ """Backward-compatible shim for Codex provider."""
2
+
3
+ from gitwise.setup_agents.providers.codex import ADAPTER
4
+
5
+ __all__ = ["ADAPTER"]
@@ -0,0 +1,5 @@
1
+ """Backward-compatible shim for Continue provider."""
2
+
3
+ from gitwise.setup_agents.providers.continue_adapter import ADAPTER
4
+
5
+ __all__ = ["ADAPTER"]
@@ -0,0 +1,5 @@
1
+ """Backward-compatible shim for Cursor provider."""
2
+
3
+ from gitwise.setup_agents.providers.cursor import ADAPTER
4
+
5
+ __all__ = ["ADAPTER"]
@@ -0,0 +1,5 @@
1
+ """Backward-compatible shim for OpenCode provider."""
2
+
3
+ from gitwise.setup_agents.providers.opencode import ADAPTER
4
+
5
+ __all__ = ["ADAPTER"]
@@ -0,0 +1,5 @@
1
+ """Backward-compatible shim for Pi provider."""
2
+
3
+ from gitwise.setup_agents.providers.pi import ADAPTER
4
+
5
+ __all__ = ["ADAPTER"]