git-paoding 0.1.0__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.
@@ -0,0 +1,32 @@
1
+ """Public package interface for git-paoding."""
2
+
3
+ from git_paoding.api import (
4
+ add_slice,
5
+ archive,
6
+ assign,
7
+ assign_batch,
8
+ get_full_status,
9
+ get_status,
10
+ init_session,
11
+ publish,
12
+ remove_slice,
13
+ rename_slice,
14
+ set_focus,
15
+ )
16
+
17
+ __version__ = "0.1.0"
18
+
19
+ __all__ = [
20
+ "__version__",
21
+ "add_slice",
22
+ "archive",
23
+ "assign",
24
+ "assign_batch",
25
+ "get_full_status",
26
+ "get_status",
27
+ "init_session",
28
+ "publish",
29
+ "remove_slice",
30
+ "rename_slice",
31
+ "set_focus",
32
+ ]
@@ -0,0 +1 @@
1
+ """Packaged agent integrations for git-paoding."""
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "git-paoding",
3
+ "version": "0.1.0",
4
+ "description": "Teach Claude Code to partition a canonical change into semantic Draft PR review projections.",
5
+ "author": {
6
+ "name": "NagisaVon"
7
+ },
8
+ "homepage": "https://github.com/NagisaVon/git-paoding",
9
+ "repository": "https://github.com/NagisaVon/git-paoding",
10
+ "license": "MIT",
11
+ "keywords": ["git", "github", "code-review", "pull-requests"]
12
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "git-paoding",
3
+ "version": "0.1.0",
4
+ "description": "Teach Codex to partition a canonical change into semantic Draft PR review projections.",
5
+ "author": {
6
+ "name": "NagisaVon"
7
+ },
8
+ "homepage": "https://github.com/NagisaVon/git-paoding",
9
+ "repository": "https://github.com/NagisaVon/git-paoding",
10
+ "license": "MIT",
11
+ "keywords": ["git", "github", "code-review", "pull-requests"],
12
+ "skills": "./skills/",
13
+ "interface": {
14
+ "displayName": "git-paoding",
15
+ "shortDescription": "Create semantic review projections from one canonical change.",
16
+ "longDescription": "Guide Codex through git-paoding's inspect, assign, publish, refresh, and archive workflow while keeping one authoritative integration branch.",
17
+ "developerName": "NagisaVon",
18
+ "category": "Productivity",
19
+ "capabilities": [],
20
+ "defaultPrompt": "Use git-paoding to prepare semantic Draft PR review projections for the committed change on my current branch. Inspect first and do not publish until I authorize remote changes."
21
+ }
22
+ }
@@ -0,0 +1,206 @@
1
+ ---
2
+ name: git-paoding
3
+ description:
4
+ Prepare and publish semantic review slices for a large committed change while
5
+ keeping one canonical integration branch. Use when an author wants focused
6
+ Draft GitHub PRs for comprehension without creating a development branch
7
+ stack.
8
+ ---
9
+
10
+ <!-- cspell:words paoding buildability PAODING -->
11
+
12
+ # git-paoding
13
+
14
+ Use `git-paoding` to partition the current `Base -> Final` diff into human-sized
15
+ semantic review slices. Keep one canonical integrated implementation state
16
+ throughout.
17
+
18
+ ## Preserve the mental model
19
+
20
+ - Edit, test, lint, and commit only on the canonical integration branch.
21
+ - Treat a slice as a review unit, never as a development phase, historical
22
+ commit range, buildable unit, or merge target.
23
+ - Never check out, edit, rebase, or restack generated `paoding/...` branches.
24
+ They are disposable projections derived from the canonical state and local
25
+ slice metadata.
26
+ - Let `publish` manage generated refs and stable PR identities. Do not manually
27
+ force-push projections or close and recreate a slice PR merely to refresh it.
28
+ - Never merge a slice PR. It is a Draft PR marked **DO NOT MERGE — review
29
+ projection only**.
30
+ - Keep authoritative CI, approval, branch protection, and merge on the
31
+ integration PR.
32
+ - Do not require slice-level CI or independent buildability. Cross-slice
33
+ dependencies are valid.
34
+ - New PR bodies contain no narrative template. Preserve human-written PR
35
+ narrative outside machine-managed regions byte-for-byte.
36
+
37
+ The operating rule is: **run `git-paoding publish` and do what it says**.
38
+ Publication is idempotent and self-checking; there is no separate refresh
39
+ ritual.
40
+
41
+ ## Plan for review without bookkeeping every edit
42
+
43
+ When review concerns are already apparent, use them as provisional,
44
+ low-frequency structure guidance:
45
+
46
+ - Prefer one primary review concern per new module or test file when that is a
47
+ natural boundary.
48
+ - Separate meaningfully different test concerns when ordinary readable
49
+ organization permits.
50
+ - Avoid one large contiguous insertion that mixes unrelated concerns when a
51
+ natural separation already exists.
52
+
53
+ This is optional guidance, not a correctness precondition. Do not create
54
+ artificial files, blank lines, awkward abstractions, or inferior architecture
55
+ merely to manufacture atom boundaries. Do not require active slices or
56
+ attribution calls during the normal edit-test-lint-fix loop. Provisional
57
+ concerns may change.
58
+
59
+ If nobody planned slice boundaries early, continue normally. Attribution is lazy
60
+ and recoverable: `status` will report unassigned or ambiguous atoms at the
61
+ review checkpoint, and those atoms can be classified then. Forgotten bookkeeping
62
+ never makes publication irrecoverable.
63
+
64
+ ## Prepare the session
65
+
66
+ Work from the branch containing the complete committed change. Confirm the CLI
67
+ and `gh` 2.45.0 or newer are available, `gh` is authenticated, and the canonical
68
+ branch is available on the Git remote. Do not push a branch without the change
69
+ owner's authorization.
70
+
71
+ ```bash
72
+ git-paoding --version
73
+ gh --version
74
+ gh auth status
75
+ git-paoding init --base origin/main --slice-prefix ABC-123
76
+ git-paoding slice add storage --title "Storage boundary"
77
+ git-paoding slice add tests --title "Storage behavior tests"
78
+ ```
79
+
80
+ Omit `--slice-prefix` to use `slice`. The prefix changes only slice PR titles;
81
+ it does not change stable slice IDs or generated refs. The integration PR title
82
+ is exactly the canonical branch name.
83
+
84
+ The base is pinned to a commit at initialization. Choose the real integration
85
+ target; do not silently change it later.
86
+
87
+ ## Classify and publish
88
+
89
+ ### 1. Inspect
90
+
91
+ ```bash
92
+ git-paoding status --json
93
+ ```
94
+
95
+ Interpret exit code `2` as action needed, not an operational failure. Read
96
+ the versioned JSON atom list. Use atom IDs for precise selection; paths,
97
+ directories, globs, and Final-coordinate ranges select broader atom sets.
98
+
99
+ Line ranges use Final coordinates: `path:20-45` means lines 20 through 45 in the
100
+ file at the canonical branch tip. A partial range selects the whole atom; ranges
101
+ do not split atoms.
102
+
103
+ Previews are short. Run `git-paoding status --full` for complete changed-hunk
104
+ previews. If surrounding context is still needed, read the current file at
105
+ `final_start` through `final_start + final_len - 1` before assigning; do not
106
+ guess from the preview. For a deletion or other atom with no Final lines,
107
+ inspect the relevant Base-side change instead.
108
+
109
+ ### 2. Assign
110
+
111
+ Prepare `paoding-assignments.json` using the frozen batch contract:
112
+
113
+ ```json
114
+ {
115
+ "contract_version": 0,
116
+ "assignments": {
117
+ "storage": ["src/storage.py"],
118
+ "tests": ["tests/test_storage.py"]
119
+ },
120
+ "force": false
121
+ }
122
+ ```
123
+
124
+ Then run:
125
+
126
+ ```bash
127
+ git-paoding assign --batch paoding-assignments.json
128
+ ```
129
+
130
+ The batch plan is ordinary local input, not git-paoding session metadata. Keep
131
+ it untracked, place it outside the working tree, or manage it according to the
132
+ repository's own policy; do not commit it accidentally.
133
+
134
+ For an interactive assignment, pass the slice followed by one or more
135
+ selectors:
136
+
137
+ ```bash
138
+ git-paoding assign storage src/storage.py
139
+ git-paoding assign tests tests/test_storage.py
140
+ ```
141
+
142
+ Review the assignment echo: it must list every moved or skipped atom with a
143
+ preview. Path and range selectors should claim only unassigned atoms by default.
144
+ Reassignment through a broad selector requires the explicit `--force` option;
145
+ an exact atom ID may take its atom directly. For batch repartitioning, set the
146
+ JSON `force` field instead of combining `--force` with `--batch`. If a selector
147
+ matches nothing or an atom ID became stale after a new commit, rerun
148
+ `status --json` rather than guessing.
149
+
150
+ ### 3. Publish
151
+
152
+ ```bash
153
+ git-paoding publish
154
+ ```
155
+
156
+ Respond to the result by exit code:
157
+
158
+ - `0`: clean success; report the integration PR and slice PRs.
159
+ - `2`: attribution needs attention; no remote effects occurred. Return to
160
+ inspect and assign.
161
+ - `1`: operational error, such as missing session, unavailable or
162
+ unauthenticated `gh`, or a push/PR failure. Fix the stated cause before
163
+ retrying.
164
+
165
+ Do not loop indefinitely. Retry after a concrete local correction; ask the
166
+ author when the fix needs new credentials, a branch push, a changed base, or
167
+ another external mutation.
168
+
169
+ ## Focus review feedback
170
+
171
+ For a task targeted at an existing slice, focus may act as a prior for genuinely
172
+ new atoms:
173
+
174
+ ```bash
175
+ git-paoding focus storage
176
+ git-paoding status --json
177
+ git-paoding focus --clear
178
+ ```
179
+
180
+ Focus must not overwrite confidently matched existing ownership. Clear it when
181
+ the targeted task ends so unrelated later work does not default to that slice.
182
+
183
+ ## Recovery
184
+
185
+ - New, unassigned, ambiguous, or updated atoms after canonical edits are
186
+ expected. Rerun `status`, inspect only what needs attention, assign it, and
187
+ publish again.
188
+ - Renames and heavy rewrites may return as unassigned or ambiguous. That is safe
189
+ degradation, not corruption.
190
+ - If local session metadata is lost, do not edit generated refs or PR bodies to
191
+ reconstruct it. Reinitialize with the original pinned base and recreate the
192
+ same stable slice IDs.
193
+ - Expect all atom attribution to return as unassigned after reinitialization. A
194
+ clean publish can adopt existing open slice PRs through their machine markers.
195
+ - If a slice becomes empty, publication may leave its existing Draft PR open and
196
+ mark it empty; do not invent changes merely to keep the projection nonempty.
197
+
198
+ After GitHub reports the integration PR as merged, run:
199
+
200
+ ```bash
201
+ git-paoding archive
202
+ ```
203
+
204
+ `archive` closes slice PRs, retains their URLs and discussion history, and
205
+ cleans generated refs; it never merges them. It refuses to run while the
206
+ integration PR is still open or merely closed.
@@ -0,0 +1,113 @@
1
+ """Install the packaged git-paoding skill for supported coding agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from importlib.resources import files
7
+ from pathlib import Path, PurePosixPath
8
+ from typing import Any, Literal
9
+
10
+ AgentTarget = Literal["codex", "claude"]
11
+ InstallScope = Literal["user", "project"]
12
+
13
+
14
+ class AgentInstallError(ValueError):
15
+ """Raised when an agent skill installation is invalid or unsafe."""
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class AgentInstallResult:
20
+ """Result of installing one packaged agent skill."""
21
+
22
+ target: AgentTarget
23
+ scope: InstallScope
24
+ destination: Path
25
+ changed: bool
26
+
27
+
28
+ def _packaged_skill() -> Any:
29
+ root = files("git_paoding._agent_plugins")
30
+ skill = root.joinpath("git-paoding").joinpath("skills").joinpath("git-paoding")
31
+ if not skill.is_dir():
32
+ raise AgentInstallError("the installed package does not contain the git-paoding skill")
33
+ return skill
34
+
35
+
36
+ def _resource_snapshot(resource: Any, prefix: PurePosixPath = PurePosixPath()) -> dict[str, bytes]:
37
+ snapshot: dict[str, bytes] = {}
38
+ for child in resource.iterdir():
39
+ relative = prefix / child.name
40
+ if child.is_dir():
41
+ snapshot.update(_resource_snapshot(child, relative))
42
+ elif child.is_file():
43
+ snapshot[relative.as_posix()] = child.read_bytes()
44
+ return snapshot
45
+
46
+
47
+ def _directory_snapshot(directory: Path) -> dict[str, bytes]:
48
+ return {
49
+ path.relative_to(directory).as_posix(): path.read_bytes()
50
+ for path in directory.rglob("*")
51
+ if path.is_file()
52
+ }
53
+
54
+
55
+ def _destination(
56
+ target: AgentTarget,
57
+ scope: InstallScope,
58
+ *,
59
+ project_root: Path,
60
+ home: Path,
61
+ ) -> Path:
62
+ if target == "codex":
63
+ base = (
64
+ home / ".agents" / "skills" if scope == "user" else project_root / ".agents" / "skills"
65
+ )
66
+ elif target == "claude":
67
+ base = (
68
+ home / ".claude" / "skills" if scope == "user" else project_root / ".claude" / "skills"
69
+ )
70
+ else: # pragma: no cover - protected by public type and Click validation
71
+ raise AgentInstallError(f"unsupported agent target: {target}")
72
+ return base / "git-paoding"
73
+
74
+
75
+ def install_agent_skill(
76
+ target: AgentTarget,
77
+ scope: InstallScope,
78
+ *,
79
+ project_root: Path | None = None,
80
+ home: Path | None = None,
81
+ force: bool = False,
82
+ ) -> AgentInstallResult:
83
+ """Install the packaged skill into one agent's user or project skill root."""
84
+
85
+ resolved_project = (project_root or Path.cwd()).resolve()
86
+ resolved_home = (home or Path.home()).resolve()
87
+ destination = _destination(
88
+ target,
89
+ scope,
90
+ project_root=resolved_project,
91
+ home=resolved_home,
92
+ )
93
+ source_snapshot = _resource_snapshot(_packaged_skill())
94
+ if not source_snapshot or "SKILL.md" not in source_snapshot:
95
+ raise AgentInstallError("the packaged git-paoding skill is incomplete")
96
+
97
+ if destination.exists():
98
+ if not destination.is_dir():
99
+ raise AgentInstallError(f"skill destination is not a directory: {destination}")
100
+ if _directory_snapshot(destination) == source_snapshot:
101
+ return AgentInstallResult(target, scope, destination, changed=False)
102
+ if not force:
103
+ raise AgentInstallError(
104
+ f"skill destination already exists with different contents: {destination}; "
105
+ "pass --force to overwrite packaged files"
106
+ )
107
+
108
+ for relative, content in source_snapshot.items():
109
+ output = destination / relative
110
+ output.parent.mkdir(parents=True, exist_ok=True)
111
+ output.write_bytes(content)
112
+
113
+ return AgentInstallResult(target, scope, destination, changed=True)