awgit 0.3.0__tar.gz → 1.0.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.
Files changed (69) hide show
  1. awgit-1.0.0/PKG-INFO +262 -0
  2. awgit-1.0.0/README.md +249 -0
  3. awgit-1.0.0/awgit/__init__.py +107 -0
  4. awgit-1.0.0/awgit/absorb.py +487 -0
  5. {awgit-0.3.0 → awgit-1.0.0}/awgit/bridge.py +9 -5
  6. {awgit-0.3.0 → awgit-1.0.0}/awgit/capture.py +130 -11
  7. awgit-1.0.0/awgit/changeid.py +160 -0
  8. awgit-1.0.0/awgit/cli.py +1880 -0
  9. awgit-1.0.0/awgit/code.py +57 -0
  10. awgit-1.0.0/awgit/commands.py +136 -0
  11. awgit-1.0.0/awgit/evidence.py +137 -0
  12. awgit-1.0.0/awgit/git.py +78 -0
  13. awgit-1.0.0/awgit/guard.py +166 -0
  14. awgit-1.0.0/awgit/hooks/pre-commit.d/vcs-mass-delete-guard +76 -0
  15. awgit-1.0.0/awgit/hooks/pre-push.d/ci-gate-parity +70 -0
  16. awgit-1.0.0/awgit/hooks/prepare-commit-msg.d/awgit-change-id +38 -0
  17. awgit-1.0.0/awgit/lazy.py +164 -0
  18. {awgit-0.3.0 → awgit-1.0.0}/awgit/leases.py +48 -1
  19. {awgit-0.3.0 → awgit-1.0.0}/awgit/mcp.py +43 -10
  20. {awgit-0.3.0 → awgit-1.0.0}/awgit/merge.py +11 -7
  21. {awgit-0.3.0 → awgit-1.0.0}/awgit/oplog.py +2 -2
  22. awgit-1.0.0/awgit/owners.py +172 -0
  23. {awgit-0.3.0 → awgit-1.0.0}/awgit/parser.py +6 -1
  24. awgit-1.0.0/awgit/plugins.py +177 -0
  25. awgit-1.0.0/awgit/prove.py +185 -0
  26. awgit-1.0.0/awgit/push.py +265 -0
  27. {awgit-0.3.0 → awgit-1.0.0}/awgit/repowise_parser.py +15 -1
  28. awgit-1.0.0/awgit/review.py +252 -0
  29. {awgit-0.3.0 → awgit-1.0.0}/awgit/schema.py +7 -0
  30. awgit-1.0.0/awgit/stack.py +198 -0
  31. awgit-1.0.0/awgit/staging.py +262 -0
  32. awgit-1.0.0/awgit/staging_selftest.py +177 -0
  33. awgit-1.0.0/awgit/worktree.py +96 -0
  34. awgit-1.0.0/awgit.egg-info/PKG-INFO +262 -0
  35. awgit-1.0.0/awgit.egg-info/SOURCES.txt +61 -0
  36. {awgit-0.3.0 → awgit-1.0.0}/pyproject.toml +1 -1
  37. awgit-1.0.0/tests/test_absorb_routes_by_node.py +178 -0
  38. {awgit-0.3.0 → awgit-1.0.0}/tests/test_awgit_standalone.py +72 -0
  39. awgit-1.0.0/tests/test_changeid_survives_history_rewrite.py +160 -0
  40. awgit-1.0.0/tests/test_documented_commands_run.py +160 -0
  41. awgit-1.0.0/tests/test_lazy_clone_is_verified.py +105 -0
  42. awgit-1.0.0/tests/test_multilang_identity.py +80 -0
  43. awgit-1.0.0/tests/test_owners_and_proof.py +115 -0
  44. awgit-1.0.0/tests/test_push_is_the_pull_request.py +304 -0
  45. awgit-1.0.0/tests/test_restack_repairs_orphans.py +114 -0
  46. awgit-1.0.0/tests/test_review_threads_survive_moves.py +223 -0
  47. awgit-0.3.0/PKG-INFO +0 -119
  48. awgit-0.3.0/README.md +0 -106
  49. awgit-0.3.0/awgit/__init__.py +0 -53
  50. awgit-0.3.0/awgit/cli.py +0 -566
  51. awgit-0.3.0/awgit.egg-info/PKG-INFO +0 -119
  52. awgit-0.3.0/awgit.egg-info/SOURCES.txt +0 -32
  53. {awgit-0.3.0 → awgit-1.0.0}/LICENSE +0 -0
  54. {awgit-0.3.0 → awgit-1.0.0}/awgit/bodies.py +0 -0
  55. {awgit-0.3.0 → awgit-1.0.0}/awgit/data_root.py +0 -0
  56. {awgit-0.3.0 → awgit-1.0.0}/awgit/diff.py +0 -0
  57. {awgit-0.3.0 → awgit-1.0.0}/awgit/graph.py +0 -0
  58. {awgit-0.3.0 → awgit-1.0.0}/awgit/hooks/chain.sh +0 -0
  59. {awgit-0.3.0 → awgit-1.0.0}/awgit/hooks/post-commit.d/vcs-capture +0 -0
  60. {awgit-0.3.0 → awgit-1.0.0}/awgit/hooks/pre-commit.d/vcs-lease-check +0 -0
  61. {awgit-0.3.0 → awgit-1.0.0}/awgit/identity.py +0 -0
  62. {awgit-0.3.0 → awgit-1.0.0}/awgit/ledger.py +0 -0
  63. {awgit-0.3.0 → awgit-1.0.0}/awgit/nodeid.py +0 -0
  64. {awgit-0.3.0 → awgit-1.0.0}/awgit/sync.py +0 -0
  65. {awgit-0.3.0 → awgit-1.0.0}/awgit.egg-info/dependency_links.txt +0 -0
  66. {awgit-0.3.0 → awgit-1.0.0}/awgit.egg-info/entry_points.txt +0 -0
  67. {awgit-0.3.0 → awgit-1.0.0}/awgit.egg-info/requires.txt +0 -0
  68. {awgit-0.3.0 → awgit-1.0.0}/awgit.egg-info/top_level.txt +0 -0
  69. {awgit-0.3.0 → awgit-1.0.0}/setup.cfg +0 -0
