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.
- gitwise/__init__.py +11 -0
- gitwise/__main__.py +113 -0
- gitwise/_cli_completions.py +88 -0
- gitwise/_cli_dispatch.py +469 -0
- gitwise/_cli_introspection.py +275 -0
- gitwise/_cli_parser.py +345 -0
- gitwise/_cli_setup_agents.py +439 -0
- gitwise/_i18n_data.json +1934 -0
- gitwise/_paths.py +22 -0
- gitwise/_runtime_config.py +246 -0
- gitwise/audit.py +338 -0
- gitwise/branches.py +183 -0
- gitwise/clean.py +197 -0
- gitwise/commit.py +142 -0
- gitwise/conflicts.py +112 -0
- gitwise/context.py +163 -0
- gitwise/design.py +383 -0
- gitwise/diff.py +309 -0
- gitwise/doctor.py +116 -0
- gitwise/git.py +254 -0
- gitwise/health.py +345 -0
- gitwise/i18n.py +99 -0
- gitwise/log.py +329 -0
- gitwise/merge.py +193 -0
- gitwise/optimize.py +212 -0
- gitwise/output.py +652 -0
- gitwise/pick.py +102 -0
- gitwise/pr.py +543 -0
- gitwise/py.typed +0 -0
- gitwise/schema.py +49 -0
- gitwise/setup.py +551 -0
- gitwise/setup_agents/__init__.py +36 -0
- gitwise/setup_agents/adapters/__init__.py +17 -0
- gitwise/setup_agents/adapters/aider.py +5 -0
- gitwise/setup_agents/adapters/base.py +5 -0
- gitwise/setup_agents/adapters/codex.py +5 -0
- gitwise/setup_agents/adapters/continue_adapter.py +5 -0
- gitwise/setup_agents/adapters/cursor.py +5 -0
- gitwise/setup_agents/adapters/opencode.py +5 -0
- gitwise/setup_agents/adapters/pi.py +5 -0
- gitwise/setup_agents/exec.py +449 -0
- gitwise/setup_agents/format.py +164 -0
- gitwise/setup_agents/plan.py +254 -0
- gitwise/setup_agents/plan_gitfiles.py +167 -0
- gitwise/setup_agents/plan_skills.py +256 -0
- gitwise/setup_agents/providers/__init__.py +96 -0
- gitwise/setup_agents/providers/aider.py +11 -0
- gitwise/setup_agents/providers/base.py +79 -0
- gitwise/setup_agents/providers/claude.py +408 -0
- gitwise/setup_agents/providers/codex.py +11 -0
- gitwise/setup_agents/providers/continue_adapter.py +11 -0
- gitwise/setup_agents/providers/cursor.py +11 -0
- gitwise/setup_agents/providers/opencode.py +11 -0
- gitwise/setup_agents/providers/pi.py +11 -0
- gitwise/setup_agents/state.py +141 -0
- gitwise/setup_agents/types.py +48 -0
- gitwise/share/agents/skills/git-audit/SKILL.md +25 -0
- gitwise/share/agents/skills/git-clean/SKILL.md +22 -0
- gitwise/share/agents/skills/git-optimize/SKILL.md +21 -0
- gitwise/share/aider/CONVENTIONS.md.template +8 -0
- gitwise/share/aider/aider.conf.yml.template +4 -0
- gitwise/share/claude/CLAUDE.md.template +9 -0
- gitwise/share/claude/rules/gitwise.md +16 -0
- gitwise/share/claude/settings.json.template +47 -0
- gitwise/share/claude/skills/git-audit/SKILL.md +25 -0
- gitwise/share/claude/skills/git-clean/SKILL.md +22 -0
- gitwise/share/claude/skills/git-optimize/SKILL.md +21 -0
- gitwise/share/codex/agents/gitwise.toml.template +18 -0
- gitwise/share/continue/rules/gitwise.md.template +14 -0
- gitwise/share/cursor/rules/gitwise.mdc.template +16 -0
- gitwise/share/git-config-modern.txt +48 -0
- gitwise/share/hooks/commit-msg +22 -0
- gitwise/share/hooks/pre-commit +19 -0
- gitwise/share/opencode/agents/gitwise.md.template +14 -0
- gitwise/share/pi/skills/gitwise.md.template +14 -0
- gitwise/share/schemas/v1/input/audit.json +40 -0
- gitwise/share/schemas/v1/input/branches.json +51 -0
- gitwise/share/schemas/v1/input/clean.json +52 -0
- gitwise/share/schemas/v1/input/commands.json +36 -0
- gitwise/share/schemas/v1/input/commit.json +63 -0
- gitwise/share/schemas/v1/input/completions.json +51 -0
- gitwise/share/schemas/v1/input/conflicts.json +46 -0
- gitwise/share/schemas/v1/input/context.json +36 -0
- gitwise/share/schemas/v1/input/diff.json +56 -0
- gitwise/share/schemas/v1/input/doctor.json +36 -0
- gitwise/share/schemas/v1/input/health.json +36 -0
- gitwise/share/schemas/v1/input/log.json +71 -0
- gitwise/share/schemas/v1/input/merge.json +63 -0
- gitwise/share/schemas/v1/input/optimize.json +44 -0
- gitwise/share/schemas/v1/input/pick.json +63 -0
- gitwise/share/schemas/v1/input/pr.json +51 -0
- gitwise/share/schemas/v1/input/schema.json +48 -0
- gitwise/share/schemas/v1/input/setup-agents.json +108 -0
- gitwise/share/schemas/v1/input/setup.json +55 -0
- gitwise/share/schemas/v1/input/show.json +46 -0
- gitwise/share/schemas/v1/input/snapshot.json +36 -0
- gitwise/share/schemas/v1/input/stash.json +68 -0
- gitwise/share/schemas/v1/input/status.json +36 -0
- gitwise/share/schemas/v1/input/suggest.json +36 -0
- gitwise/share/schemas/v1/input/summarize.json +44 -0
- gitwise/share/schemas/v1/input/sync.json +55 -0
- gitwise/share/schemas/v1/input/tag.json +73 -0
- gitwise/share/schemas/v1/input/undo.json +60 -0
- gitwise/share/schemas/v1/input/update.json +40 -0
- gitwise/share/schemas/v1/input/worktree.json +50 -0
- gitwise/show.py +118 -0
- gitwise/snapshot.py +110 -0
- gitwise/stash.py +188 -0
- gitwise/status.py +93 -0
- gitwise/suggest.py +148 -0
- gitwise/summarize.py +202 -0
- gitwise/sync.py +257 -0
- gitwise/tag.py +252 -0
- gitwise/undo.py +145 -0
- gitwise/update.py +42 -0
- gitwise/utils/__init__.py +1 -0
- gitwise/utils/git_output.py +51 -0
- gitwise/utils/json_envelope.py +58 -0
- gitwise/utils/parsing.py +34 -0
- gitwise/worktree.py +182 -0
- gitwise_cli-0.24.2.dist-info/METADATA +151 -0
- gitwise_cli-0.24.2.dist-info/RECORD +125 -0
- gitwise_cli-0.24.2.dist-info/WHEEL +4 -0
- gitwise_cli-0.24.2.dist-info/entry_points.txt +2 -0
- gitwise_cli-0.24.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Provider registry: multi-agent tool support for setup-agents."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from gitwise.i18n import t
|
|
6
|
+
from gitwise.setup_agents.plan_skills import _SKILLS
|
|
7
|
+
from gitwise.setup_agents.providers.aider import ADAPTER as AIDER
|
|
8
|
+
from gitwise.setup_agents.providers.base import AdapterConfig, AdapterContext
|
|
9
|
+
from gitwise.setup_agents.providers.claude import ADAPTER as CLAUDE
|
|
10
|
+
from gitwise.setup_agents.providers.codex import ADAPTER as CODEX
|
|
11
|
+
from gitwise.setup_agents.providers.continue_adapter import ADAPTER as CONTINUE
|
|
12
|
+
from gitwise.setup_agents.providers.cursor import ADAPTER as CURSOR
|
|
13
|
+
from gitwise.setup_agents.providers.opencode import ADAPTER as OPENCODE
|
|
14
|
+
from gitwise.setup_agents.providers.pi import ADAPTER as PI
|
|
15
|
+
from gitwise.setup_agents.state import _AGENTS_MD, _detect_state, _gpg_ready
|
|
16
|
+
|
|
17
|
+
ADAPTERS: dict[str, AdapterConfig] = {
|
|
18
|
+
"claude": CLAUDE,
|
|
19
|
+
"cursor": CURSOR,
|
|
20
|
+
"continue": CONTINUE,
|
|
21
|
+
"opencode": OPENCODE,
|
|
22
|
+
"codex": CODEX,
|
|
23
|
+
"aider": AIDER,
|
|
24
|
+
"pi": PI,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def list_adapters() -> list[str]:
|
|
29
|
+
return sorted(ADAPTERS.keys())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def list_providers() -> list[str]:
|
|
33
|
+
return list_adapters()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def resolve_adapter_selection(
|
|
37
|
+
names: list[str] | None,
|
|
38
|
+
) -> tuple[list[AdapterConfig], list[str]]:
|
|
39
|
+
if not names:
|
|
40
|
+
return [], []
|
|
41
|
+
normalized = ["claude" if name == "claude-only" else name for name in names]
|
|
42
|
+
if "none" in normalized:
|
|
43
|
+
if len(normalized) > 1:
|
|
44
|
+
return [], [t("adapters_none_with_others")]
|
|
45
|
+
return [], []
|
|
46
|
+
resolved: list[AdapterConfig] = []
|
|
47
|
+
seen: set[str] = set()
|
|
48
|
+
errors: list[str] = []
|
|
49
|
+
for name in normalized:
|
|
50
|
+
if name in seen:
|
|
51
|
+
continue
|
|
52
|
+
seen.add(name)
|
|
53
|
+
cfg = ADAPTERS.get(name)
|
|
54
|
+
if cfg is None:
|
|
55
|
+
errors.append(t("unknown_adapter", name=name))
|
|
56
|
+
else:
|
|
57
|
+
resolved.append(cfg)
|
|
58
|
+
return resolved, errors
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def detect_global_skills(home: Path | None = None) -> frozenset[str]:
|
|
62
|
+
home_dir = home or Path.home()
|
|
63
|
+
return frozenset(
|
|
64
|
+
skill_name
|
|
65
|
+
for skill_name in _SKILLS
|
|
66
|
+
if (home_dir / ".claude" / "skills" / skill_name / "SKILL.md").exists()
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def plan_adapter_actions(
|
|
71
|
+
adapter_names: list[str] | None,
|
|
72
|
+
root: Path,
|
|
73
|
+
context: AdapterContext | None = None,
|
|
74
|
+
) -> tuple[list[dict], list[str], list[str]]:
|
|
75
|
+
if not adapter_names:
|
|
76
|
+
return [], [], []
|
|
77
|
+
selected, errors = resolve_adapter_selection(adapter_names)
|
|
78
|
+
if errors:
|
|
79
|
+
return [], errors, []
|
|
80
|
+
if context is None:
|
|
81
|
+
state = _detect_state(root)
|
|
82
|
+
context = {
|
|
83
|
+
"state": state,
|
|
84
|
+
"canonical_doc_path": _AGENTS_MD,
|
|
85
|
+
"global_skills": detect_global_skills(),
|
|
86
|
+
"supports_symlinks": state["supports_symlinks"],
|
|
87
|
+
"gpg_ready": _gpg_ready(root),
|
|
88
|
+
"flags": {},
|
|
89
|
+
}
|
|
90
|
+
actions: list[dict] = []
|
|
91
|
+
warnings: list[str] = []
|
|
92
|
+
for cfg in selected:
|
|
93
|
+
adapter_actions, adapter_warnings = cfg.plan(root, context)
|
|
94
|
+
actions.extend(adapter_actions)
|
|
95
|
+
warnings.extend(adapter_warnings)
|
|
96
|
+
return actions, [], warnings
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Aider provider — .aider.conf.yml + CONVENTIONS.md."""
|
|
2
|
+
|
|
3
|
+
from gitwise.setup_agents.providers.base import AdapterConfig
|
|
4
|
+
|
|
5
|
+
ADAPTER = AdapterConfig(
|
|
6
|
+
name="aider",
|
|
7
|
+
display_name="Aider",
|
|
8
|
+
config_paths=(".aider.conf.yml", "CONVENTIONS.md"),
|
|
9
|
+
template_paths=("aider.conf.yml.template", "CONVENTIONS.md.template"),
|
|
10
|
+
template_dir="share/aider",
|
|
11
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Base types and planning behavior for the provider registry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TypedDict
|
|
7
|
+
|
|
8
|
+
from gitwise.i18n import t
|
|
9
|
+
from gitwise.setup_agents.state import _classify_path
|
|
10
|
+
from gitwise.setup_agents.types import ActionDict, StateDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AdapterFlags(TypedDict, total=False):
|
|
14
|
+
no_symlinks: bool
|
|
15
|
+
replace_claude_with_symlink: bool
|
|
16
|
+
migrate_legacy_claude: bool
|
|
17
|
+
frozen_time: bool
|
|
18
|
+
no_git_files: bool
|
|
19
|
+
core_claude_planned: bool
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AdapterContext(TypedDict):
|
|
23
|
+
state: StateDict
|
|
24
|
+
canonical_doc_path: str
|
|
25
|
+
global_skills: frozenset[str]
|
|
26
|
+
supports_symlinks: bool
|
|
27
|
+
gpg_ready: bool
|
|
28
|
+
flags: AdapterFlags
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AdapterConfig:
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
*,
|
|
35
|
+
name: str,
|
|
36
|
+
display_name: str,
|
|
37
|
+
config_paths: tuple[str, ...],
|
|
38
|
+
template_paths: tuple[str, ...],
|
|
39
|
+
template_dir: str,
|
|
40
|
+
) -> None:
|
|
41
|
+
self.name = name
|
|
42
|
+
self.display_name = display_name
|
|
43
|
+
self.config_paths = config_paths
|
|
44
|
+
self.template_paths = template_paths
|
|
45
|
+
self.template_dir = template_dir
|
|
46
|
+
|
|
47
|
+
def _read_template(self, template_name: str) -> str:
|
|
48
|
+
from gitwise._paths import share_dir
|
|
49
|
+
|
|
50
|
+
template_dir_str = str(self.template_dir)
|
|
51
|
+
relative = (
|
|
52
|
+
template_dir_str.removeprefix("share/")
|
|
53
|
+
if template_dir_str.startswith("share/")
|
|
54
|
+
else template_dir_str
|
|
55
|
+
)
|
|
56
|
+
template_path = share_dir() / relative / template_name
|
|
57
|
+
if not template_path.exists():
|
|
58
|
+
raise FileNotFoundError(t("adapter_no_template", path=str(template_path)))
|
|
59
|
+
return template_path.read_text(encoding="utf-8")
|
|
60
|
+
|
|
61
|
+
def plan(self, root: Path, _context: AdapterContext) -> tuple[list[ActionDict], list[str]]:
|
|
62
|
+
actions: list[ActionDict] = []
|
|
63
|
+
warnings: list[str] = []
|
|
64
|
+
for config_path, template_path in zip(self.config_paths, self.template_paths, strict=True):
|
|
65
|
+
target_path = root / config_path
|
|
66
|
+
state = _classify_path(target_path)
|
|
67
|
+
if state == "absent":
|
|
68
|
+
content = self._read_template(template_path)
|
|
69
|
+
actions.append(
|
|
70
|
+
{
|
|
71
|
+
"action": "adapter-create",
|
|
72
|
+
"file": config_path,
|
|
73
|
+
"content": content,
|
|
74
|
+
"adapter": self.display_name,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
warnings.append(t("adapter_exists", adapter=self.display_name, file=config_path))
|
|
79
|
+
return actions, warnings
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Claude provider wrapper for staged migration from plan.py."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from gitwise.i18n import t
|
|
9
|
+
from gitwise.setup_agents.plan_skills import _read_template, plan_global_skills
|
|
10
|
+
from gitwise.setup_agents.providers.base import AdapterConfig, AdapterContext
|
|
11
|
+
from gitwise.setup_agents.state import (
|
|
12
|
+
_AGENTS_MD,
|
|
13
|
+
_CLAUDE_MD,
|
|
14
|
+
_files_equal,
|
|
15
|
+
_gpg_ready,
|
|
16
|
+
_has_marker,
|
|
17
|
+
)
|
|
18
|
+
from gitwise.setup_agents.types import ActionDict, StateDict
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ClaudeAdapter(AdapterConfig):
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
super().__init__(
|
|
24
|
+
name="claude",
|
|
25
|
+
display_name="Claude",
|
|
26
|
+
config_paths=(),
|
|
27
|
+
template_paths=(),
|
|
28
|
+
template_dir="share/claude",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def plan_settings(self, root: Path) -> tuple[list[ActionDict], list[str]]:
|
|
32
|
+
settings_path = root / ".claude" / "settings.json"
|
|
33
|
+
settings_template: dict = json.loads(_read_template("settings.json.template"))
|
|
34
|
+
if settings_path.exists():
|
|
35
|
+
try:
|
|
36
|
+
existing_settings: dict = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
37
|
+
except json.JSONDecodeError:
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
"file": ".claude/settings.json",
|
|
41
|
+
"action": "create",
|
|
42
|
+
"data": settings_template,
|
|
43
|
+
}
|
|
44
|
+
], [t("invalid_json")]
|
|
45
|
+
|
|
46
|
+
existing_deny: list = existing_settings.get("permissions", {}).get("deny", [])
|
|
47
|
+
new_deny: list = settings_template.get("permissions", {}).get("deny", [])
|
|
48
|
+
merged_deny = list(dict.fromkeys(existing_deny + new_deny))
|
|
49
|
+
existing_settings.setdefault("permissions", {})["deny"] = merged_deny
|
|
50
|
+
gpg_rules = [r for r in merged_deny if "gpgsign" in r or "no-gpg-sign" in r]
|
|
51
|
+
warnings: list[str] = []
|
|
52
|
+
if not gpg_rules:
|
|
53
|
+
warnings.append(t("settings_sin_gpg_deny"))
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
"file": ".claude/settings.json",
|
|
57
|
+
"action": "merge",
|
|
58
|
+
"data": existing_settings,
|
|
59
|
+
}
|
|
60
|
+
], warnings
|
|
61
|
+
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
"file": ".claude/settings.json",
|
|
65
|
+
"action": "create",
|
|
66
|
+
"data": settings_template,
|
|
67
|
+
}
|
|
68
|
+
], []
|
|
69
|
+
|
|
70
|
+
def pointer_template(self) -> str:
|
|
71
|
+
return t("conventions_heading") + t("conventions_pointer")
|
|
72
|
+
|
|
73
|
+
def compute_backup_path(self, path: Path) -> Path:
|
|
74
|
+
backup = path.with_suffix(".md.bak")
|
|
75
|
+
if backup.exists():
|
|
76
|
+
suffix = int(time.time())
|
|
77
|
+
backup = path.parent / f"{path.stem}.md.bak.{suffix}"
|
|
78
|
+
return backup
|
|
79
|
+
|
|
80
|
+
def build_template(self, root: Path) -> str:
|
|
81
|
+
raw = _read_template("CLAUDE.md.template")
|
|
82
|
+
if _gpg_ready(root):
|
|
83
|
+
return raw
|
|
84
|
+
return (
|
|
85
|
+
"\n".join(
|
|
86
|
+
line
|
|
87
|
+
for line in raw.splitlines()
|
|
88
|
+
if "GPG" not in line and "gpg-sign" not in line.lower()
|
|
89
|
+
)
|
|
90
|
+
+ "\n"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def bucket1_no_agents(
|
|
94
|
+
self,
|
|
95
|
+
state: StateDict,
|
|
96
|
+
root: Path,
|
|
97
|
+
template: str,
|
|
98
|
+
) -> tuple[int, list[ActionDict], list[str]]:
|
|
99
|
+
c_state = state["c_state"]
|
|
100
|
+
claude_md = root / _CLAUDE_MD
|
|
101
|
+
if c_state == "absent":
|
|
102
|
+
return 1, [{"file": _CLAUDE_MD, "action": "create", "content": template}], []
|
|
103
|
+
if c_state in ("symlink_valid", "regular"):
|
|
104
|
+
if _has_marker(claude_md):
|
|
105
|
+
return (
|
|
106
|
+
1,
|
|
107
|
+
[
|
|
108
|
+
{
|
|
109
|
+
"file": _CLAUDE_MD,
|
|
110
|
+
"action": "skip",
|
|
111
|
+
"reason": t("already_contains_conventions"),
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
[],
|
|
115
|
+
)
|
|
116
|
+
return 1, [{"file": _CLAUDE_MD, "action": "append", "content": template}], []
|
|
117
|
+
return 1, [], []
|
|
118
|
+
|
|
119
|
+
def bucket2_agents_no_claude(
|
|
120
|
+
self,
|
|
121
|
+
root: Path,
|
|
122
|
+
supports_symlinks: bool,
|
|
123
|
+
template: str,
|
|
124
|
+
) -> tuple[int, list[ActionDict], list[str]]:
|
|
125
|
+
agents_actions: list[ActionDict] = []
|
|
126
|
+
agents_md = root / _AGENTS_MD
|
|
127
|
+
if not _has_marker(agents_md):
|
|
128
|
+
agents_actions.append({"file": _AGENTS_MD, "action": "append", "content": template})
|
|
129
|
+
if supports_symlinks:
|
|
130
|
+
claude_actions: list[ActionDict] = [
|
|
131
|
+
{"file": _CLAUDE_MD, "action": "symlink-create", "target_relative": _AGENTS_MD}
|
|
132
|
+
]
|
|
133
|
+
else:
|
|
134
|
+
claude_actions = [
|
|
135
|
+
{"file": _CLAUDE_MD, "action": "create", "content": self.pointer_template()}
|
|
136
|
+
]
|
|
137
|
+
return 2, agents_actions + claude_actions, []
|
|
138
|
+
|
|
139
|
+
def _migrate_legacy_claude_only(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
root: Path,
|
|
143
|
+
state: StateDict,
|
|
144
|
+
template: str,
|
|
145
|
+
supports_symlinks: bool,
|
|
146
|
+
) -> tuple[int, list[ActionDict], list[str]]:
|
|
147
|
+
claude_md = root / _CLAUDE_MD
|
|
148
|
+
c_state = state["c_state"]
|
|
149
|
+
|
|
150
|
+
if c_state == "regular":
|
|
151
|
+
try:
|
|
152
|
+
agents_content = claude_md.read_text(encoding="utf-8")
|
|
153
|
+
except OSError:
|
|
154
|
+
agents_content = template
|
|
155
|
+
else:
|
|
156
|
+
agents_content = template
|
|
157
|
+
|
|
158
|
+
agents_actions: list[ActionDict] = [
|
|
159
|
+
{
|
|
160
|
+
"file": _AGENTS_MD,
|
|
161
|
+
"action": "create",
|
|
162
|
+
"content": agents_content,
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
if c_state == "absent":
|
|
167
|
+
if supports_symlinks:
|
|
168
|
+
return (
|
|
169
|
+
2,
|
|
170
|
+
agents_actions
|
|
171
|
+
+ [
|
|
172
|
+
{
|
|
173
|
+
"file": _CLAUDE_MD,
|
|
174
|
+
"action": "symlink-create",
|
|
175
|
+
"target_relative": _AGENTS_MD,
|
|
176
|
+
}
|
|
177
|
+
],
|
|
178
|
+
[],
|
|
179
|
+
)
|
|
180
|
+
return (
|
|
181
|
+
2,
|
|
182
|
+
agents_actions
|
|
183
|
+
+ [
|
|
184
|
+
{
|
|
185
|
+
"file": _CLAUDE_MD,
|
|
186
|
+
"action": "create",
|
|
187
|
+
"content": self.pointer_template(),
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
[],
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if c_state == "regular":
|
|
194
|
+
_bucket, replace_actions, replace_warnings = self.bucket4_replace(
|
|
195
|
+
agents_actions, claude_md
|
|
196
|
+
)
|
|
197
|
+
return 4, replace_actions, replace_warnings
|
|
198
|
+
|
|
199
|
+
_bucket, fallback_actions, fallback_warnings = self.bucket4_default(
|
|
200
|
+
state, claude_md, agents_actions
|
|
201
|
+
)
|
|
202
|
+
return 4, fallback_actions, fallback_warnings
|
|
203
|
+
|
|
204
|
+
def bucket4_default(
|
|
205
|
+
self,
|
|
206
|
+
state: StateDict,
|
|
207
|
+
claude_md: Path,
|
|
208
|
+
agents_actions: list[ActionDict],
|
|
209
|
+
) -> tuple[int, list[ActionDict], list[str]]:
|
|
210
|
+
c_state = state["c_state"]
|
|
211
|
+
if c_state == "symlink_valid":
|
|
212
|
+
try:
|
|
213
|
+
link_target = os.readlink(claude_md)
|
|
214
|
+
except OSError:
|
|
215
|
+
link_target = ""
|
|
216
|
+
return (
|
|
217
|
+
4,
|
|
218
|
+
agents_actions,
|
|
219
|
+
[
|
|
220
|
+
t(
|
|
221
|
+
"claude_md_symlink_other",
|
|
222
|
+
file=_CLAUDE_MD,
|
|
223
|
+
existing=link_target,
|
|
224
|
+
expected=_AGENTS_MD,
|
|
225
|
+
)
|
|
226
|
+
],
|
|
227
|
+
)
|
|
228
|
+
if c_state == "regular":
|
|
229
|
+
return 4, agents_actions, [t("claude_md_separate", c=_CLAUDE_MD, a=_AGENTS_MD)]
|
|
230
|
+
return 5, [], []
|
|
231
|
+
|
|
232
|
+
def bucket4_replace(
|
|
233
|
+
self,
|
|
234
|
+
agents_actions: list[ActionDict],
|
|
235
|
+
claude_md: Path,
|
|
236
|
+
) -> tuple[int, list[ActionDict], list[str]]:
|
|
237
|
+
backup_path = self.compute_backup_path(claude_md)
|
|
238
|
+
return (
|
|
239
|
+
4,
|
|
240
|
+
agents_actions
|
|
241
|
+
+ [
|
|
242
|
+
{
|
|
243
|
+
"file": _CLAUDE_MD,
|
|
244
|
+
"action": "claude-md-replace-with-symlink",
|
|
245
|
+
"target_relative": _AGENTS_MD,
|
|
246
|
+
"backup_path": str(backup_path),
|
|
247
|
+
}
|
|
248
|
+
],
|
|
249
|
+
[
|
|
250
|
+
t(
|
|
251
|
+
"claude_md_replaced",
|
|
252
|
+
file=_CLAUDE_MD,
|
|
253
|
+
target=_AGENTS_MD,
|
|
254
|
+
backup=backup_path.name,
|
|
255
|
+
)
|
|
256
|
+
],
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def bucket3(
|
|
260
|
+
self,
|
|
261
|
+
state: StateDict,
|
|
262
|
+
claude_md: Path,
|
|
263
|
+
agents_md: Path,
|
|
264
|
+
agents_actions: list[ActionDict],
|
|
265
|
+
) -> tuple[int, list[ActionDict], list[str]]:
|
|
266
|
+
c_state = state["c_state"]
|
|
267
|
+
if c_state == "symlink_valid":
|
|
268
|
+
try:
|
|
269
|
+
link_target = os.readlink(claude_md)
|
|
270
|
+
except OSError:
|
|
271
|
+
link_target = ""
|
|
272
|
+
points_to_agents = link_target == _AGENTS_MD or Path(
|
|
273
|
+
os.path.realpath(str(claude_md.parent / link_target))
|
|
274
|
+
) == Path(os.path.realpath(str(agents_md)))
|
|
275
|
+
if points_to_agents:
|
|
276
|
+
return (
|
|
277
|
+
3,
|
|
278
|
+
agents_actions
|
|
279
|
+
+ [
|
|
280
|
+
{
|
|
281
|
+
"file": _CLAUDE_MD,
|
|
282
|
+
"action": "symlink-skip",
|
|
283
|
+
"reason": t("already_points_to_agents"),
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
[],
|
|
287
|
+
)
|
|
288
|
+
if c_state == "regular" and _files_equal(claude_md, agents_md):
|
|
289
|
+
return (
|
|
290
|
+
3,
|
|
291
|
+
agents_actions
|
|
292
|
+
+ [
|
|
293
|
+
{
|
|
294
|
+
"file": _CLAUDE_MD,
|
|
295
|
+
"action": "skip",
|
|
296
|
+
"reason": t("claude_md_identical_content"),
|
|
297
|
+
}
|
|
298
|
+
],
|
|
299
|
+
[],
|
|
300
|
+
)
|
|
301
|
+
return self.bucket4_default(state, claude_md, agents_actions)
|
|
302
|
+
|
|
303
|
+
def resolve_canonical_doc(
|
|
304
|
+
self,
|
|
305
|
+
root: Path,
|
|
306
|
+
state: StateDict,
|
|
307
|
+
*,
|
|
308
|
+
no_symlinks: bool = False,
|
|
309
|
+
replace_claude_with_symlink: bool = False,
|
|
310
|
+
migrate_legacy_claude: bool = False,
|
|
311
|
+
) -> tuple[int, list[ActionDict], list[str]]:
|
|
312
|
+
a_state = state["a_state"]
|
|
313
|
+
c_state = state["c_state"]
|
|
314
|
+
claude_md = root / _CLAUDE_MD
|
|
315
|
+
agents_md = root / _AGENTS_MD
|
|
316
|
+
supports_symlinks = state["supports_symlinks"] and not no_symlinks
|
|
317
|
+
template = self.build_template(root)
|
|
318
|
+
|
|
319
|
+
if a_state == "absent":
|
|
320
|
+
if migrate_legacy_claude:
|
|
321
|
+
return self._migrate_legacy_claude_only(
|
|
322
|
+
root=root,
|
|
323
|
+
state=state,
|
|
324
|
+
template=template,
|
|
325
|
+
supports_symlinks=supports_symlinks,
|
|
326
|
+
)
|
|
327
|
+
return self.bucket1_no_agents(state, root, template)
|
|
328
|
+
|
|
329
|
+
agents_actions: list[ActionDict] = []
|
|
330
|
+
if not _has_marker(agents_md):
|
|
331
|
+
agents_actions.append({"file": _AGENTS_MD, "action": "append", "content": template})
|
|
332
|
+
|
|
333
|
+
if c_state == "absent":
|
|
334
|
+
return self.bucket2_agents_no_claude(root, supports_symlinks, template)
|
|
335
|
+
|
|
336
|
+
if c_state in ("symlink_valid", "regular"):
|
|
337
|
+
if c_state == "regular" and (replace_claude_with_symlink or migrate_legacy_claude):
|
|
338
|
+
return self.bucket4_replace(agents_actions, claude_md)
|
|
339
|
+
return self.bucket3(state, claude_md, agents_md, agents_actions)
|
|
340
|
+
|
|
341
|
+
return 5, [], []
|
|
342
|
+
|
|
343
|
+
def plan_rules(self, root: Path) -> tuple[list[ActionDict], list[str]]:
|
|
344
|
+
rule_path = root / ".claude" / "rules" / "gitwise.md"
|
|
345
|
+
if rule_path.exists():
|
|
346
|
+
return [
|
|
347
|
+
{
|
|
348
|
+
"file": ".claude/rules/gitwise.md",
|
|
349
|
+
"action": "skip",
|
|
350
|
+
"reason": t("already_exists"),
|
|
351
|
+
}
|
|
352
|
+
], []
|
|
353
|
+
return [
|
|
354
|
+
{
|
|
355
|
+
"file": ".claude/rules/gitwise.md",
|
|
356
|
+
"action": "create",
|
|
357
|
+
"content": _read_template("rules/gitwise.md"),
|
|
358
|
+
}
|
|
359
|
+
], []
|
|
360
|
+
|
|
361
|
+
def plan_snapshot(self, *, frozen_time: bool = False) -> list[ActionDict]:
|
|
362
|
+
return [
|
|
363
|
+
{"file": ".claude/git-snapshot.md", "action": "generate", "frozen_time": frozen_time}
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
def plan_global(
|
|
367
|
+
self,
|
|
368
|
+
home: Path,
|
|
369
|
+
*,
|
|
370
|
+
no_skills: bool = False,
|
|
371
|
+
) -> tuple[list[ActionDict], list[str], list[ActionDict]]:
|
|
372
|
+
actions: list[ActionDict] = []
|
|
373
|
+
warnings: list[str] = []
|
|
374
|
+
|
|
375
|
+
settings_actions, settings_warnings = self.plan_settings(home)
|
|
376
|
+
actions += settings_actions
|
|
377
|
+
warnings += settings_warnings
|
|
378
|
+
|
|
379
|
+
rules_actions, rules_warnings = self.plan_rules(home)
|
|
380
|
+
actions += rules_actions
|
|
381
|
+
warnings += rules_warnings
|
|
382
|
+
|
|
383
|
+
if not no_skills:
|
|
384
|
+
skills_actions, skills_warnings = plan_global_skills(home)
|
|
385
|
+
actions += skills_actions
|
|
386
|
+
warnings += skills_warnings
|
|
387
|
+
|
|
388
|
+
return actions, warnings, []
|
|
389
|
+
|
|
390
|
+
def plan(self, root: Path, context: AdapterContext) -> tuple[list[ActionDict], list[str]]:
|
|
391
|
+
if context["flags"].get("core_claude_planned", False):
|
|
392
|
+
return [], []
|
|
393
|
+
actions: list[ActionDict] = []
|
|
394
|
+
warnings: list[str] = []
|
|
395
|
+
|
|
396
|
+
settings_actions, settings_warnings = self.plan_settings(root)
|
|
397
|
+
rules_actions, rules_warnings = self.plan_rules(root)
|
|
398
|
+
|
|
399
|
+
actions += settings_actions
|
|
400
|
+
actions += rules_actions
|
|
401
|
+
actions += self.plan_snapshot(frozen_time=context["flags"].get("frozen_time", False))
|
|
402
|
+
|
|
403
|
+
warnings += settings_warnings
|
|
404
|
+
warnings += rules_warnings
|
|
405
|
+
return actions, warnings
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
ADAPTER = ClaudeAdapter()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Codex (OpenAI) provider — .codex/agents/*.toml."""
|
|
2
|
+
|
|
3
|
+
from gitwise.setup_agents.providers.base import AdapterConfig
|
|
4
|
+
|
|
5
|
+
ADAPTER = AdapterConfig(
|
|
6
|
+
name="codex",
|
|
7
|
+
display_name="Codex",
|
|
8
|
+
config_paths=(".codex/agents/gitwise.toml",),
|
|
9
|
+
template_paths=("agents/gitwise.toml.template",),
|
|
10
|
+
template_dir="share/codex",
|
|
11
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Continue provider — .continue/rules/*.md."""
|
|
2
|
+
|
|
3
|
+
from gitwise.setup_agents.providers.base import AdapterConfig
|
|
4
|
+
|
|
5
|
+
ADAPTER = AdapterConfig(
|
|
6
|
+
name="continue",
|
|
7
|
+
display_name="Continue",
|
|
8
|
+
config_paths=(".continue/rules/gitwise.md",),
|
|
9
|
+
template_paths=("rules/gitwise.md.template",),
|
|
10
|
+
template_dir="share/continue",
|
|
11
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Cursor provider — .cursor/rules/*.mdc (MDC format with YAML frontmatter)."""
|
|
2
|
+
|
|
3
|
+
from gitwise.setup_agents.providers.base import AdapterConfig
|
|
4
|
+
|
|
5
|
+
ADAPTER = AdapterConfig(
|
|
6
|
+
name="cursor",
|
|
7
|
+
display_name="Cursor",
|
|
8
|
+
config_paths=(".cursor/rules/gitwise.mdc",),
|
|
9
|
+
template_paths=("rules/gitwise.mdc.template",),
|
|
10
|
+
template_dir="share/cursor",
|
|
11
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""OpenCode provider — .opencode/agents/*.md."""
|
|
2
|
+
|
|
3
|
+
from gitwise.setup_agents.providers.base import AdapterConfig
|
|
4
|
+
|
|
5
|
+
ADAPTER = AdapterConfig(
|
|
6
|
+
name="opencode",
|
|
7
|
+
display_name="OpenCode",
|
|
8
|
+
config_paths=(".opencode/agents/gitwise.md",),
|
|
9
|
+
template_paths=("agents/gitwise.md.template",),
|
|
10
|
+
template_dir="share/opencode",
|
|
11
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Pi provider — .pi/agent/skills/gitwise.md."""
|
|
2
|
+
|
|
3
|
+
from gitwise.setup_agents.providers.base import AdapterConfig
|
|
4
|
+
|
|
5
|
+
ADAPTER = AdapterConfig(
|
|
6
|
+
name="pi",
|
|
7
|
+
display_name="Pi",
|
|
8
|
+
config_paths=(".pi/agent/skills/gitwise.md",),
|
|
9
|
+
template_paths=("skills/gitwise.md.template",),
|
|
10
|
+
template_dir="share/pi",
|
|
11
|
+
)
|