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,165 @@
1
+ """Fail-fast advisory locking for mutating session operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import time
8
+ from dataclasses import dataclass
9
+ from datetime import timedelta
10
+ from pathlib import Path
11
+ from types import TracebackType
12
+ from typing import Any
13
+
14
+ from git_paoding.core.model import (
15
+ ConcurrentSessionAccessError,
16
+ SessionLockError,
17
+ StaleSessionLockError,
18
+ )
19
+ from git_paoding.store.jsonstore import branch_key, paoding_dir
20
+
21
+ DEFAULT_STALE_AFTER = timedelta(hours=1)
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class LockOwner:
26
+ """Process identity persisted in a lock file."""
27
+
28
+ pid: int
29
+ created_at: float
30
+
31
+
32
+ def _pid_is_alive(pid: int) -> bool:
33
+ if pid <= 0:
34
+ return False
35
+ try:
36
+ os.kill(pid, 0)
37
+ except ProcessLookupError:
38
+ return False
39
+ except PermissionError:
40
+ return True
41
+ return True
42
+
43
+
44
+ class SessionLock:
45
+ """Exclusive advisory lock scoped to one canonical branch."""
46
+
47
+ def __init__(
48
+ self,
49
+ repo: Path,
50
+ canonical_branch: str,
51
+ *,
52
+ stale_after: timedelta = DEFAULT_STALE_AFTER,
53
+ override_stale: bool = False,
54
+ ) -> None:
55
+ if stale_after.total_seconds() < 0:
56
+ raise ValueError("stale_after must not be negative")
57
+ self.repo = repo.resolve()
58
+ self.canonical_branch = canonical_branch
59
+ self.stale_after = stale_after
60
+ self.override_stale = override_stale
61
+ self.path = paoding_dir(self.repo) / "locks" / f"{branch_key(canonical_branch)}.lock"
62
+ self._acquired = False
63
+
64
+ def acquire(self) -> SessionLock:
65
+ """Acquire immediately or fail without waiting."""
66
+
67
+ if self._acquired:
68
+ raise SessionLockError(f"Session lock is already held by this object: {self.path}")
69
+ self.path.parent.mkdir(parents=True, exist_ok=True)
70
+ while True:
71
+ try:
72
+ descriptor = os.open(self.path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
73
+ except FileExistsError:
74
+ owner = self._read_owner()
75
+ age = max(0.0, time.time() - owner.created_at)
76
+ alive = _pid_is_alive(owner.pid)
77
+ stale = not alive and age >= self.stale_after.total_seconds()
78
+ if not stale:
79
+ raise ConcurrentSessionAccessError(
80
+ f"Another mutating git-paoding process holds {self.path} "
81
+ f"(pid={owner.pid}, age={age:.1f}s); concurrent writes fail fast."
82
+ ) from None
83
+ if not self.override_stale:
84
+ raise StaleSessionLockError(
85
+ f"Stale git-paoding session lock detected at {self.path} "
86
+ f"(pid={owner.pid} is not running, age={age:.1f}s). "
87
+ "After verifying no mutating command is active, retry with "
88
+ "override_stale=True to remove the stale lock."
89
+ ) from None
90
+ self._remove_stale(owner)
91
+ continue
92
+
93
+ owner = LockOwner(pid=os.getpid(), created_at=time.time())
94
+ payload = json.dumps({"pid": owner.pid, "created_at": owner.created_at}) + "\n"
95
+ try:
96
+ with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
97
+ stream.write(payload)
98
+ stream.flush()
99
+ os.fsync(stream.fileno())
100
+ except BaseException:
101
+ self.path.unlink(missing_ok=True)
102
+ raise
103
+ self._acquired = True
104
+ return self
105
+
106
+ def release(self) -> None:
107
+ """Release a lock owned by this object."""
108
+
109
+ if not self._acquired:
110
+ return
111
+ try:
112
+ current = self._read_owner()
113
+ if current.pid != os.getpid():
114
+ raise SessionLockError(
115
+ f"Refusing to release session lock {self.path}: ownership changed "
116
+ f"from pid {os.getpid()} to pid {current.pid}"
117
+ )
118
+ self.path.unlink()
119
+ except FileNotFoundError as error:
120
+ raise SessionLockError(
121
+ f"Session lock disappeared before release: {self.path}"
122
+ ) from error
123
+ finally:
124
+ self._acquired = False
125
+
126
+ def _read_owner(self) -> LockOwner:
127
+ try:
128
+ payload: Any = json.loads(self.path.read_text(encoding="utf-8"))
129
+ if not isinstance(payload, dict):
130
+ raise TypeError("lock payload is not an object")
131
+ pid = payload["pid"]
132
+ created_at = payload["created_at"]
133
+ if isinstance(pid, bool) or not isinstance(pid, int):
134
+ raise TypeError("pid is not an integer")
135
+ if isinstance(created_at, bool) or not isinstance(created_at, (int, float)):
136
+ raise TypeError("created_at is not a number")
137
+ return LockOwner(pid=pid, created_at=float(created_at))
138
+ except (OSError, ValueError, TypeError, KeyError) as error:
139
+ raise StaleSessionLockError(
140
+ f"Cannot validate existing session lock {self.path}: {error}. "
141
+ "After verifying no mutating command is active, remove the lock manually "
142
+ "or retry with override_stale=True."
143
+ ) from error
144
+
145
+ def _remove_stale(self, expected: LockOwner) -> None:
146
+ current = self._read_owner()
147
+ if current != expected:
148
+ raise ConcurrentSessionAccessError(
149
+ f"Session lock {self.path} changed while checking staleness; retry the operation."
150
+ )
151
+ try:
152
+ self.path.unlink()
153
+ except FileNotFoundError:
154
+ return
155
+
156
+ def __enter__(self) -> SessionLock:
157
+ return self.acquire()
158
+
159
+ def __exit__(
160
+ self,
161
+ exc_type: type[BaseException] | None,
162
+ exc_value: BaseException | None,
163
+ traceback: TracebackType | None,
164
+ ) -> None:
165
+ self.release()
@@ -0,0 +1,351 @@
1
+ Metadata-Version: 2.3
2
+ Name: git-paoding
3
+ Version: 0.1.0
4
+ Summary: Semantic review slicing for large agent-generated changes
5
+ Requires-Dist: click>=8.1
6
+ Requires-Dist: pydantic>=2
7
+ Requires-Dist: hypothesis>=6 ; extra == 'dev'
8
+ Requires-Dist: mypy>=1.15 ; extra == 'dev'
9
+ Requires-Dist: pre-commit>=4 ; extra == 'dev'
10
+ Requires-Dist: pytest>=8 ; extra == 'dev'
11
+ Requires-Dist: pytest-cov>=6 ; extra == 'dev'
12
+ Requires-Dist: ruff>=0.11 ; extra == 'dev'
13
+ Requires-Python: >=3.11
14
+ Provides-Extra: dev
15
+ Description-Content-Type: text/markdown
16
+
17
+ # git-paoding
18
+
19
+ <!-- cspell:words paoding pipx PAODING -->
20
+
21
+ Agent writes globally. Humans review locally.
22
+
23
+ `git-paoding` lets a coding agent keep one coherent implementation on one
24
+ canonical integration branch while presenting the final change as several small,
25
+ semantic Draft GitHub pull requests. Those slice PRs are review projections:
26
+ they help people understand one concern at a time, but they are not development
27
+ branches or merge targets.
28
+
29
+ The canonical integration PR remains authoritative. It contains the complete
30
+ change, runs CI, receives final approval, and merges. Review feedback goes back
31
+ into the canonical branch; a later `git-paoding publish` refreshes the
32
+ projections without creating a branch stack to restack.
33
+
34
+ The name comes from 庖丁解牛 (_Chef Ding carves the ox_): cutting along the
35
+ natural joints.
36
+
37
+
38
+ ## Install the CLI and agent workflow
39
+
40
+ `git-paoding` is designed for coding agents rather than as a human-operated UI.
41
+ A complete installation has two parts:
42
+
43
+ 1. The Python package provides the deterministic `git-paoding` executable.
44
+ 2. The agent skill or plugin teaches Codex or Claude Code when and how to use it.
45
+
46
+ Installing only the Python package does not make an agent discover the workflow.
47
+ Install the CLI and one agent integration together.
48
+
49
+ ### Give this page to an agent
50
+
51
+ You can send an agent this repository URL and the following request:
52
+
53
+ ```text
54
+ Install git-paoding by following the repository README. Install the Python CLI
55
+ and the integration for the agent you are running as, verify both, and then
56
+ explain the workflow. Do not initialize a session, push, publish, or change any
57
+ repository until I give you a specific review-slicing task.
58
+ ```
59
+
60
+ ### 1. Install the CLI
61
+
62
+ `git-paoding` requires Python 3.11 or newer, Git, and
63
+ [GitHub CLI](https://cli.github.com/) 2.45.0 or newer. GitHub operations reuse
64
+ the account and credentials configured by `gh`; authenticate before initializing
65
+ a session:
66
+
67
+ ```bash
68
+ gh auth login
69
+ gh auth status
70
+ ```
71
+
72
+ Install the released package with one of these methods. `uv tool` or `pipx` is
73
+ recommended because it keeps the agent-facing command isolated:
74
+
75
+ ```bash
76
+ uv tool install git-paoding
77
+ pipx install git-paoding
78
+ python -m pip install git-paoding
79
+ ```
80
+
81
+ Choose one installation method, not all three. Confirm the explicit command and
82
+ Git's external-subcommand form resolve to the same release:
83
+
84
+ ```bash
85
+ git-paoding --version
86
+ git paoding --version
87
+ ```
88
+
89
+ If the first PyPI release is not available yet, install the current GitHub
90
+ version directly:
91
+
92
+ ```bash
93
+ uv tool install "git+https://github.com/NagisaVon/git-paoding.git"
94
+ ```
95
+
96
+ To install from a source checkout instead:
97
+
98
+ ```bash
99
+ git clone https://github.com/NagisaVon/git-paoding.git
100
+ cd git-paoding
101
+ uv sync --extra dev --locked
102
+ uv run git-paoding --help
103
+ ```
104
+
105
+ ### 2. Install the agent integration
106
+
107
+ Choose one of the following installation methods for each agent. The bundled
108
+ standalone skill and the marketplace plugin provide the same instructions, so
109
+ installing both for the same agent is unnecessary.
110
+
111
+ #### Option A: install the bundled standalone skill
112
+
113
+ The Python distribution carries the same `SKILL.md` used by both plugins. The
114
+ following commands copy that bundled skill into the official personal skill
115
+ directory and work without a plugin UI:
116
+
117
+ For Codex:
118
+
119
+ ```bash
120
+ git-paoding agent install --target codex --scope user
121
+ ```
122
+
123
+ For Claude Code:
124
+
125
+ ```bash
126
+ git-paoding agent install --target claude --scope user
127
+ ```
128
+
129
+ To install both, repeat `--target` in one command:
130
+
131
+ ```bash
132
+ git-paoding agent install --target codex --target claude --scope user
133
+ ```
134
+
135
+ Use `--scope project` to install into the current repository instead of the
136
+ current user's global skill directory. Re-run with `--force` after upgrading if
137
+ the installed skill was modified locally. Codex installs to
138
+ `.agents/skills/git-paoding`; Claude Code installs to
139
+ `.claude/skills/git-paoding` (under the home directory for user scope).
140
+
141
+ Verify the skill appears in Codex with `/skills`. In Claude Code, run `/skills`
142
+ or invoke `/git-paoding` directly. Restart the agent only if a newly created
143
+ top-level skill directory is not detected in the current session.
144
+
145
+ #### Option B: install through the plugin marketplace
146
+
147
+ This repository is also a marketplace for a skill-only `git-paoding` plugin.
148
+ The Codex and Claude Code manifests share one packaged skill, so their behavior
149
+ does not drift.
150
+
151
+ For Codex, add the GitHub marketplace:
152
+
153
+ ```bash
154
+ codex plugin marketplace add NagisaVon/git-paoding
155
+ codex plugin add git-paoding@git-paoding
156
+ ```
157
+
158
+ The same plugin then appears in the Plugins Directory in the ChatGPT desktop
159
+ app. Invoke its skill as `$git-paoding`.
160
+
161
+ For Claude Code, the complete installation is available from the CLI:
162
+
163
+ ```bash
164
+ claude plugin marketplace add NagisaVon/git-paoding
165
+ claude plugin install git-paoding@git-paoding --scope user
166
+ ```
167
+
168
+ Run `/reload-plugins` if Claude Code asks for it, then invoke the plugin skill as
169
+ `/git-paoding:git-paoding`.
170
+
171
+ ### 3. Ask the agent to prepare review slices
172
+
173
+ For Codex:
174
+
175
+ ```text
176
+ $git-paoding Prepare semantic review slices for the complete committed change
177
+ on my current branch, using origin/main as the base. Inspect and propose the
178
+ slice assignments first. Do not push or publish until I approve the plan.
179
+ ```
180
+
181
+ For the Claude Code plugin:
182
+
183
+ ```text
184
+ /git-paoding:git-paoding Prepare semantic review slices for the complete
185
+ committed change on my current branch, using origin/main as the base. Inspect
186
+ and propose the slice assignments first. Do not push or publish until I approve
187
+ the plan.
188
+ ```
189
+
190
+ ## Run a review-slicing session
191
+
192
+ After installation, Codex and Claude Code follow the same operational workflow
193
+ below. Their invocation syntax differs (`$git-paoding` in Codex and
194
+ `/git-paoding:git-paoding` for the Claude Code plugin), but both use the same
195
+ `git-paoding` CLI and repository state.
196
+
197
+ Start from the branch that contains the complete, committed implementation. The
198
+ base is pinned when the session is initialized; moving `origin/main` later does
199
+ not silently move that pin.
200
+
201
+ ```bash
202
+ git-paoding init --base origin/main --slice-prefix ABC-123
203
+ git-paoding slice add storage --title "Storage boundary"
204
+ git-paoding slice add tests --title "Storage behavior tests"
205
+ git-paoding status --json
206
+ ```
207
+
208
+ `status` is local and read-only. Exit code `2` is expected while it reports
209
+ unassigned or ambiguous atoms. Its JSON includes each atom's ID, path, Base and
210
+ Final ranges, owner, state, and short preview. Use `git-paoding status --full`
211
+ when complete changed-hunk previews are useful.
212
+
213
+ `--slice-prefix` is optional and defaults to `slice`. It changes only generated
214
+ slice PR titles, such as `[ABC-123] Storage boundary`; slice IDs and generated
215
+ refs remain stable. The integration PR title is the canonical branch name.
216
+
217
+ Assign interactively by an atom ID, path, directory, glob, or Final-coordinate
218
+ line range. Broad selectors preserve already-owned atoms unless `--force` is
219
+ passed; explicit atom IDs may reassign their exact atom without it. Every
220
+ selected atom is echoed as assigned or skipped:
221
+
222
+ ```bash
223
+ git-paoding assign storage src/storage.py
224
+ git-paoding assign tests tests/test_storage.py
225
+ git-paoding status --json
226
+ ```
227
+
228
+ When no action is needed, publish the review projections:
229
+
230
+ ```bash
231
+ git-paoding publish
232
+ ```
233
+
234
+ `publish` is idempotent. It reconciles first and stops with exit code `2` and no
235
+ remote effects if attribution still needs attention. A clean publish pushes
236
+ generated projection refs, creates or refreshes stable Draft slice PRs, and
237
+ creates or updates the Draft integration PR and its slice index. Operational
238
+ failures use exit code `1`; success uses `0`.
239
+
240
+ Keep the canonical branch available on the selected Git remote before
241
+ publishing. If it has not been pushed, obtain the change owner's approval before
242
+ doing so:
243
+
244
+ ```bash
245
+ git push -u origin HEAD
246
+ ```
247
+
248
+ ### Three-step agent flow
249
+
250
+ The intended agent loop is:
251
+
252
+ ```bash
253
+ git-paoding status --json
254
+ git-paoding assign --batch paoding-assignments.json
255
+ git-paoding publish
256
+ ```
257
+
258
+ The batch request uses the frozen versioned contract:
259
+
260
+ ```json
261
+ {
262
+ "contract_version": 0,
263
+ "assignments": {
264
+ "storage": ["src/storage.py"],
265
+ "tests": ["tests/test_storage.py"]
266
+ },
267
+ "force": false
268
+ }
269
+ ```
270
+
271
+ Batch assignment is all-or-nothing: an unknown slice, invalid selector, or
272
+ cross-slice conflict rejects the entire request. Set the JSON `force` field to
273
+ `true` when a batch is intentionally repartitioning already-owned atoms; do not
274
+ combine the interactive `--force` option with `--batch`. The batch plan is an
275
+ ordinary local input file rather than session metadata; keep it untracked or
276
+ manage it according to the repository's own policy.
277
+
278
+ For targeted review feedback, focus may provide a default owner for genuinely
279
+ new atoms without overwriting confidently matched ownership:
280
+
281
+ ```bash
282
+ git-paoding focus storage
283
+ git-paoding status --json
284
+ git-paoding focus --clear
285
+ ```
286
+
287
+ ## What reviewers should know
288
+
289
+ A slice PR is a view onto one semantic part of the final integrated change.
290
+ Different slices may touch different regions of the same file, and one slice may
291
+ rely on code shown by another. A slice is not required to build or test by
292
+ itself.
293
+
294
+ Every slice PR is Draft and carries a
295
+ **DO NOT MERGE — review projection only** warning because:
296
+
297
+ - Its generated base and head refs are disposable projections.
298
+ - It is not the branch where implementation work happens.
299
+ - Its review is for comprehension, not authoritative approval.
300
+ - Only the integration PR represents the complete change and real merge target.
301
+
302
+ Use normal GitHub review features on a slice PR: read its narrative, inspect
303
+ Files changed, and leave inline comments. After feedback, update the canonical
304
+ branch and refresh the same slice PR. GitHub may mark comments
305
+ outdated when their lines change; the discussion history and stable PR identity
306
+ remain useful.
307
+
308
+ ## Suppress CI for slice PRs
309
+
310
+ Slice projections are review units, not integration units. In consumer
311
+ repositories, filter the `pull_request` workflow to real target branches so
312
+ generated `paoding/.../base` refs do not start authoritative CI. For a
313
+ repository that merges into `main`:
314
+
315
+ ```yaml
316
+ name: CI
317
+
318
+ on:
319
+ pull_request:
320
+ branches: [main]
321
+ push:
322
+ branches: [main]
323
+ ```
324
+
325
+ GitHub evaluates the `pull_request.branches` filter against the PR's base
326
+ branch. Keep the normal CI and branch-protection requirements on the integration
327
+ PR.
328
+
329
+ ## Safety and recovery
330
+
331
+ - Work only on the canonical integration branch. Never check out or edit
332
+ generated `paoding/...` branches.
333
+ - Never merge a slice PR. Close/archive it after the integration PR merges.
334
+ - Do not expect a slice projection to build, test, or pass CI independently.
335
+ - Unassigned or ambiguous atoms are normal recovery states, not metadata
336
+ corruption. Rerun `status`, classify what remains, and publish again.
337
+ - Slice metadata lives in the repository's common Git directory and is not
338
+ committed. Do not delete it as a routine reset.
339
+ - If session metadata is lost, recreate the session and the same stable slice
340
+ IDs. Attribution returns as unassigned, while existing open slice PRs can be
341
+ adopted by their machine markers on the next clean publish.
342
+ - New PR bodies contain only their machine-managed region; the tool does not
343
+ seed a narrative template. Human narrative added outside those delimiters is
344
+ preserved byte-for-byte on refresh.
345
+
346
+ After GitHub reports the integration PR as merged, archive the generated review
347
+ surface without merging any slice PR:
348
+
349
+ ```bash
350
+ git-paoding archive
351
+ ```
@@ -0,0 +1,36 @@
1
+ git_paoding/__init__.py,sha256=E7c45xo-EyUXLNUguozhSN0jiaCfCwmBAHYcwa4YyP8,514
2
+ git_paoding/_agent_plugins/__init__.py,sha256=aY6bQaHPc46VFLH6zM8dPxhVwBlc4xntdWnoPdqmGAQ,51
3
+ git_paoding/_agent_plugins/git-paoding/.claude-plugin/plugin.json,sha256=wXOOX81TO2LwVp9U49bKhENSETEOzjigMY7V_cfXHa8,409
4
+ git_paoding/_agent_plugins/git-paoding/.codex-plugin/plugin.json,sha256=8qZcbSXqZomCpl6tiTmmN350WVv0jxomwYDLmgMnXHg,1032
5
+ git_paoding/_agent_plugins/git-paoding/skills/git-paoding/SKILL.md,sha256=HXeCHfmUo3J7YmYDrZvJI6aQrdNc6fsKGxrEqGR_Lx0,7639
6
+ git_paoding/agent_install.py,sha256=ZO6XHkzeQOzlSWMXj5qa5qUqA4lXxVU1TfdE3FsA4fs,3734
7
+ git_paoding/api.py,sha256=ocjyM0IgJQXyWnfTqzK8YangzOyufVYJN5K_JhTKiV0,12614
8
+ git_paoding/cli/__init__.py,sha256=0ouz55BJFQtDyuOsMAIaI-RI0vCWqZfos6OF1lDc_js,38
9
+ git_paoding/cli/facade.py,sha256=AochEYhKAav3iiBil4fyKg0weclaxQSJf8voQkh3Azk,3578
10
+ git_paoding/cli/main.py,sha256=wCQwyBMUH2EjaYuFkCm-gEO1wFSB8_YhWjkYxwNYbyA,9388
11
+ git_paoding/cli/render.py,sha256=pnNzt1WItuX5CnCTxD2ssEwGBfgD4zufmwt1umxuw-4,6967
12
+ git_paoding/core/__init__.py,sha256=vB0bvZGYzUdHNhwbziqopojz1tbNWB4h5BMNFEeYIfA,27
13
+ git_paoding/core/diffatoms.py,sha256=MzlvrVN5dBvGHqdRescik4ToxS7WRTbYGTEO5Y4FvsU,7193
14
+ git_paoding/core/model.py,sha256=0Vp2trstldmGcvoGqqMNuUyEMIlG5XouPsMM5rLuP70,8688
15
+ git_paoding/core/projection.py,sha256=Ep3pv7tQYfuqGWNlEk1qhO7-iPPxDbyPmx-qjrs1ftI,12841
16
+ git_paoding/core/publish.py,sha256=-47PWMvWF0Y5_3pPBIaEecS0jTh3JcW76TOhoIHv7tY,24277
17
+ git_paoding/core/reconcile.py,sha256=SisqK5eSoe39pJpGRlpnWDz_34gaNsifzecBCyMYyx0,8397
18
+ git_paoding/core/selectors.py,sha256=DELr18hNBzhqVMli9V1aGFkBQCyiutLwiX8ybCNRkXU,10239
19
+ git_paoding/github/__init__.py,sha256=t0Y980WY5OXoAJoB12cdR9pSf-wxMhY1fS8TwXrIaYw,34
20
+ git_paoding/github/backend.py,sha256=snDHc5pQ2l29A2d2QbmKPvUu-Ugpms0IeVUgSbofIDo,1700
21
+ git_paoding/github/gh_cli.py,sha256=tBVmQp_SSBoKVyvM6eI7x7JxaifOII9wdVW8BM5Gt4c,11425
22
+ git_paoding/github/lifecycle.py,sha256=HAnx4bg1QB6Uv5LvFrmyjPhMX3pKrttvLfDtWM4zoSU,3414
23
+ git_paoding/github/prbody.py,sha256=nFSRzNmU1ZsVAt6BDf9_PhiAM1DMa6LkvlW99H5edGE,8765
24
+ git_paoding/gitio/__init__.py,sha256=joLcPXhFt8OIX6hPB5k3hHPGN4KxLEHl3ZuXhjWTbXc,897
25
+ git_paoding/gitio/diffparse.py,sha256=avAcZZKgooK9uHdWdGpxKKapPJoy2QIGHJ1vGxTbBcI,9078
26
+ git_paoding/gitio/plumbing.py,sha256=SOADfV5pq-1hdum9OUxaeiRo794peZIS2k9FjzZgTMk,5520
27
+ git_paoding/gitio/refs.py,sha256=GFApgr44r07hD7zph0N04ryVs8uR1YbVNp4yxcUBFew,4428
28
+ git_paoding/gitio/runner.py,sha256=7zp2JXNNvT1vjXrddke6DJu7tJ3s5VuEfpr3IuKS5EY,3785
29
+ git_paoding/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
30
+ git_paoding/store/__init__.py,sha256=ZqA3MFvJiR5MAwQqhQ8NRBiITIs_26wCoqb0z4tdOjc,249
31
+ git_paoding/store/jsonstore.py,sha256=xS_DQTM_BlJ4mRt9QwnStMCQp9UX4owdTtQV5dsadZ0,5277
32
+ git_paoding/store/lock.py,sha256=CuxnfZegsFr-zX5r8I_g2h53Pd4-x1KziLYEFLOnSj8,5981
33
+ git_paoding-0.1.0.dist-info/WHEEL,sha256=ZFFp7t7R4RYQ5KYZkmiFWoQvHay7SrTmn-6ZYfoFZ3U,80
34
+ git_paoding-0.1.0.dist-info/entry_points.txt,sha256=BY2d7F5n-uEQseTerylxaZMcUb9TeUObHB5rrJ_kvE0,59
35
+ git_paoding-0.1.0.dist-info/METADATA,sha256=UyswhHRBlMjCWufSE9vFdVUCHwwIujy8iBpcXKEJre8,11990
36
+ git_paoding-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.7
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ git-paoding = git_paoding.cli.main:main
3
+