awgit-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: awgit
3
+ Version: 1.0.0
4
+ Summary: Aither World-Graph git — semantic version control on top of git: edit-ops keyed on stable node ids, content-addressed bodies, differential sync.
5
+ License: Apache-2.0
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: httpx>=0.25.0
10
+ Provides-Extra: multilang
11
+ Requires-Dist: repowise>=0.31.0; extra == "multilang"
12
+ Dynamic: license-file
13
+
14
+ # awgit — the git that knows a function from a line
15
+
16
+ Git has no world model. It knows which lines moved, not what a function is — so
17
+ "which commit owns this edit", "did this change conflict with mine", and "is
18
+ this the same change I pushed before" are all answered by heuristics over text.
19
+
20
+ **awgit rides on top of git** (git stays the byte-truth, the transport, the
21
+ history) and adds the layer above it: a **world model as the database**,
22
+ **semantic edit-ops as transactions**, **stacked commits with one pull request
23
+ each**, **verified identity**, and **leases** so several agents can work in one
24
+ tree without overwriting each other.
25
+
26
+ ```bash
27
+ pip install awgit
28
+ awgit init # verify the actor, install the capture hooks
29
+ ```
30
+
31
+ Needs Python 3.10+ and `git`. `gh` (the GitHub CLI) is needed for `push` and
32
+ `pr`, and for the verified identity — everything else works without it.
33
+
34
+ ## The model in one screen
35
+
36
+ ```bash
37
+ awgit stack # your commits above trunk — one PR each
38
+ awgit commit -m "fix parser" # git commit, lease-checked and captured
39
+ awgit push # one pull request per commit (dry run; --apply)
40
+ awgit absorb # route pending edits into the commits they belong to
41
+ awgit pr wait 42 --for merged
42
+ ```
43
+
44
+ There is no `awgit pr create`. **Push is how a pull request is opened**, and
45
+ pushing again after `commit --amend` adds a REVISION to the same PR rather than
46
+ opening a second one.
47
+
48
+ ## Stacked commits
49
+
50
+ Work is a stack of commits between trunk and HEAD, each one a reviewable change
51
+ that becomes its own pull request, diffed against the commit below it. A
52
+ reviewer reads one logical change instead of a 900-line branch, and the bottom
53
+ of a stack can land while the top is still being argued about.
54
+
55
+ | | |
56
+ |---|---|
57
+ | `awgit stack` / `awgit sl` | the stack, newest first, with each commit's Change-Id |
58
+ | `awgit prev` / `awgit next` | move down / up the stack |
59
+ | `awgit push` | publish it — one PR per commit |
60
+ | `awgit absorb` | fold pending edits into the commits that own them |
61
+ | `awgit uncommit` | undo the last commit, keep its changes |
62
+ | `awgit restack` / `awgit pull` | rebase onto trunk (`pull` fetches first) |
63
+ | `awgit worktree new` (also `list`, `rm`) | a checkout of your own |
64
+
65
+ ### Identity that survives a rewrite
66
+
67
+ Every commit carries an `Awgit-Change-Id` trailer, written once by a
68
+ `prepare-commit-msg` hook. A sha identifies a *snapshot*; the Change-Id
69
+ identifies the *change*, and it survives `--amend`, `rebase` and `cherry-pick` —
70
+ which is what lets `push` find the pull request you already opened instead of
71
+ opening another one.
72
+
73
+ ```bash
74
+ awgit change-id show # this commit's change id
75
+ awgit change-id find I3f2a… # every commit carrying it
76
+ ```
77
+
78
+ ### absorb routes by node, not by line
79
+
80
+ git answers "which commit owns this change" per LINE, so reindenting a function
81
+ makes every line blame the reformat. awgit asks which commit last changed **this
82
+ node**, so the answer survives reindentation and line movement, and a rename
83
+ within a file. (A function moved to a *different* file gets a new node id —
84
+ capture's rename detection is per-file — so absorb sees a delete and an add.)
85
+
86
+ ```bash
87
+ awgit absorb # show the routing
88
+ awgit absorb --apply # create the fixups and autosquash
89
+ ```
90
+
91
+ Generated and minified files are skipped and reported — they have no node
92
+ identity, and one bundle can stall a parser indefinitely.
93
+
94
+ ## Review that survives the code moving
95
+
96
+ ```bash
97
+ awgit review show # threads on this change
98
+ awgit review comment --node <id> -b "needs a bound" # a DRAFT
99
+ awgit review submit # publish them all at once
100
+ awgit review resolve <thread>
101
+ ```
102
+
103
+ A comment is anchored to a **node**, not to a line. The line number is computed
104
+ each time from where the node is now, so a thread follows the function down the
105
+ file as code is inserted above it, survives a rebase and a reformat, and is
106
+ re-found when the function moves to another file. A thread whose node was
107
+ DELETED is shown as `[orphaned]` with its text intact — "the function you
108
+ objected to is gone" is a review outcome, not a reason to hide the objection.
109
+
110
+ Comments are drafts until `submit`, so you can read a whole diff and publish
111
+ once. **Unresolved threads block `awgit pr merge`**: "merge and follow up" is
112
+ how an objection becomes a TODO nobody files.
113
+
114
+ Node ids come from capture, so comment on a node the diff showed you
115
+ (`awgit diff <a> <b> --json`).
116
+
117
+ ## Big repositories
118
+
119
+ ```bash
120
+ awgit clone <url> <dest> --paths AitherOS/lib # history now, contents on demand
121
+ awgit sparse add apps/web # materialise more
122
+ awgit sparse status # is this clone ACTUALLY lazy?
123
+ ```
124
+
125
+ `awgit clone` uses git's own partial clone (`--filter=blob:none`) with a cone
126
+ sparse-checkout: history and trees arrive immediately, file content is fetched
127
+ the first time something reads it, and the working tree holds only the
128
+ directories you asked for.
129
+
130
+ This is deliberately not a virtual filesystem. A VFS means FUSE on Linux and
131
+ ProjFS on Windows — and Microsoft, who wrote both ProjFS and VFSforGit, retired
132
+ that approach in favour of exactly this pair. Reimplementing what its own author
133
+ walked away from would trade a supported git feature for a kernel-adjacent
134
+ dependency on the platform where it is least reliable.
135
+
136
+ **`sparse status` reads the repository, not the flag you passed.**
137
+ `git clone --filter` succeeds and warns when a server declines to filter — you
138
+ get a full clone and exit 0. awgit reports lazy only when the filter is really
139
+ in the config and a promisor remote really exists to backfill from.
140
+
141
+ ## Landing it: evidence, ownership, the queue
142
+
143
+ ```bash
144
+ awgit prove # what this change touched, and what checked it
145
+ awgit owners <path> # who owns it — declared AND measured
146
+ awgit code def <symbol> # where it is defined
147
+ awgit queue enqueue 42 # GitHub's merge queue
148
+ awgit ci status
149
+ ```
150
+
151
+ **`awgit prove` is proof-carrying review.** A pull request normally arrives as a
152
+ diff and an assertion; "all checks passed" and "no checks ran" render almost
153
+ identically. `prove` attaches the nodes the change touched and what each gate
154
+ actually returned — and it never reports success without evidence: a gate that
155
+ exits 1 is a violation, a gate that exits 2 *could not judge*, and no gates at
156
+ all is **not proved** (exit 2), because "nothing verified this" must not be
157
+ spelled the same way as "verified". `--markdown` renders it as a PR comment.
158
+
159
+ Your gate runner attaches through the plugin seam, so awgit never has to know
160
+ what your checks are:
161
+
162
+ ```python
163
+ from awgit import plugins
164
+ plugins.register(plugins.GATES, lambda paths: [{"name": "ruff", "status": "ok"}])
165
+ ```
166
+
167
+ **`awgit owners` shows both answers.** CODEOWNERS says who *should* review;
168
+ the op-log knows who actually changed those nodes, weighted so ownership decays
169
+ (someone who last touched a module a year ago is not who you want reviewing it).
170
+ When the two disagree, that is the useful part — usually the file is stale.
171
+
172
+ `awgit queue` drives **GitHub's** merge queue rather than reimplementing one: it
173
+ already rebases, re-tests and lands, and a second implementation would be a
174
+ service that disagrees with it.
175
+
176
+ ## Working next to other people (and other agents)
177
+
178
+ A lease says "I am editing this". Take one **before** you edit: the baseline it
179
+ captures is what lets `stage-mine` separate your work from a peer's.
180
+
181
+ ```bash
182
+ awgit lease list # who holds what right now
183
+ awgit lease acquire <paths> # claim files, TTL-renewed
184
+ awgit stage-mine <path> # stage only YOUR edits to a shared file
185
+ ```
186
+
187
+ History rewrites (`absorb`, `uncommit`, `restack`, `pull`) refuse when another
188
+ actor holds a lease in the same worktree, and point you at `awgit worktree new`.
189
+ A solo checkout has no other actors and rewrites freely; set
190
+ `AWGIT_ALLOW_UNSAFE_REWRITE=1` to override.
191
+
192
+ ## The world model
193
+
194
+ Every commit is captured as an `EditOp`: which functions/classes/methods
195
+ changed, their old and new bodies by content address, the actor, the parent
196
+ chain. Ops key on **stable node ids** that survive renames and moves.
197
+
198
+ ```bash
199
+ awgit status # op-log status
200
+ awgit diff <sha> <sha> # NODE-level diff (not a text diff — see below)
201
+ awgit graph --format mermaid # the op-log as a graph
202
+ awgit ledger --sha <sha> # who changed what, under a verified identity
203
+ awgit evidence # the measurable claim, from your own op-log
204
+ awgit bodies --get <sha> # read a body from the content-addressed store
205
+ awgit dedupe --scan <trees> # quantify duplication; --reclaim to hard-link
206
+ ```
207
+
208
+ - **Merge** at node granularity: disjoint node sets merge clean by
209
+ construction, and a genuine collision escalates naming the exact function
210
+ (`awgit merge-preview`, `awgit merge-conflicts`, `awgit resolve-conflict`).
211
+ - **Differential sync**: `awgit sync export` emits the ops a peer is missing
212
+ plus the bodies they reference; `awgit sync import` applies idempotently.
213
+
214
+ ## It speaks git
215
+
216
+ Everyday verbs forward to git untouched — `awgit log`, `awgit add`,
217
+ `awgit show`, `awgit rebase`, and about twenty more — and `awgit git -- <args>`
218
+ runs anything else.
219
+
220
+ **No verb is repurposed.** `awgit diff` has always meant a node-level diff and
221
+ still does; git's own diff is `awgit git diff`. `awgit status` reports the
222
+ op-log. Silently changing what an existing command returns would break callers
223
+ in ways that surface far from the change.
224
+
225
+ ## For agents
226
+
227
+ ```bash
228
+ awgit commands --json # the entire CLI as JSON: every command, flag and type
229
+ ```
230
+
231
+ Read it once instead of scraping `--help`. It is introspected from the live
232
+ parser, so a command cannot be described unless it exists. Every read-only
233
+ command takes `--json`.
234
+
235
+ `awgit pr wait <n> --for merged` exits **0** when the condition holds and
236
+ **124** on timeout, so a loop can tell "it happened" from "I gave up".
237
+
238
+ ## Set up
239
+
240
+ ```bash
241
+ awgit init # verify the actor, install hooks, report the store
242
+ awgit hooks install # chained hooks — preserves any you already have
243
+ # post-commit -> `awgit capture` (records the op)
244
+ # pre-commit -> `awgit lease-check` (the lease gate)
245
+ # prepare-commit-msg -> stamps the Change-Id
246
+ awgit hooks uninstall # restore them
247
+ awgit version --json
248
+ ```
249
+
250
+ The durable store lives **outside** the git tree at `~/.aither/awgit/data`
251
+ (override with `VCS_DATA_ROOT`), so the op-log and body store never pollute your
252
+ repository or your clones.
253
+
254
+ ## Tests
255
+
256
+ ```bash
257
+ cd awgit && python -m pytest tests/ -q
258
+ ```
259
+
260
+ ## License
261
+
262
+ Apache-2.0. Built by Aitherium — the git that scales to agents.
awgit-1.0.0/README.md ADDED
@@ -0,0 +1,249 @@
1
+ # awgit — the git that knows a function from a line
2
+
3
+ Git has no world model. It knows which lines moved, not what a function is — so
4
+ "which commit owns this edit", "did this change conflict with mine", and "is
5
+ this the same change I pushed before" are all answered by heuristics over text.
6
+
7
+ **awgit rides on top of git** (git stays the byte-truth, the transport, the
8
+ history) and adds the layer above it: a **world model as the database**,
9
+ **semantic edit-ops as transactions**, **stacked commits with one pull request
10
+ each**, **verified identity**, and **leases** so several agents can work in one
11
+ tree without overwriting each other.
12
+
13
+ ```bash
14
+ pip install awgit
15
+ awgit init # verify the actor, install the capture hooks
16
+ ```
17
+
18
+ Needs Python 3.10+ and `git`. `gh` (the GitHub CLI) is needed for `push` and
19
+ `pr`, and for the verified identity — everything else works without it.
20
+
21
+ ## The model in one screen
22
+
23
+ ```bash
24
+ awgit stack # your commits above trunk — one PR each
25
+ awgit commit -m "fix parser" # git commit, lease-checked and captured
26
+ awgit push # one pull request per commit (dry run; --apply)
27
+ awgit absorb # route pending edits into the commits they belong to
28
+ awgit pr wait 42 --for merged
29
+ ```
30
+
31
+ There is no `awgit pr create`. **Push is how a pull request is opened**, and
32
+ pushing again after `commit --amend` adds a REVISION to the same PR rather than
33
+ opening a second one.
34
+
35
+ ## Stacked commits
36
+
37
+ Work is a stack of commits between trunk and HEAD, each one a reviewable change
38
+ that becomes its own pull request, diffed against the commit below it. A
39
+ reviewer reads one logical change instead of a 900-line branch, and the bottom
40
+ of a stack can land while the top is still being argued about.
41
+
42
+ | | |
43
+ |---|---|
44
+ | `awgit stack` / `awgit sl` | the stack, newest first, with each commit's Change-Id |
45
+ | `awgit prev` / `awgit next` | move down / up the stack |
46
+ | `awgit push` | publish it — one PR per commit |
47
+ | `awgit absorb` | fold pending edits into the commits that own them |
48
+ | `awgit uncommit` | undo the last commit, keep its changes |
49
+ | `awgit restack` / `awgit pull` | rebase onto trunk (`pull` fetches first) |
50
+ | `awgit worktree new` (also `list`, `rm`) | a checkout of your own |
51
+
52
+ ### Identity that survives a rewrite
53
+
54
+ Every commit carries an `Awgit-Change-Id` trailer, written once by a
55
+ `prepare-commit-msg` hook. A sha identifies a *snapshot*; the Change-Id
56
+ identifies the *change*, and it survives `--amend`, `rebase` and `cherry-pick` —
57
+ which is what lets `push` find the pull request you already opened instead of
58
+ opening another one.
59
+
60
+ ```bash
61
+ awgit change-id show # this commit's change id
62
+ awgit change-id find I3f2a… # every commit carrying it
63
+ ```
64
+
65
+ ### absorb routes by node, not by line
66
+
67
+ git answers "which commit owns this change" per LINE, so reindenting a function
68
+ makes every line blame the reformat. awgit asks which commit last changed **this
69
+ node**, so the answer survives reindentation and line movement, and a rename
70
+ within a file. (A function moved to a *different* file gets a new node id —
71
+ capture's rename detection is per-file — so absorb sees a delete and an add.)
72
+
73
+ ```bash
74
+ awgit absorb # show the routing
75
+ awgit absorb --apply # create the fixups and autosquash
76
+ ```
77
+
78
+ Generated and minified files are skipped and reported — they have no node
79
+ identity, and one bundle can stall a parser indefinitely.
80
+
81
+ ## Review that survives the code moving
82
+
83
+ ```bash
84
+ awgit review show # threads on this change
85
+ awgit review comment --node <id> -b "needs a bound" # a DRAFT
86
+ awgit review submit # publish them all at once
87
+ awgit review resolve <thread>
88
+ ```
89
+
90
+ A comment is anchored to a **node**, not to a line. The line number is computed
91
+ each time from where the node is now, so a thread follows the function down the
92
+ file as code is inserted above it, survives a rebase and a reformat, and is
93
+ re-found when the function moves to another file. A thread whose node was
94
+ DELETED is shown as `[orphaned]` with its text intact — "the function you
95
+ objected to is gone" is a review outcome, not a reason to hide the objection.
96
+
97
+ Comments are drafts until `submit`, so you can read a whole diff and publish
98
+ once. **Unresolved threads block `awgit pr merge`**: "merge and follow up" is
99
+ how an objection becomes a TODO nobody files.
100
+
101
+ Node ids come from capture, so comment on a node the diff showed you
102
+ (`awgit diff <a> <b> --json`).
103
+
104
+ ## Big repositories
105
+
106
+ ```bash
107
+ awgit clone <url> <dest> --paths AitherOS/lib # history now, contents on demand
108
+ awgit sparse add apps/web # materialise more
109
+ awgit sparse status # is this clone ACTUALLY lazy?
110
+ ```
111
+
112
+ `awgit clone` uses git's own partial clone (`--filter=blob:none`) with a cone
113
+ sparse-checkout: history and trees arrive immediately, file content is fetched
114
+ the first time something reads it, and the working tree holds only the
115
+ directories you asked for.
116
+
117
+ This is deliberately not a virtual filesystem. A VFS means FUSE on Linux and
118
+ ProjFS on Windows — and Microsoft, who wrote both ProjFS and VFSforGit, retired
119
+ that approach in favour of exactly this pair. Reimplementing what its own author
120
+ walked away from would trade a supported git feature for a kernel-adjacent
121
+ dependency on the platform where it is least reliable.
122
+
123
+ **`sparse status` reads the repository, not the flag you passed.**
124
+ `git clone --filter` succeeds and warns when a server declines to filter — you
125
+ get a full clone and exit 0. awgit reports lazy only when the filter is really
126
+ in the config and a promisor remote really exists to backfill from.
127
+
128
+ ## Landing it: evidence, ownership, the queue
129
+
130
+ ```bash
131
+ awgit prove # what this change touched, and what checked it
132
+ awgit owners <path> # who owns it — declared AND measured
133
+ awgit code def <symbol> # where it is defined
134
+ awgit queue enqueue 42 # GitHub's merge queue
135
+ awgit ci status
136
+ ```
137
+
138
+ **`awgit prove` is proof-carrying review.** A pull request normally arrives as a
139
+ diff and an assertion; "all checks passed" and "no checks ran" render almost
140
+ identically. `prove` attaches the nodes the change touched and what each gate
141
+ actually returned — and it never reports success without evidence: a gate that
142
+ exits 1 is a violation, a gate that exits 2 *could not judge*, and no gates at
143
+ all is **not proved** (exit 2), because "nothing verified this" must not be
144
+ spelled the same way as "verified". `--markdown` renders it as a PR comment.
145
+
146
+ Your gate runner attaches through the plugin seam, so awgit never has to know
147
+ what your checks are:
148
+
149
+ ```python
150
+ from awgit import plugins
151
+ plugins.register(plugins.GATES, lambda paths: [{"name": "ruff", "status": "ok"}])
152
+ ```
153
+
154
+ **`awgit owners` shows both answers.** CODEOWNERS says who *should* review;
155
+ the op-log knows who actually changed those nodes, weighted so ownership decays
156
+ (someone who last touched a module a year ago is not who you want reviewing it).
157
+ When the two disagree, that is the useful part — usually the file is stale.
158
+
159
+ `awgit queue` drives **GitHub's** merge queue rather than reimplementing one: it
160
+ already rebases, re-tests and lands, and a second implementation would be a
161
+ service that disagrees with it.
162
+
163
+ ## Working next to other people (and other agents)
164
+
165
+ A lease says "I am editing this". Take one **before** you edit: the baseline it
166
+ captures is what lets `stage-mine` separate your work from a peer's.
167
+
168
+ ```bash
169
+ awgit lease list # who holds what right now
170
+ awgit lease acquire <paths> # claim files, TTL-renewed
171
+ awgit stage-mine <path> # stage only YOUR edits to a shared file
172
+ ```
173
+
174
+ History rewrites (`absorb`, `uncommit`, `restack`, `pull`) refuse when another
175
+ actor holds a lease in the same worktree, and point you at `awgit worktree new`.
176
+ A solo checkout has no other actors and rewrites freely; set
177
+ `AWGIT_ALLOW_UNSAFE_REWRITE=1` to override.
178
+
179
+ ## The world model
180
+
181
+ Every commit is captured as an `EditOp`: which functions/classes/methods
182
+ changed, their old and new bodies by content address, the actor, the parent
183
+ chain. Ops key on **stable node ids** that survive renames and moves.
184
+
185
+ ```bash
186
+ awgit status # op-log status
187
+ awgit diff <sha> <sha> # NODE-level diff (not a text diff — see below)
188
+ awgit graph --format mermaid # the op-log as a graph
189
+ awgit ledger --sha <sha> # who changed what, under a verified identity
190
+ awgit evidence # the measurable claim, from your own op-log
191
+ awgit bodies --get <sha> # read a body from the content-addressed store
192
+ awgit dedupe --scan <trees> # quantify duplication; --reclaim to hard-link
193
+ ```
194
+
195
+ - **Merge** at node granularity: disjoint node sets merge clean by
196
+ construction, and a genuine collision escalates naming the exact function
197
+ (`awgit merge-preview`, `awgit merge-conflicts`, `awgit resolve-conflict`).
198
+ - **Differential sync**: `awgit sync export` emits the ops a peer is missing
199
+ plus the bodies they reference; `awgit sync import` applies idempotently.
200
+
201
+ ## It speaks git
202
+
203
+ Everyday verbs forward to git untouched — `awgit log`, `awgit add`,
204
+ `awgit show`, `awgit rebase`, and about twenty more — and `awgit git -- <args>`
205
+ runs anything else.
206
+
207
+ **No verb is repurposed.** `awgit diff` has always meant a node-level diff and
208
+ still does; git's own diff is `awgit git diff`. `awgit status` reports the
209
+ op-log. Silently changing what an existing command returns would break callers
210
+ in ways that surface far from the change.
211
+
212
+ ## For agents
213
+
214
+ ```bash
215
+ awgit commands --json # the entire CLI as JSON: every command, flag and type
216
+ ```
217
+
218
+ Read it once instead of scraping `--help`. It is introspected from the live
219
+ parser, so a command cannot be described unless it exists. Every read-only
220
+ command takes `--json`.
221
+
222
+ `awgit pr wait <n> --for merged` exits **0** when the condition holds and
223
+ **124** on timeout, so a loop can tell "it happened" from "I gave up".
224
+
225
+ ## Set up
226
+
227
+ ```bash
228
+ awgit init # verify the actor, install hooks, report the store
229
+ awgit hooks install # chained hooks — preserves any you already have
230
+ # post-commit -> `awgit capture` (records the op)
231
+ # pre-commit -> `awgit lease-check` (the lease gate)
232
+ # prepare-commit-msg -> stamps the Change-Id
233
+ awgit hooks uninstall # restore them
234
+ awgit version --json
235
+ ```
236
+
237
+ The durable store lives **outside** the git tree at `~/.aither/awgit/data`
238
+ (override with `VCS_DATA_ROOT`), so the op-log and body store never pollute your
239
+ repository or your clones.
240
+
241
+ ## Tests
242
+
243
+ ```bash
244
+ cd awgit && python -m pytest tests/ -q
245
+ ```
246
+
247
+ ## License
248
+
249
+ Apache-2.0. Built by Aitherium — the git that scales to agents.
@@ -0,0 +1,107 @@
1
+ """awgit — Aither World-Graph git: semantic version control on top of git.
2
+
3
+ Rides ON TOP of git (git stays the byte-transport + history). Adds: edit-ops
4
+ keyed on stable graph node ids, a durable op-log, semantic diff, a
5
+ node-granularity merge engine, lease-based conflict prevention, content-addressed
6
+ bodies, verified-identity attribution, and differential sync.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from awgit.bodies import BodyStore, blob_sha, dedupe_report, reclaim, scan_tree
12
+ from awgit.capture import capture_ops
13
+ from awgit.data_root import vcs_data_root
14
+ from awgit.diff import diff_git, diff_opsets, render
15
+ from awgit.identity import attribution_id, github_email
16
+ from awgit.leases import LeaseRegistry
17
+ from awgit.ledger import LedgerEntry, mint_ledger_ref, op_to_ledger_entry
18
+ from awgit.merge import MergeResult, list_conflicts, merge_ops, resolve_conflict
19
+ from awgit.oplog import OpLog
20
+ from awgit.schema import SCHEMA_VERSION, EditOp, MergeConflict, NodeChange
21
+ from awgit.sync import export_delta, import_delta, sync_state, sync_status
22
+
23
+
24
+ def _installed_version() -> str:
25
+ """Version from installed metadata; "" when awgit is not an installed dist."""
26
+ try:
27
+ from importlib.metadata import version
28
+
29
+ return version("awgit")
30
+ except Exception: # noqa: BLE001 - absence is a normal state, see _resolve_version
31
+ return ""
32
+
33
+
34
+ def _pyproject_version() -> str:
35
+ """Version from the adjacent pyproject; "" when there is no source checkout."""
36
+ import re
37
+ from pathlib import Path
38
+
39
+ try:
40
+ text = (Path(__file__).resolve().parent.parent / "pyproject.toml").read_text(
41
+ encoding="utf-8")
42
+ except OSError:
43
+ return ""
44
+ match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.M)
45
+ return match.group(1) if match else ""
46
+
47
+
48
+ def _resolve_version() -> str:
49
+ """The version, DERIVED — never a literal that can drift from pyproject.
50
+
51
+ The adjacent pyproject wins when there IS one, because that means we are
52
+ running from a source checkout and the source is what someone is editing.
53
+ Installed metadata is the answer for anyone who pip-installed us.
54
+
55
+ That order is deliberate and was measured the moment it was written the
56
+ other way round: with metadata first this returned **0.1.0** while
57
+ pyproject said 0.3.1, because an editable install records the version it
58
+ was installed AT and never refreshes. Reporting a version two releases
59
+ stale — from the tool whose own release rail exists because it once sat at
60
+ 0.1.0 for a month while the package kept moving — is worse than reporting
61
+ nothing.
62
+
63
+ A hardcoded ``__version__`` is not an option either: it is a second source
64
+ of truth for the one string a release is identified by, and the drift only
65
+ becomes visible once something is published under the wrong number.
66
+
67
+ Each source RETURNS "" when it does not apply rather than swallowing the
68
+ error, so "not installed" and "no checkout" are ordinary answers here, not
69
+ suppressed failures. The last resort is a marker nobody can mistake for a
70
+ real release.
71
+ """
72
+ return _pyproject_version() or _installed_version() or "0+unknown"
73
+
74
+
75
+ __version__ = _resolve_version()
76
+
77
+ __all__ = [
78
+ "BodyStore",
79
+ "EditOp",
80
+ "LedgerEntry",
81
+ "MergeConflict",
82
+ "MergeResult",
83
+ "NodeChange",
84
+ "SCHEMA_VERSION",
85
+ "OpLog",
86
+ "LeaseRegistry",
87
+ "attribution_id",
88
+ "blob_sha",
89
+ "capture_ops",
90
+ "dedupe_report",
91
+ "diff_git",
92
+ "diff_opsets",
93
+ "export_delta",
94
+ "github_email",
95
+ "import_delta",
96
+ "list_conflicts",
97
+ "merge_ops",
98
+ "mint_ledger_ref",
99
+ "op_to_ledger_entry",
100
+ "reclaim",
101
+ "render",
102
+ "resolve_conflict",
103
+ "scan_tree",
104
+ "sync_state",
105
+ "sync_status",
106
+ "vcs_data_root",
107
+ ]