axm-git 0.2.0__tar.gz → 0.4.0__tar.gz
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.
- {axm_git-0.2.0 → axm_git-0.4.0}/.gitignore +1 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/PKG-INFO +11 -6
- {axm_git-0.2.0 → axm_git-0.4.0}/README.md +9 -5
- {axm_git-0.2.0 → axm_git-0.4.0}/docs/explanation/architecture.md +5 -4
- {axm_git-0.2.0 → axm_git-0.4.0}/docs/index.md +2 -2
- {axm_git-0.2.0 → axm_git-0.4.0}/docs/reference/cli.md +1 -1
- {axm_git-0.2.0 → axm_git-0.4.0}/docs/tutorials/getting-started.md +1 -1
- axm_git-0.4.0/pyproject.toml +382 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/_version.py +2 -2
- axm_git-0.4.0/src/axm_git/core/__init__.py +17 -0
- axm_git-0.4.0/src/axm_git/core/identity.py +210 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/core/runner.py +82 -35
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/_resolve.py +9 -8
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/await_merge.py +27 -13
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/branch_delete.py +5 -5
- axm_git-0.4.0/src/axm_git/hooks/commit_phase.py +465 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/create_branch.py +12 -8
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/create_pr.py +12 -11
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/merge_squash.py +13 -10
- axm_git-0.4.0/src/axm_git/hooks/preflight.py +142 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/pull.py +6 -6
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/push.py +6 -5
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/worktree_add.py +17 -9
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/worktree_remove.py +5 -5
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/tools/branch.py +29 -26
- axm_git-0.4.0/src/axm_git/tools/clone.py +67 -0
- axm_git-0.4.0/src/axm_git/tools/commit.py +294 -0
- axm_git-0.4.0/src/axm_git/tools/commit_preflight.py +152 -0
- axm_git-0.4.0/src/axm_git/tools/commit_text.py +217 -0
- axm_git-0.4.0/src/axm_git/tools/pr.py +108 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/tools/push.py +44 -41
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/tools/tag.py +48 -41
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/tools/worktree.py +20 -6
- axm_git-0.4.0/tests/integration/_helpers.py +30 -0
- axm_git-0.4.0/tests/integration/test_branch_delete_hook.py +50 -0
- axm_git-0.4.0/tests/integration/test_branch_delete_hook__run_git.py +119 -0
- axm_git-0.4.0/tests/integration/test_branch_name_from_ticket__worktree_add_hook.py +37 -0
- axm_git-0.4.0/tests/integration/test_build_commit_result.py +173 -0
- axm_git-0.4.0/tests/integration/test_clone__run_git.py +81 -0
- axm_git-0.4.0/tests/integration/test_commit_identity.py +263 -0
- axm_git-0.4.0/tests/integration/test_commit_phase_hook.py +659 -0
- axm_git-0.4.0/tests/integration/test_commit_phase_hook__cp.py +92 -0
- axm_git-0.2.0/tests/unit/hooks/test_commit_phase.py → axm_git-0.4.0/tests/integration/test_commit_phase_hook__run_git.py +123 -194
- axm_git-0.4.0/tests/integration/test_create_branch_hook.py +134 -0
- axm_git-0.4.0/tests/integration/test_create_branch_hook__run_git.py +31 -0
- axm_git-0.4.0/tests/integration/test_detect_package_name.py +34 -0
- axm_git-0.4.0/tests/integration/test_find_git_root.py +31 -0
- axm_git-0.4.0/tests/integration/test_get_tag_prefix.py +39 -0
- axm_git-0.4.0/tests/integration/test_git_branch_tool.py +95 -0
- axm_git-0.4.0/tests/integration/test_git_commit_tool.py +280 -0
- axm_git-0.4.0/tests/integration/test_git_preflight_tool.py +56 -0
- axm_git-0.4.0/tests/integration/test_git_profile_config__load_config.py +55 -0
- axm_git-0.4.0/tests/integration/test_git_push_tool.py +56 -0
- axm_git-0.2.0/tests/unit/tools/test_tag.py → axm_git-0.4.0/tests/integration/test_git_tag_tool.py +22 -102
- axm_git-0.4.0/tests/integration/test_ident_axm1710__resolve_identity.py +184 -0
- axm_git-0.4.0/tests/integration/test_load_config.py +169 -0
- axm_git-0.4.0/tests/integration/test_merge_squash_hook.py +97 -0
- axm_git-0.4.0/tests/integration/test_merge_squash_hook__run_git.py +63 -0
- axm_git-0.4.0/tests/integration/test_not_a_repo_error.py +37 -0
- axm_git-0.4.0/tests/integration/test_preflight_hook.py +314 -0
- axm_git-0.2.0/tests/unit/hooks/test_pull.py → axm_git-0.4.0/tests/integration/test_pull_hook.py +0 -21
- axm_git-0.2.0/tests/unit/hooks/test_push.py → axm_git-0.4.0/tests/integration/test_push_hook.py +0 -8
- axm_git-0.4.0/tests/integration/test_resolve_by_override.py +108 -0
- axm_git-0.4.0/tests/integration/test_resolve_by_schedule.py +141 -0
- axm_git-0.4.0/tests/integration/test_resolve_identity.py +385 -0
- axm_git-0.4.0/tests/integration/test_retry_commit_on_autofix.py +65 -0
- axm_git-0.4.0/tests/integration/test_run_git.py +43 -0
- axm_git-0.4.0/tests/integration/test_run_git__worktree_add_hook.py +55 -0
- axm_git-0.4.0/tests/integration/test_run_git__worktree_remove_hook.py +44 -0
- axm_git-0.4.0/tests/integration/test_stage_spec_files.py +359 -0
- axm_git-0.4.0/tests/integration/test_suggest_git_repos.py +43 -0
- axm_git-0.4.0/tests/integration/test_worktree_add_hook.py +94 -0
- axm_git-0.4.0/tests/integration/test_worktree_add_hook__worktree_remove_hook.py +33 -0
- axm_git-0.2.0/tests/unit/hooks/test_worktree_remove.py → axm_git-0.4.0/tests/integration/test_worktree_remove_hook.py +1 -20
- axm_git-0.4.0/tests/unit/core/__init__.py +0 -0
- axm_git-0.4.0/tests/unit/core/test_branch_naming.py +210 -0
- axm_git-0.4.0/tests/unit/core/test_identity.py +76 -0
- axm_git-0.4.0/tests/unit/core/test_runner.py +108 -0
- axm_git-0.4.0/tests/unit/core/test_semver.py +133 -0
- axm_git-0.4.0/tests/unit/hooks/test__resolve.py +55 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/unit/hooks/test_await_merge.py +42 -1
- axm_git-0.4.0/tests/unit/hooks/test_commit_phase.py +397 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/unit/hooks/test_create_pr.py +28 -23
- axm_git-0.4.0/tests/unit/hooks/test_preflight.py +45 -0
- axm_git-0.4.0/tests/unit/hooks/test_pull.py +30 -0
- axm_git-0.4.0/tests/unit/hooks/test_push.py +17 -0
- axm_git-0.4.0/tests/unit/tools/__init__.py +0 -0
- axm_git-0.4.0/tests/unit/tools/test_clone.py +13 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/unit/tools/test_commit.py +18 -0
- axm_git-0.4.0/tests/unit/tools/test_commit_preflight.py +401 -0
- axm_git-0.4.0/tests/unit/tools/test_commit_text.py +218 -0
- axm_git-0.4.0/tests/unit/tools/test_tag.py +211 -0
- axm_git-0.2.0/pyproject.toml +0 -206
- axm_git-0.2.0/src/axm_git/core/__init__.py +0 -1
- axm_git-0.2.0/src/axm_git/hooks/commit_phase.py +0 -171
- axm_git-0.2.0/src/axm_git/hooks/preflight.py +0 -86
- axm_git-0.2.0/src/axm_git/tools/commit.py +0 -214
- axm_git-0.2.0/src/axm_git/tools/commit_preflight.py +0 -87
- axm_git-0.2.0/src/axm_git/tools/pr.py +0 -99
- axm_git-0.2.0/tests/functional/__init__.py +0 -1
- axm_git-0.2.0/tests/functional/test_flows.py +0 -225
- axm_git-0.2.0/tests/functional/test_hook_discovery.py +0 -31
- axm_git-0.2.0/tests/functional/test_worktree_roundtrip.py +0 -70
- axm_git-0.2.0/tests/test_version.py +0 -15
- axm_git-0.2.0/tests/unit/core/test_branch_naming.py +0 -159
- axm_git-0.2.0/tests/unit/core/test_runner.py +0 -184
- axm_git-0.2.0/tests/unit/core/test_semver.py +0 -95
- axm_git-0.2.0/tests/unit/hooks/test__resolve.py +0 -51
- axm_git-0.2.0/tests/unit/hooks/test_branch_delete.py +0 -168
- axm_git-0.2.0/tests/unit/hooks/test_commit_phase_retry.py +0 -154
- axm_git-0.2.0/tests/unit/hooks/test_create_branch.py +0 -154
- axm_git-0.2.0/tests/unit/hooks/test_merge_squash.py +0 -158
- axm_git-0.2.0/tests/unit/hooks/test_preflight.py +0 -135
- axm_git-0.2.0/tests/unit/hooks/test_worktree_add.py +0 -150
- axm_git-0.2.0/tests/unit/test_tag_helpers.py +0 -153
- axm_git-0.2.0/tests/unit/tools/test_commit_preflight.py +0 -141
- {axm_git-0.2.0 → axm_git-0.4.0}/.python-version +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/CONTRIBUTING.md +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/LICENSE +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/Makefile +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/docs/howto/index.md +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/mkdocs.yml +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/__init__.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/core/branch_naming.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/core/phase_commit.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/core/semver.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/hooks/__init__.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/py.typed +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/src/axm_git/tools/__init__.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/__init__.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/conftest.py +0 -0
- {axm_git-0.2.0/tests/unit/core → axm_git-0.4.0/tests/e2e}/__init__.py +0 -0
- {axm_git-0.2.0/tests/unit/tools → axm_git-0.4.0/tests/integration}/__init__.py +0 -0
- /axm_git-0.2.0/tests/unit/core/test_phase_commit.py → /axm_git-0.4.0/tests/integration/test_get_phase_commit.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/unit/__init__.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/unit/hooks/__init__.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/unit/tools/test_branch.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/unit/tools/test_pr.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/unit/tools/test_push.py +0 -0
- {axm_git-0.2.0 → axm_git-0.4.0}/tests/unit/tools/test_worktree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axm-git
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Git workflow automation for AXM agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/axm-protocols/axm-git
|
|
6
6
|
Project-URL: Documentation, https://axm-protocols.github.io/axm-git/
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Typing :: Typed
|
|
17
17
|
Requires-Python: >=3.12
|
|
18
18
|
Requires-Dist: axm>=0.1.0
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
19
20
|
Description-Content-Type: text/markdown
|
|
20
21
|
|
|
21
22
|
<p align="center">
|
|
@@ -28,8 +29,8 @@ Description-Content-Type: text/markdown
|
|
|
28
29
|
|
|
29
30
|
<p align="center">
|
|
30
31
|
<a href="https://github.com/axm-protocols/axm-forge/actions/workflows/ci.yml"><img src="https://github.com/axm-protocols/axm-forge/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
31
|
-
<a href="https://
|
|
32
|
-
<a href="https://
|
|
32
|
+
<a href="https://forge.axm-protocols.io/audit/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-git/axm-audit.json" alt="axm-audit"></a>
|
|
33
|
+
<a href="https://forge.axm-protocols.io/init/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-git/axm-init.json" alt="axm-init"></a>
|
|
33
34
|
<a href="https://github.com/axm-protocols/axm-forge/actions/workflows/axm-quality.yml"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-git/coverage.json" alt="Coverage"></a>
|
|
34
35
|
<a href="https://pypi.org/project/axm-git/"><img src="https://img.shields.io/pypi/v/axm-git" alt="PyPI"></a>
|
|
35
36
|
<img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="Python 3.12+">
|
|
@@ -42,13 +43,14 @@ Description-Content-Type: text/markdown
|
|
|
42
43
|
|
|
43
44
|
- 🔍 **Preflight** — Structured working tree status with diff summary
|
|
44
45
|
- 🌿 **Branch** — Create or checkout branches with one call
|
|
45
|
-
- 📦 **Commit** — Batched atomic commits with auto-retry on pre-commit fixes
|
|
46
|
+
- 📦 **Commit** — Batched atomic commits with auto-retry on pre-commit fixes and optional author identity injection
|
|
46
47
|
- 🏷️ **Tag** — One-shot semver tagging from Conventional Commits
|
|
47
48
|
- 🚀 **Push** — Push with dirty-check, auto-upstream detection, and force support
|
|
48
49
|
- 🌲 **Worktree** — Add, remove, or list git worktrees
|
|
49
50
|
- 🔀 **PR** — Create GitHub pull requests with optional auto-merge
|
|
50
51
|
- 🧭 **Error Recovery** — When called on a non-git directory, tools suggest nearby git repos
|
|
51
52
|
- 🪝 **Hooks** — Lifecycle hook actions (preflight, create-branch, branch-delete, commit-phase, merge-squash, worktree-add, worktree-remove, push, create-pr, await-merge, pull-main) with `enabled` guard, auto-discovered via entry-points
|
|
53
|
+
- 🪪 **Identity** — Resolve git author from `git-profiles.toml` with schedule-based or explicit profile selection. Schedule rules apply only under user-configured `workspace_paths`; comparison is timezone-aware via the optional `timezone` field (default `Europe/Paris`)
|
|
52
54
|
- 🔎 **Phase Lookup** — `get_phase_commit()` retrieves commit hashes for protocol phases
|
|
53
55
|
|
|
54
56
|
## Installation
|
|
@@ -62,7 +64,7 @@ uv add axm-git
|
|
|
62
64
|
```python
|
|
63
65
|
# Check what changed
|
|
64
66
|
git_preflight(path="/path/to/repo")
|
|
65
|
-
# → {files: [{path: "foo.py", status: "M"}, ...], clean: false}
|
|
67
|
+
# → {files: [{path: "foo.py", status: "M"}, ...], clean: false, text: "git_preflight | 1 files · dirty\n..."}
|
|
66
68
|
|
|
67
69
|
# Create or switch branch
|
|
68
70
|
git_branch(name="feat/new-feature", path="/path/to/repo")
|
|
@@ -95,7 +97,7 @@ Report working tree changes so the agent can plan commits.
|
|
|
95
97
|
| `path` | `.` | Project root directory |
|
|
96
98
|
| `diff_lines` | `200` | Max diff lines to include (0 to disable) |
|
|
97
99
|
|
|
98
|
-
Returns: file list with status (`M`, `A`, `D`, `??`), diff stat, clean flag.
|
|
100
|
+
Returns: file list with status (`M`, `A`, `D`, `??`), diff stat, clean flag, and a compact `text` summary for agent display.
|
|
99
101
|
|
|
100
102
|
### `git_branch`
|
|
101
103
|
|
|
@@ -118,6 +120,7 @@ Execute one or more atomic commits with pre-commit hook handling.
|
|
|
118
120
|
|---|---|---|
|
|
119
121
|
| `path` | `.` | Project root directory |
|
|
120
122
|
| `commits` | *required* | List of commit specs (see below) |
|
|
123
|
+
| `profile` | `None` | Identity profile name — overrides schedule-based resolution from `git-profiles.toml` |
|
|
121
124
|
|
|
122
125
|
Each commit spec:
|
|
123
126
|
|
|
@@ -129,6 +132,8 @@ Each commit spec:
|
|
|
129
132
|
|
|
130
133
|
When a pre-commit hook auto-fixes files (e.g. ruff `--fix`), the tool re-stages and retries once automatically.
|
|
131
134
|
|
|
135
|
+
Identity is resolved once per call (not per commit). When resolved, each commit includes `--author="Name <email>"`. The result includes an `author` key (`{name, email}` or `null`).
|
|
136
|
+
|
|
132
137
|
### `git_tag`
|
|
133
138
|
|
|
134
139
|
Compute the next semver version from Conventional Commits, create and push the tag.
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://github.com/axm-protocols/axm-forge/actions/workflows/ci.yml"><img src="https://github.com/axm-protocols/axm-forge/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
11
|
-
<a href="https://
|
|
12
|
-
<a href="https://
|
|
11
|
+
<a href="https://forge.axm-protocols.io/audit/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-git/axm-audit.json" alt="axm-audit"></a>
|
|
12
|
+
<a href="https://forge.axm-protocols.io/init/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-git/axm-init.json" alt="axm-init"></a>
|
|
13
13
|
<a href="https://github.com/axm-protocols/axm-forge/actions/workflows/axm-quality.yml"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-git/coverage.json" alt="Coverage"></a>
|
|
14
14
|
<a href="https://pypi.org/project/axm-git/"><img src="https://img.shields.io/pypi/v/axm-git" alt="PyPI"></a>
|
|
15
15
|
<img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="Python 3.12+">
|
|
@@ -22,13 +22,14 @@
|
|
|
22
22
|
|
|
23
23
|
- 🔍 **Preflight** — Structured working tree status with diff summary
|
|
24
24
|
- 🌿 **Branch** — Create or checkout branches with one call
|
|
25
|
-
- 📦 **Commit** — Batched atomic commits with auto-retry on pre-commit fixes
|
|
25
|
+
- 📦 **Commit** — Batched atomic commits with auto-retry on pre-commit fixes and optional author identity injection
|
|
26
26
|
- 🏷️ **Tag** — One-shot semver tagging from Conventional Commits
|
|
27
27
|
- 🚀 **Push** — Push with dirty-check, auto-upstream detection, and force support
|
|
28
28
|
- 🌲 **Worktree** — Add, remove, or list git worktrees
|
|
29
29
|
- 🔀 **PR** — Create GitHub pull requests with optional auto-merge
|
|
30
30
|
- 🧭 **Error Recovery** — When called on a non-git directory, tools suggest nearby git repos
|
|
31
31
|
- 🪝 **Hooks** — Lifecycle hook actions (preflight, create-branch, branch-delete, commit-phase, merge-squash, worktree-add, worktree-remove, push, create-pr, await-merge, pull-main) with `enabled` guard, auto-discovered via entry-points
|
|
32
|
+
- 🪪 **Identity** — Resolve git author from `git-profiles.toml` with schedule-based or explicit profile selection. Schedule rules apply only under user-configured `workspace_paths`; comparison is timezone-aware via the optional `timezone` field (default `Europe/Paris`)
|
|
32
33
|
- 🔎 **Phase Lookup** — `get_phase_commit()` retrieves commit hashes for protocol phases
|
|
33
34
|
|
|
34
35
|
## Installation
|
|
@@ -42,7 +43,7 @@ uv add axm-git
|
|
|
42
43
|
```python
|
|
43
44
|
# Check what changed
|
|
44
45
|
git_preflight(path="/path/to/repo")
|
|
45
|
-
# → {files: [{path: "foo.py", status: "M"}, ...], clean: false}
|
|
46
|
+
# → {files: [{path: "foo.py", status: "M"}, ...], clean: false, text: "git_preflight | 1 files · dirty\n..."}
|
|
46
47
|
|
|
47
48
|
# Create or switch branch
|
|
48
49
|
git_branch(name="feat/new-feature", path="/path/to/repo")
|
|
@@ -75,7 +76,7 @@ Report working tree changes so the agent can plan commits.
|
|
|
75
76
|
| `path` | `.` | Project root directory |
|
|
76
77
|
| `diff_lines` | `200` | Max diff lines to include (0 to disable) |
|
|
77
78
|
|
|
78
|
-
Returns: file list with status (`M`, `A`, `D`, `??`), diff stat, clean flag.
|
|
79
|
+
Returns: file list with status (`M`, `A`, `D`, `??`), diff stat, clean flag, and a compact `text` summary for agent display.
|
|
79
80
|
|
|
80
81
|
### `git_branch`
|
|
81
82
|
|
|
@@ -98,6 +99,7 @@ Execute one or more atomic commits with pre-commit hook handling.
|
|
|
98
99
|
|---|---|---|
|
|
99
100
|
| `path` | `.` | Project root directory |
|
|
100
101
|
| `commits` | *required* | List of commit specs (see below) |
|
|
102
|
+
| `profile` | `None` | Identity profile name — overrides schedule-based resolution from `git-profiles.toml` |
|
|
101
103
|
|
|
102
104
|
Each commit spec:
|
|
103
105
|
|
|
@@ -109,6 +111,8 @@ Each commit spec:
|
|
|
109
111
|
|
|
110
112
|
When a pre-commit hook auto-fixes files (e.g. ruff `--fix`), the tool re-stages and retries once automatically.
|
|
111
113
|
|
|
114
|
+
Identity is resolved once per call (not per commit). When resolved, each commit includes `--author="Name <email>"`. The result includes an `author` key (`{name, email}` or `null`).
|
|
115
|
+
|
|
112
116
|
### `git_tag`
|
|
113
117
|
|
|
114
118
|
Compute the next semver version from Conventional Commits, create and push the tag.
|
|
@@ -59,6 +59,7 @@ graph TD
|
|
|
59
59
|
Branch --> Runner
|
|
60
60
|
Push --> Runner
|
|
61
61
|
PF --> Runner
|
|
62
|
+
PF -->|"_render_text()"| Preflight
|
|
62
63
|
CB --> Runner
|
|
63
64
|
CP --> Runner
|
|
64
65
|
CP --> PhaseCommit
|
|
@@ -91,8 +92,8 @@ graph TD
|
|
|
91
92
|
Each tool exposes an `execute(*, path, ..., **kwargs) → ToolResult` method with explicit typed parameters:
|
|
92
93
|
|
|
93
94
|
- **`GitTagTool`** — Full tag workflow: check clean tree, check CI, compute semver bump, create tag, verify hatch-vcs, push.
|
|
94
|
-
- **`GitCommitTool`** — Stage files, commit with pre-commit hooks, auto-retry on linter fixes. Supports batched commits.
|
|
95
|
-
- **`GitPreflightTool`** — Parse `git status --porcelain` and `git diff --stat` into structured data.
|
|
95
|
+
- **`GitCommitTool`** — Stage files, commit with pre-commit hooks, auto-retry on linter fixes. Supports batched commits. Each commit spec is processed by `_process_single_commit()` (validate → stage → commit → record).
|
|
96
|
+
- **`GitPreflightTool`** — Parse `git status --porcelain` and `git diff --stat` into structured data. Uses `find_git_root()` to scope status and diff to the target subdirectory via pathspec, matching the behaviour of `PreflightHook`.
|
|
96
97
|
- **`GitBranchTool`** — Create or checkout a branch. Supports `from_ref` (branch from tag/commit) and `checkout_only` (switch without creating).
|
|
97
98
|
- **`GitPushTool`** — Push with dirty-check guard, auto-upstream detection, custom remote, and force-push support.
|
|
98
99
|
|
|
@@ -100,7 +101,7 @@ Each tool exposes an `execute(*, path, ..., **kwargs) → ToolResult` method wit
|
|
|
100
101
|
|
|
101
102
|
Shared logic used by multiple tools:
|
|
102
103
|
|
|
103
|
-
- **`runner.py`** — `run_git()` and `run_gh()` subprocess wrappers, `gh_available()` auth check, `detect_package_name()` from `pyproject.toml`. Also provides `suggest_git_repos()` (scans for child directories that are git repos) and `not_a_repo_error()` (enriches "not a git repository" errors with suggestions for nearby repos).
|
|
104
|
+
- **`runner.py`** — `find_git_root()` locates the repository root via `rev-parse --show-toplevel`, `run_git()` and `run_gh()` subprocess wrappers, `gh_available()` auth check, `detect_package_name()` from `pyproject.toml`. Also provides `suggest_git_repos()` (scans for child directories that are git repos) and `not_a_repo_error()` (enriches "not a git repository" errors with suggestions for nearby repos). All subprocess call-sites pass an explicit `timeout=` kwarg (defaults: `DEFAULT_GIT_TIMEOUT=30s`, `DEFAULT_GH_TIMEOUT=120s`, `uv sync`=600s); `run_git`/`run_gh` log a warning and re-raise `subprocess.TimeoutExpired`, which tool entry points catch and convert to `ToolResult` via `timeout_error_result()`. This is an interim shim pending `axm-common.run_safe`.
|
|
104
105
|
- **`semver.py`** — `parse_tag()` for version parsing, `compute_bump()` for Conventional Commits analysis (returns `VersionBump` with next version + reason).
|
|
105
106
|
- **`phase_commit.py`** — `get_phase_commit()` looks up the commit hash for a given protocol phase name by searching git log.
|
|
106
107
|
|
|
@@ -110,7 +111,7 @@ Lifecycle hook actions conforming to the `HookAction` protocol from `axm.hooks.b
|
|
|
110
111
|
|
|
111
112
|
All hooks accept an `enabled` param (default `True`). Pass `enabled=False` to skip git operations entirely (returns `HookResult.ok(skipped=True, reason="git disabled")`).
|
|
112
113
|
|
|
113
|
-
- **`PreflightHook`** — Runs a structured working tree status check before a phase begins. Entry point: `git:preflight`.
|
|
114
|
+
- **`PreflightHook`** — Runs a structured working tree status check before a phase begins. Returns a compact `text` render (via `_render_text` from `commit_preflight`) alongside structured metadata. Entry point: `git:preflight`.
|
|
114
115
|
- **`CreateBranchHook`** — Creates a session branch. Accepts `branch`, `ticket_id`, `ticket_title`, and `ticket_labels` params; `_resolve_branch()` derives the final branch name from those inputs. Skips if not a git repo.
|
|
115
116
|
- **`BranchDeleteHook`** — Deletes a branch via `git branch -D`. Branch name resolved from `branch` param then `branch` context key. Entry point: `git:branch-delete`.
|
|
116
117
|
- **`CommitPhaseHook`** — Stages all changes, commits with `[axm] {phase_name}`. Pass `from_outputs=True` to derive staged files from protocol outputs instead of staging everything. Skips if nothing to commit.
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
9
|
<a href="https://github.com/axm-protocols/axm-forge/actions/workflows/ci.yml"><img src="https://github.com/axm-protocols/axm-forge/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
10
|
-
<a href="https://
|
|
11
|
-
<a href="https://
|
|
10
|
+
<a href="https://forge.axm-protocols.io/audit/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-git/axm-audit.json" alt="axm-audit"></a>
|
|
11
|
+
<a href="https://forge.axm-protocols.io/init/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-git/axm-init.json" alt="axm-init"></a>
|
|
12
12
|
<a href="https://github.com/axm-protocols/axm-forge/actions/workflows/axm-quality.yml"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/axm-protocols/axm-forge/gh-pages/badges/axm-git/coverage.json" alt="Coverage"></a>
|
|
13
13
|
<a href="https://pypi.org/project/axm-git/"><img src="https://img.shields.io/pypi/v/axm-git" alt="PyPI"></a>
|
|
14
14
|
<img src="https://img.shields.io/badge/python-3.12+-blue.svg" alt="Python 3.12+" />
|
|
@@ -27,4 +27,4 @@ Hook actions auto-discovered via the `axm.hooks` entry-point group by `HookRegis
|
|
|
27
27
|
|
|
28
28
|
## Python API
|
|
29
29
|
|
|
30
|
-
Auto-generated API reference is available under [Python API](
|
|
30
|
+
Auto-generated API reference is available under [Python API](../../reference/axm_git/index.md).
|
|
@@ -114,4 +114,4 @@ The tool verifies the tree is clean before pushing, and automatically sets the u
|
|
|
114
114
|
## Next Steps
|
|
115
115
|
|
|
116
116
|
- [Architecture](../explanation/architecture.md) — How the project is structured
|
|
117
|
-
- [API Reference](
|
|
117
|
+
- [API Reference](../../reference/axm_git/index.md) — Full API documentation
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "axm-git"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Git workflow automation for AXM agents"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "Apache-2.0" }
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
authors = [{ name = "Gabriel", email = "gabriel@axm.dev" }]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Programming Language :: Python :: 3.12",
|
|
12
|
+
"Programming Language :: Python :: 3.13",
|
|
13
|
+
"Typing :: Typed",
|
|
14
|
+
"License :: OSI Approved :: Apache Software License",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"axm>=0.1.0",
|
|
19
|
+
"pydantic>=2.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.entry-points."axm.tools"]
|
|
23
|
+
git_tag = "axm_git.tools.tag:GitTagTool"
|
|
24
|
+
git_preflight = "axm_git.tools.commit_preflight:GitPreflightTool"
|
|
25
|
+
git_commit = "axm_git.tools.commit:GitCommitTool"
|
|
26
|
+
git_branch = "axm_git.tools.branch:GitBranchTool"
|
|
27
|
+
git_push = "axm_git.tools.push:GitPushTool"
|
|
28
|
+
git_clone = "axm_git.tools.clone:GitCloneTool"
|
|
29
|
+
git_worktree = "axm_git.tools.worktree:GitWorktreeTool"
|
|
30
|
+
git_pr = "axm_git.tools.pr:GitPRTool"
|
|
31
|
+
|
|
32
|
+
[project.entry-points."axm.hooks"]
|
|
33
|
+
"git:branch-delete" = "axm_git.hooks.branch_delete:BranchDeleteHook"
|
|
34
|
+
"git:create-branch" = "axm_git.hooks.create_branch:CreateBranchHook"
|
|
35
|
+
"git:commit-phase" = "axm_git.hooks.commit_phase:CommitPhaseHook"
|
|
36
|
+
"git:merge-squash" = "axm_git.hooks.merge_squash:MergeSquashHook"
|
|
37
|
+
"git:preflight" = "axm_git.hooks.preflight:PreflightHook"
|
|
38
|
+
"git:worktree-add" = "axm_git.hooks.worktree_add:WorktreeAddHook"
|
|
39
|
+
"git:worktree-remove" = "axm_git.hooks.worktree_remove:WorktreeRemoveHook"
|
|
40
|
+
"git:push" = "axm_git.hooks.push:PushHook"
|
|
41
|
+
"git:create-pr" = "axm_git.hooks.create_pr:CreatePRHook"
|
|
42
|
+
"git:await-merge" = "axm_git.hooks.await_merge:AwaitMergeHook"
|
|
43
|
+
"git:pull-main" = "axm_git.hooks.pull:PullHook"
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/axm-protocols/axm-git"
|
|
47
|
+
Documentation = "https://axm-protocols.github.io/axm-git/"
|
|
48
|
+
Repository = "https://github.com/axm-protocols/axm-git.git"
|
|
49
|
+
Issues = "https://github.com/axm-protocols/axm-git/issues"
|
|
50
|
+
|
|
51
|
+
[build-system]
|
|
52
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
53
|
+
build-backend = "hatchling.build"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.version]
|
|
56
|
+
source = "vcs"
|
|
57
|
+
tag-pattern = "git/v(?P<version>.*)"
|
|
58
|
+
raw-options = { root = "../..", git_describe_command = "git describe --dirty --tags --long --match git/v*" }
|
|
59
|
+
fallback-version = "0.1.0.dev0"
|
|
60
|
+
|
|
61
|
+
[tool.hatch.build.hooks.vcs]
|
|
62
|
+
version-file = "src/axm_git/_version.py"
|
|
63
|
+
|
|
64
|
+
[tool.hatch.build.targets.wheel]
|
|
65
|
+
packages = ["src/axm_git"]
|
|
66
|
+
|
|
67
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
68
|
+
"docs/index.md" = "axm_git/docs/index.md"
|
|
69
|
+
|
|
70
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
# UV Configuration
|
|
72
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
73
|
+
[tool.uv]
|
|
74
|
+
managed = true
|
|
75
|
+
|
|
76
|
+
[dependency-groups]
|
|
77
|
+
dev = [
|
|
78
|
+
"pytest>=9.0.3",
|
|
79
|
+
"pytest-cov>=7.1.0",
|
|
80
|
+
"pytest-mock>=3.15.1",
|
|
81
|
+
"ruff==0.15.15",
|
|
82
|
+
"mypy>=1.20.0",
|
|
83
|
+
"pre-commit>=4.0",
|
|
84
|
+
"pip-audit>=2.0",
|
|
85
|
+
]
|
|
86
|
+
docs = [
|
|
87
|
+
"mkdocs-material>=9.0",
|
|
88
|
+
"mkdocstrings[python]>=1.0.3",
|
|
89
|
+
"mkdocs-gen-files>=0.5",
|
|
90
|
+
"mkdocs-literate-nav>=0.6.3",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
94
|
+
# Ruff Configuration
|
|
95
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
[tool.ruff]
|
|
97
|
+
target-version = "py312"
|
|
98
|
+
line-length = 88
|
|
99
|
+
src = ["src", "tests"]
|
|
100
|
+
exclude = ["*/_version.py"]
|
|
101
|
+
|
|
102
|
+
[tool.ruff.lint]
|
|
103
|
+
select = ["E", "F", "I", "N", "UP", "B", "BLE", "C4", "C901", "S", "T20", "PLE", "PLR", "RUF"]
|
|
104
|
+
ignore = ["S101", "S603", "S607"]
|
|
105
|
+
|
|
106
|
+
[tool.ruff.lint.per-file-ignores]
|
|
107
|
+
"tests/*" = ["S101", "S105", "S106", "S108", "S603", "S607", "PLR2004", "PLR0913"]
|
|
108
|
+
"src/axm_git/hooks/await_merge.py" = ["PLR0911"]
|
|
109
|
+
"src/axm_git/tools/tag.py" = ["PLR0911"]
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint.mccabe]
|
|
112
|
+
max-complexity = 10
|
|
113
|
+
|
|
114
|
+
[tool.ruff.lint.pylint]
|
|
115
|
+
max-args = 5
|
|
116
|
+
max-branches = 12
|
|
117
|
+
max-returns = 6
|
|
118
|
+
max-statements = 50
|
|
119
|
+
max-locals = 15
|
|
120
|
+
|
|
121
|
+
[tool.ruff.lint.isort]
|
|
122
|
+
known-first-party = ["axm_git"]
|
|
123
|
+
|
|
124
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
+
# MyPy Configuration
|
|
126
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
127
|
+
[tool.mypy]
|
|
128
|
+
python_version = "3.12"
|
|
129
|
+
strict = true
|
|
130
|
+
disallow_any_generics = true
|
|
131
|
+
disallow_any_unimported = true
|
|
132
|
+
disallow_any_explicit = true
|
|
133
|
+
pretty = true
|
|
134
|
+
warn_return_any = true
|
|
135
|
+
warn_unused_ignores = true
|
|
136
|
+
disallow_untyped_defs = true
|
|
137
|
+
disallow_incomplete_defs = true
|
|
138
|
+
check_untyped_defs = true
|
|
139
|
+
show_error_codes = true
|
|
140
|
+
|
|
141
|
+
[[tool.mypy.overrides]]
|
|
142
|
+
module = ["tests.*"]
|
|
143
|
+
disallow_untyped_defs = false
|
|
144
|
+
disallow_any_explicit = false
|
|
145
|
+
disable_error_code = [
|
|
146
|
+
"index",
|
|
147
|
+
"arg-type",
|
|
148
|
+
"attr-defined",
|
|
149
|
+
"operator",
|
|
150
|
+
"union-attr",
|
|
151
|
+
"call-overload",
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
155
|
+
# Pytest Configuration
|
|
156
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
157
|
+
[tool.pytest.ini_options]
|
|
158
|
+
testpaths = ["tests"]
|
|
159
|
+
pythonpath = ["src"]
|
|
160
|
+
python_files = "test_*.py"
|
|
161
|
+
addopts = [
|
|
162
|
+
"-v",
|
|
163
|
+
"--strict-markers",
|
|
164
|
+
"--strict-config",
|
|
165
|
+
"--tb=short",
|
|
166
|
+
"--import-mode=importlib",
|
|
167
|
+
"--cov=src/axm_git",
|
|
168
|
+
"--cov-report=term-missing",
|
|
169
|
+
"--cov-report=html:coverage_html",
|
|
170
|
+
"--cov-report=xml",
|
|
171
|
+
"--cov-fail-under=0",
|
|
172
|
+
]
|
|
173
|
+
filterwarnings = ["error"]
|
|
174
|
+
markers = [
|
|
175
|
+
"integration: tests that touch real I/O (git, filesystem, network)",
|
|
176
|
+
"e2e: end-to-end tests via subprocess/CLI",
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
180
|
+
# Coverage Configuration
|
|
181
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
182
|
+
[tool.coverage.run]
|
|
183
|
+
source = ["src/axm_git"]
|
|
184
|
+
branch = true
|
|
185
|
+
relative_files = true
|
|
186
|
+
omit = ["*/tests/*"]
|
|
187
|
+
|
|
188
|
+
[tool.coverage.xml]
|
|
189
|
+
output = "coverage.xml"
|
|
190
|
+
|
|
191
|
+
[tool.coverage.report]
|
|
192
|
+
exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:", "raise NotImplementedError"]
|
|
193
|
+
|
|
194
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
195
|
+
# Git-Cliff Changelog Configuration
|
|
196
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
197
|
+
[tool.git-cliff.changelog]
|
|
198
|
+
header = """
|
|
199
|
+
# Changelog
|
|
200
|
+
|
|
201
|
+
All notable changes to this project will be documented in this file.
|
|
202
|
+
"""
|
|
203
|
+
body = """
|
|
204
|
+
{% if version %}\
|
|
205
|
+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
206
|
+
{% else %}\
|
|
207
|
+
## [Unreleased]
|
|
208
|
+
{% endif %}\
|
|
209
|
+
{% for group, commits in commits | group_by(attribute="group") %}
|
|
210
|
+
### {{ group | striptags | trim | upper_first }}
|
|
211
|
+
{% for commit in commits %}
|
|
212
|
+
- {{ commit.message | upper_first }}\
|
|
213
|
+
{% endfor %}
|
|
214
|
+
{% endfor %}
|
|
215
|
+
"""
|
|
216
|
+
trim = true
|
|
217
|
+
|
|
218
|
+
[tool.git-cliff.git]
|
|
219
|
+
conventional_commits = true
|
|
220
|
+
filter_unconventional = true
|
|
221
|
+
commit_parsers = [
|
|
222
|
+
{ message = "^chore", skip = true },
|
|
223
|
+
{ message = "^ci", skip = true },
|
|
224
|
+
{ message = "^feat", group = "🚀 Features" },
|
|
225
|
+
{ message = "^fix", group = "🐛 Bug Fixes" },
|
|
226
|
+
{ message = "^doc", group = "📚 Documentation" },
|
|
227
|
+
{ message = "^perf", group = "⚡ Performance" },
|
|
228
|
+
{ message = "^refactor", group = "🔧 Refactor" },
|
|
229
|
+
{ message = "^test", group = "🧪 Tests" },
|
|
230
|
+
]
|
|
231
|
+
tag_pattern = "git/v[0-9].*"
|
|
232
|
+
include_path = "packages/axm-git/**"
|
|
233
|
+
|
|
234
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
235
|
+
# Deptry Configuration
|
|
236
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
237
|
+
[tool.deptry]
|
|
238
|
+
known_first_party = ["axm_git"]
|
|
239
|
+
|
|
240
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
241
|
+
# axm-audit Configuration
|
|
242
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
243
|
+
[tool.axm-audit.mirror]
|
|
244
|
+
# I/O-heavy git hooks: fully covered by tests/integration/ scenarios that
|
|
245
|
+
# exercise real subprocess calls. Unit-level mock-everything tests would
|
|
246
|
+
# duplicate coverage with weaker assertions.
|
|
247
|
+
exempt_paths = [
|
|
248
|
+
"hooks/branch_delete.py",
|
|
249
|
+
"hooks/create_branch.py",
|
|
250
|
+
"hooks/merge_squash.py",
|
|
251
|
+
"hooks/worktree_add.py",
|
|
252
|
+
"hooks/worktree_remove.py",
|
|
253
|
+
"core/phase_commit.py",
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
257
|
+
hash = "99ce49e6bcd9"
|
|
258
|
+
reason = "6 failure-path tests across 3 classes assert on distinct error messages (commit_spec presence, type, files key, missing-file diagnostics, retry exhaustion) and use different fixtures (mock-based vs real-repo) -- parametrization would erase the validation/diagnostic divergence"
|
|
259
|
+
|
|
260
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
261
|
+
hash = "7e0906172112"
|
|
262
|
+
reason = "tests cover two distinct repo layouts (flat git repo via tmp_git_repo vs nested workspace via tmp_workspace_repo with tuple unpacking) -- different fixtures and scenarios, no shared data to parametrize"
|
|
263
|
+
|
|
264
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
265
|
+
hash = "5ef18a0e31ec"
|
|
266
|
+
reason = "tests cover opposite branches of retry-on-autofix control flow (retry path with 4 mocked git calls and stage call_count==2 vs clean path with 3 mocked git calls and stage call_count==1) -- collapsing them would obscure the call-sequence verification that is the whole point"
|
|
267
|
+
|
|
268
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
269
|
+
hash = "3a0c99a704de"
|
|
270
|
+
reason = "three verify_hatch_vcs failure paths exercise structurally different subprocess mock setups (return_value with rc=1 for sync_fails, side_effect callback with call counter for version_check_fails, side_effect=FileNotFoundError for uv_not_found) -- collapsing into parametrize would erase the mock-setup divergence that documents each failure mode"
|
|
271
|
+
|
|
272
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
273
|
+
hash = "e3d125f53c7c"
|
|
274
|
+
reason = "test_dirty_tree and test_ci_red_blocks cover unrelated GitTagTool preconditions (dirty working tree vs red CI) and patch different collaborators (run_git status output vs check_ci return value) -- shared shape is just `execute fails` but the failure cause and the patched seam differ"
|
|
275
|
+
|
|
276
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
277
|
+
hash = "8efb16a1f64a"
|
|
278
|
+
reason = "test_deleted_tracked_file_stages_successfully stages a pure deletion (file removed from disk, staged as `D`), while test_deleted_then_recreated_file stages a deletion followed by recreation with different content (staged as content modification on a re-existing file) -- distinct git states despite identical assertions, parametrize would obscure the disk-state divergence"
|
|
279
|
+
|
|
280
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
281
|
+
hash = "31cab244bf8d"
|
|
282
|
+
reason = "11 tests span three classes (TestResolveIdentity, TestScheduleBoundaries, TestIsAxmWorkspace) and three concerns (schedule resolution by time/day, schedule boundary inclusivity, axm-workspace detection) with several inline-TOML setups (default VALID_TOML, minimal `[default]` only, multi-rule first-wins, symlink) -- collapsing into a single parametrize would conflate three independent behaviors and obscure the per-class documentation intent"
|
|
283
|
+
|
|
284
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
285
|
+
hash = "53396655fe5b"
|
|
286
|
+
reason = "parse_tag cluster mixes 3 success-path equality assertions (parse_tag(s) == tuple) with 1 pytest.raises ValueError invalid case -- success vs raises observation surfaces are heterogeneous, parametrize would force an awkward optional-expected/optional-raises matrix that erases the failure-mode documentation"
|
|
287
|
+
|
|
288
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
289
|
+
hash = "b52a6d37c8d3"
|
|
290
|
+
reason = "three resolve_identity scenarios document distinct workspace-paths topologies (single-path match, multi-path selection where target is in second root, symlink resolution of a real_root via link) -- shared `result.name == Work` assertion masks substantively different setup graphs that the test names are meant to surface"
|
|
291
|
+
|
|
292
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
293
|
+
hash = "a75d19003c8d"
|
|
294
|
+
reason = "outside-paths vs empty-paths exercise opposite branches of the workspace-paths fall-through (non-empty list with target outside, vs empty list short-circuit) -- both reach the default identity but via different code paths and different fixture topologies, parametrize would collapse the branch-coverage intent"
|
|
295
|
+
|
|
296
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
297
|
+
hash = "8f4b32ba6350"
|
|
298
|
+
reason = "closed-without-merge and network-error exercise distinct failure paths (PR state=CLOSED with rc=0 vs gh subprocess rc=1 stderr) with different mock setups and different error-message substrings (`closed without merging` vs `failed to query`) -- the failure-mode divergence is the whole point"
|
|
299
|
+
|
|
300
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
301
|
+
hash = "edd3b4d908fd"
|
|
302
|
+
reason = "test_git_failure patches only run_git and asserts the bare 'not a git repository' message, while test_not_git_repo_no_suggestions also patches suggest_git_repos and verifies the no-suggestions branch -- different patch topologies and different error-rendering branches, parametrize would require an awkward optional-second-patch matrix"
|
|
303
|
+
|
|
304
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
305
|
+
hash = "045fc214b278"
|
|
306
|
+
reason = "7 load_config tests span 3 behavior groups -- silent_on_missing/empty assert result is None AND caplog.records == 0 (no warning), missing_file/invalid_toml/empty_file assert result is None ONLY (no caplog observation), warns_on_malformed_toml/invalid_schema assert result is None AND caplog.records >= 1 (warning emitted) -- the warning-emission observation surface differs across groups (zero vs none vs nonzero), and the file-content fixtures encode the silent-vs-noisy contract that parametrize would erase"
|
|
307
|
+
|
|
308
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
309
|
+
hash = "754178fe7dfb"
|
|
310
|
+
reason = "4 suggest_git_repos tests exercise 4 distinct directory topologies and 4 different return shapes -- finds_repos builds two child .git dirs (returns sorted [alpha, beta]), in_git_repo plants .git at the root itself (early-return []), no_children uses bare tmp_path (returns []), permission_error chmod 0o000 on a sibling with try/finally cleanup (returns [ok] despite the unreadable peer) -- shared assertion is `result == list[str]` but the values, setups, and the os-permission-recovery teardown differ substantively"
|
|
311
|
+
|
|
312
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
313
|
+
hash = "f1c75d2de6f0"
|
|
314
|
+
reason = "4 gh_available tests exercise structurally distinct mock topologies for the shutil.which + subprocess.run pair -- not_installed (which=None, subprocess never called), auth_ok (which=path + CompletedProcess rc=0), auth_fail (which=path + CompletedProcess rc=1), timeout (which=path + subprocess raises TimeoutExpired) -- each documents an independent failure/success seam, parametrize would force a 3-axis (which_value, return_value, side_effect) matrix that erases the per-failure-mode intent"
|
|
315
|
+
|
|
316
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
317
|
+
hash = "8ceb7c3d4980"
|
|
318
|
+
reason = "test_commit_with_identity (single commit) and test_batch_commits_same_identity (two commits) exercise different SUT shapes -- single asserts len(commit_calls)==1 with one _AUTHOR_FLAG check, batch asserts len(commit_calls)==2 with per-call _AUTHOR_FLAG iteration AND resolve_identity.assert_called_once verifying identity is cached across commits not re-resolved per commit -- batch documents a caching invariant single cannot, parametrize would erase the per-commit-iteration assertion"
|
|
319
|
+
|
|
320
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
321
|
+
hash = "5d1c55c2cc51"
|
|
322
|
+
reason = "test_not_found patches run_git to return empty stdout (exercises the git-ran-but-no-match branch), while test_not_git_repo skips mocking entirely and relies on the .git directory absence guard (exercises the early-return branch before any subprocess call) -- shared `result is None` assertion masks two structurally different mock topologies and two distinct control-flow exits"
|
|
323
|
+
|
|
324
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
325
|
+
hash = "9298be6e5099"
|
|
326
|
+
reason = "test_no_suggestions feeds the 'fatal: not a git repository' string with no child .git dirs (asserts no suggestions in result.data), while test_other_error_passthrough feeds an unrelated 'fatal: some other error' message with a child .git planted (asserts the scanner short-circuits on non-repo errors) -- different error inputs, different scanner branches, different substring assertions ('not a git repository' vs 'some other error')"
|
|
327
|
+
|
|
328
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
329
|
+
hash = "e84d13905500"
|
|
330
|
+
reason = "test_commit_legacy_with_identity drives the legacy `_commit_legacy` codepath with the `_run_git_success` fixture (status/add/commit/rev-parse mocks, no commit_spec in context), while test_commit_from_outputs_with_identity drives the newer `commit_from_outputs` codepath with `_commit_from_outputs_deps` fixture (ls-files/diff/add/commit mocks plus a commit_spec dict in context) -- two distinct SUT entry points, two distinct fixture topologies, parametrize would conflate the legacy/modern API boundary"
|
|
331
|
+
|
|
332
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
333
|
+
hash = "1e9f84905d11"
|
|
334
|
+
reason = "test_branch_delete_success uses the flat `tmp_git_repo: Path` fixture and runs the hook with working_dir == git root, while test_subdirectory_of_git_repo uses the nested `tmp_workspace_repo: tuple[Path, Path]` fixture (git_root, pkg_dir unpacking) and runs the hook with working_dir == pkg_dir to exercise sub-directory resolution -- different fixture shapes, different working-dir topologies, parametrize cannot bridge tuple-vs-Path fixtures cleanly"
|
|
335
|
+
|
|
336
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
337
|
+
hash = "3cb3f5c55fe3"
|
|
338
|
+
reason = "11 tests span 8 hook classes (CommitPhaseHook, CreateBranchHook, BranchDeleteHook, PullHook, PushHook, WorktreeAddHook, WorktreeRemoveHook, AwaitMergeHook) across 2 pyramid levels (10 integration + 1 unit) with 4+ distinct fixtures (tmp_path, tmp_git_repo, _make_context, monkeypatch-only) and 2 metadata observation surfaces (skipped is True vs pulled/pushed is True for pull_already_up_to_date and push_everything_up_to_date) -- worktree_remove::test_skip_missing_path even uses tmp_git_repo for a different skip reason (missing worktree path, not non-repo); the apparent shared shape `success + boolean metadata` hides 8 distinct hook contracts that cannot share a fixture or parametrize cleanly"
|
|
339
|
+
|
|
340
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
341
|
+
hash = "0c17112c5c87"
|
|
342
|
+
reason = "8 tests across 5 files exercise distinct SUTs and fixtures -- CommitPhaseHook::test_workspace_nothing_to_commit uses tmp_workspace_repo tuple-unpacking with run_git side-effects; PreflightHook::test_not_a_repo uses bare tmp_path while test_clean_repo uses tmp_git_repo (different repo setup, both via shared conftest); PullHook/PushHook ::test_*_not_git_repo use tmp_path with no monkeypatching; CreatePRHook trio (test_create_pr_skip_no_gh patches only gh_available=False, test_create_pr_already_exists patches gh_available+run_gh with conditional rc=1 stderr 'already exists' AND a gh view JSON response, test_create_pr_auto_merge_failure_non_fatal patches gh_available+run_gh with rc=0 for create + rc=1 for merge) -- each has a distinct mock topology and asserts on different metadata keys (skipped/reason vs pr_url/already_existed vs pr_url/auto_merge=False), parametrize would erase the per-failure-mode setup divergence"
|
|
343
|
+
|
|
344
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
345
|
+
hash = "e7377c7b0e34"
|
|
346
|
+
reason = "5 tests across 4 files target 4 distinct hook SUTs (BranchDeleteHook, CreateBranchHook, MergeSquashHook, PreflightHook) with different fixtures and context shapes -- branch_delete_from_context resolves branch from context dict on tmp_git_repo, create_branch::test_default_working_dir uses monkeypatch.chdir to test the '.' fallback while test_create_branch_session_fallback uses working_dir-in-context path (different setup mechanism for branch naming), merge_squash_branch_from_context requires tmp_git_repo_with_named_branch (pre-created branch fixture), preflight_dict_worktree_path tests dict-typed worktree_path unwrapping with a one-file dirty repo -- different hook contracts, different context-resolution paths, no shared parametrizable matrix"
|
|
347
|
+
|
|
348
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
349
|
+
hash = "ba0764a49848"
|
|
350
|
+
reason = "canonical 'every hook has a disabled test' pattern -- 4 tests across 4 files instantiate 4 distinct hook classes (CommitPhaseHook, CreateBranchHook, BranchDeleteHook, MergeSquashHook) with hook-specific context payloads (phase_name for commit_phase, session_id+protocol_name for merge_squash, session_id only for branch hooks); each test documents the per-hook 'enabled=False short-circuits to skipped reason=git disabled' contract for its own SUT and cannot be folded across hook classes without losing the per-hook coverage signal"
|
|
351
|
+
|
|
352
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
353
|
+
hash = "e7e9aeba284a"
|
|
354
|
+
reason = "ambiguous_distinct_sut -- 4 disabled-path tests instantiate 4 different hook classes (PreflightHook, WorktreeAddHook, PullHook, PushHook) split across pyramid levels (2 integration with real fixtures: tmp_git_repo + _make_context helper; 2 unit with no fixtures: working_dir='.' literal); the per-hook disabled-contract assertion cannot be parametrized across distinct hook constructors and distinct context shapes (worktree_add needs ticket_id, pull/push need only working_dir)"
|
|
355
|
+
|
|
356
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
357
|
+
hash = "37d744e2512d"
|
|
358
|
+
reason = "ambiguous_template_pair -- 4 `test_name` assertions across 4 distinct MCP tool classes (GitBranchTool, GitPRTool, GitPushTool, GitWorktreeTool) each verifying its own .name string literal ('git_branch', 'git_pr', 'git_push', 'git_worktree'); identical shape but heterogeneous SUTs with hard-coded per-tool string contracts, no parametrizable matrix"
|
|
359
|
+
|
|
360
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
361
|
+
hash = "00c56eb1fb91"
|
|
362
|
+
reason = "ambiguous_template_pair -- 3 `test_name` assertions across 3 distinct MCP tool classes (GitCommitTool, GitPreflightTool, GitTagTool) each verifying its own .name string literal ('git_commit', 'git_preflight', 'git_tag'); cookie-cutter shape but heterogeneous SUTs with hard-coded per-tool string contracts, no parametrizable matrix"
|
|
363
|
+
|
|
364
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
365
|
+
hash = "b8af6ffac47d"
|
|
366
|
+
reason = "ambiguous_distinct_literals -- 3 failure-path tests target 3 distinct hook SUTs (CreateBranchHook, PullHook, PushHook): branch_exists_fails uses tmp_git_repo and double-execute to trigger 'branch already exists' git error, pull_hook_failure uses monkeypatched run_git returning rc=1 stderr 'fatal: couldn't find remote ref', push_hook_fail_on_error uses monkeypatched run_git rc=1 stderr 'remote rejected' with branch in context -- distinct fixtures, distinct trigger mechanisms (real git collision vs mocked subprocess), distinct error strings, no parametrizable matrix"
|
|
367
|
+
|
|
368
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
369
|
+
hash = "ff1f29f7b19a"
|
|
370
|
+
reason = "ambiguous_distinct_literals -- 3 *_disabled tests instantiate 3 distinct hook SUTs (WorktreeRemoveHook, AwaitMergeHook, CreatePRHook) across 2 pyramid levels (1 integration with tmp_git_repo + repo_path/worktree_path context; 2 unit with no fixtures + working_dir='.' literal); each test documents the per-hook enabled=False short-circuit contract with hook-specific context payloads (worktree_remove needs worktree_path, await_merge needs only working_dir, create_pr needs only working_dir) -- the per-hook disabled-contract cannot be parametrized across distinct hook constructors and distinct minimal context shapes"
|
|
371
|
+
|
|
372
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
373
|
+
hash = "bc21505a59bc"
|
|
374
|
+
reason = "ambiguous_distinct_literals -- 3 dict-worktree-path tests target 3 distinct hook SUTs (PullHook, PushHook, CreatePRHook) across 2 pyramid levels (2 integration with tmp_git_repo + run_git mocks; 1 unit with run_gh mock and /tmp/wt literal) -- each patches a different subprocess seam (axm_git.hooks.pull.run_git vs axm_git.hooks.push.run_git with rev-parse branch vs axm_git.hooks.create_pr.run_gh with gh_available + create_pr.commit_spec context) and asserts captured_cwd against a different path source (tmp_git_repo Path vs /tmp/wt literal) -- the dict-unwrap contract is identical but the mock topology and context shape diverge per hook, no parametrizable matrix"
|
|
375
|
+
|
|
376
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
377
|
+
hash = "393d37013529"
|
|
378
|
+
reason = "ambiguous_distinct_literals -- 2 missing-input failure tests target distinct hook SUTs and distinct error contracts: BranchDeleteHook::test_branch_delete_missing_branch runs on tmp_git_repo with no branch in context/params, asserts error contains 'no branch specified' (input-validation branch in branch-delete); AwaitMergeHook::test_await_merge_no_pr_ref runs with monkeypatched gh_available=True and no pr_number/pr_url, asserts error contains 'no pr_number or pr_url' (pre-poll guard in await-merge) -- different SUTs, different setup (real-repo vs gh-mock), different error substrings, no parametrizable matrix"
|
|
379
|
+
|
|
380
|
+
[[tool.axm-audit.duplicate_tests.acknowledged]]
|
|
381
|
+
hash = "e714397697ca"
|
|
382
|
+
reason = "signal1_call_assert -- 2 entry-point load tests target distinct hook SUTs and distinct entry-point names: test_commit_phase_hook_loads loads 'git:commit-phase' and asserts ep.load() is CommitPhaseHook; test_preflight_hook_loads loads 'git:preflight' and asserts ep.load() is PreflightHook -- the cookie-cutter shape (entry_points lookup + identity check) hides hard-coded per-hook string-and-class pairs that cannot be parametrized without losing the per-hook entry-point contract documentation"
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.4.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Core logic — subprocess runners and semver computation."""
|
|
2
|
+
|
|
3
|
+
from axm_git.core.identity import (
|
|
4
|
+
GitIdentity,
|
|
5
|
+
GitProfileConfig,
|
|
6
|
+
author_args,
|
|
7
|
+
load_config,
|
|
8
|
+
resolve_identity,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"GitIdentity",
|
|
13
|
+
"GitProfileConfig",
|
|
14
|
+
"author_args",
|
|
15
|
+
"load_config",
|
|
16
|
+
"resolve_identity",
|
|
17
|
+
]
|