Changelog
All notable changes to awgit are recorded here.
[1.0.0] — 2026-08-12
awgit stops being a sidecar you remember to invoke and becomes the tool you drive: stacked commits, one pull request per commit, review threads that survive the code moving, and evidence attached to the change itself.
The 1.0 is a commitment to the command surface below. Everything in it is exercised by the test suite (ACC008 holds that at zero), documented (ACC007 holds that at 40/40), and free of internal references (AWP001 holds that at zero). push was verified against real GitHub: three commits became three pull requests, each showing one file, bases chained, and two amends plus a restack updated them in place instead of opening duplicates.
Stacked commits, and push as the way a pull request is opened. awgit stops being a sidecar you remember to invoke and becomes the tool you drive.
Added
- Stacked commits.
stack/sl,prev,next. Work is the commits between trunk and HEAD, each a reviewable change. pushIS the pull request. One PR per commit, based on the commit below it, so a reviewer reads one logical change instead of a 900-line branch. There is nopr create. Pushing aftercommit --amendadds a REVISION to the same PR rather than opening a second one.Awgit-Change-Id, a trailer written once by aprepare-commit-msghook. A sha identifies a snapshot; this identifies the CHANGE, and survives amend, rebase and cherry-pick — which is what makes the line above true.absorb, routing pending edits into the commits that own them BY NODE rather than by line blame, so the answer survives reindentation, renaming and moving between files.- A rewrite guard.
absorb/uncommit/restack/pullrefuse when another actor holds a lease in the same worktree and point atawgit worktree new. A solo checkout has no other actors and rewrites freely. worktree new|list|rm,pr list|view|checks|merge|wait(waitexits 0 on the condition and 124 on timeout),commit(lease checked, captured),init,version.commands --json— the whole CLI as data, introspected from the live parser, so an agent never scrapes--help. Every read-only command takes--json.-
Git passthrough for ~20 everyday verbs plus
awgit git -- <args>. No existing verb was repurposed:awgit diffstill means a node-level diff. - Review threads anchored to a NODE, not a line.
review show|comment| submit|resolve; drafts until submit; the line is COMPUTED each time, so a thread follows the function down the file, survives a rebase and a reformat, and is re-found when it moves to another file (ambiguity is refused, not guessed). A thread whose node was deleted shows as[orphaned]with its text intact. Unresolved threads blockpr merge. prove— proof-carrying review. The nodes a change touched and what each gate actually returned, with VIOLATION (exit 1) and DEAD (exit 2) kept apart. No gates at all is exit 2: “nothing verified this” must not be spelled the same way as “verified”.--markdownrenders a PR comment. Gate runners attach throughawgit.plugins.owners— ownership measured, not declared. CODEOWNERS says who should review; the op-log knows who actually changed those nodes, under a verified identity, recency-weighted so ownership decays. Both are shown, because the disagreement is the signal.code def|search, answering from the node registry, and saying plainly that an uncaptured symbol is ABSENT rather than missing.clone/sparse— instant checkout at any size via git’s own partial clone plus a cone sparse-checkout.sparse statusreads the REPOSITORY, not the flag:git clone --filtersucceeds and warns when a server declines, so awgit reports lazy only when the filter and a promisor remote both really exist.queue/ci, driving GitHub’s merge queue and Actions rather than reimplementing either.
Fixed
- An unparseable file was recorded as “every node deleted”. A file carrying live conflict markers parses to zero nodes, which diffs as deletion — a confidently wrong record in a log meant to be authoritative. Unknown is now distinct from empty.
- BOM-prefixed files were silently skipped, because the parser read plain utf-8 and
astrejects the resulting U+FEFF. register_tools()returned 5 and registered nothing — five MCP tools reported as wired, none of them wired.awgit initdid not exist despite being step one of the documented setup.restackdid not restack. Amending a commit in the middle of a stack orphans everything above it, andrestackrebased onto trunk instead of repairing them — soprev+ amend +restackprinted “HEAD is up to date” and silently DROPPED the rest of the stack. It now replays orphans, matched by Change-Id and scoped to commits whose parent was REPLACED, so deliberately discarded work is never resurrected.sync export/sync importdid not exist; the documented spelling for the package’s headline feature errored. Both now work.
[0.3.1] — 2026-08-10
awgit ledger was unusable from its own output.
Fixed
--oprefused the identifier the listing printed. The listing renderedledger_refas column 1 and the op_id NOWHERE, while--opmatched onlyop_id. So copying an id offawgit ledgerand passing it back answeredvcs: ledger: no ops match— which reads as “that op does not exist”, not as “you passed the wrong one of two ids you were never shown”. There was no way to look an op up from the command that lists ops.--opnow takes either id, by prefix. The prefix part is load-bearing and was learned the hard way: the first fix printed the op_id too, abbreviated to 16 chars, while still matching on equality — reintroducing the identical defect one layer down, on the id it had just added. Ambiguous prefixes fail loudly with the match count rather than silently answering about the wrong op.- The listing now prints the abbreviated op_id alongside
ledger_ref.
Added
awgit ledger --json— the fullEditOpset, not a re-parse of a display string that was never a contract. This is the machine seam for anything programmatic (world-model seeding, reward programs, exports).
Notes
- The in-repo upstream copy (
AitherOS/lib/awgit/cli.py) receives the lookup fix and the printed op_id;--jsonis standalone-package-only for now, because upstream’s_cmd_ledgeralso carries the ACTA--creditpath and the two functions have deliberately diverged. AWG005’s parity checks (_actor,coverage_gap,is_guarded) are unaffected. - AWG006 compares version strings only (
local == published), so it can see an unreleased bump but not unreleased source drift. This bump is what makes the fix visible to it — until0.3.1is published, AWG006 correctly goes red.
[0.3.0] — 2026-08-09
awgit stops being Python-only, and starts drawing itself.
Added
-
Multi-language node identity.
awgitunderstood 7,824 files in the repo it was built for and was blind to the other 12,600 — every.ts,.tsx,.go,.csfile was invisible to the diff, the merge engine and the graph, because node identity came from CPython’sast. It now borrows the parser the surrounding platform already runs: 75 extensions across 20+ languages, via the optionalawgit[multilang]extra.The property that made this possible, verified before the adapter was written: those symbol ids are stable under movement. A TypeScript function that changed position and had its body rewritten kept its id. Stability under movement is the whole basis of node-level merge.
Python still parses natively — its node ids are already in existing op-logs, and switching would orphan them. The dependency is optional by design: awgit imports and guards Python without it, a file the parser chokes on degrades to “no symbols” rather than losing the commit’s op, and a file type nobody can parse is skipped rather than recorded as an empty change.
-
awgit graph— the op-log is a graph, so it renders as one.mermaidfor humans (files as subgraphs, code nodes inside, a node two or more actors touched drawn as a collision) and node/edgejsonso it can be ingested alongside other graphs instead of being a private format. An empty op-log says so explicitly, because a blank diagram reads as “nothing is wrong” rather than “no data”.
Fixed
- Capture attributed every agent to one identity.
resolve_actorresolved to the verified GitHub login, and where many agents share one login that collapsed every session into a single actor — making “two actors touched this node” inexpressible and the collision view structurally blind. Drawing the graph is what exposed it: 188 ops across 189 files, one actor. The session now supplies the claimed actor whileverified_actorstill records the verified identity — the schema always separated those, capture just wasn’t using the distinction. This also aligns capture with the lease gate, which had already moved to per-session actors; the two disagreeing meant leases discriminated per session while captures did not.
[0.2.0] — 2026-08-09
The release that makes the concurrent-edit guard actually guard something.
0.1.0 shipped a lease registry and a pre-commit gate whose entire purpose is stopping two agents from clobbering each other’s work — and in practice it prevented nothing, because it could not be switched on without breaking the machine it ran on. This release fixes that, and widens what it protects.
Added
awgit lease acquire --staged— take leases on exactly the staged files the gate will check. Complying with enforcement is now one command instead of a per-file chore, which is what makes broader coverage survivable.- Automatic per-session identity.
_actor()derivesclaude:<session-id>fromCLAUDE_CODE_SESSION_ID(stable for a session and inherited by the git hook’s subprocess), then falls back touser@host. Nothing to export.
Changed
- The lease gate no longer covers only
.py.coverage_gap()now consultsis_guarded(): source (.py .ts .tsx .js .jsx .mjs), config (.yml .yaml .toml .json .ini .cfg), scripts (.sh .bash .ps1 .psm1),.md, and.sql .proto .env— minus bulk content nobody races on (site content, generated data, published artifacts). Not “everything”: guarding bulk content would make a routine commit need dozens of leases, and a gate that heavy gets routed around rather than satisfied.
Fixed
- Enforcement could not be enabled.
_actor()returned"unknown"unlessAITHER_ACTORwas exported, andlease-checkrejects an unknown actor — soVCS_LEASES_ENFORCE=1would have blocked every commit until every session remembered a variable. A guard that can only be turned on by breaking the machine never gets turned on, and it never was. - A
.ymlcommit reportedlease-check OKwithout checking anything, so compose files, CI workflows and docs were exactly as clobberable as before.
Why this matters, measured
Across 568 commits in one week on the repo this was built for: the median commit touches 1 guarded file, 89% touch five or fewer, and 9% touch a file that another commit touched inside a five-minute window — the collision git resolves by letting both through and telling you later. During the same period a single file was overwritten by a concurrent session four times, the last overwrite silently reverting a fix that was already committed upstream.
[0.1.0] — 2026-08-08
Initial public release: semantic (node-level) diff and merge for Python, an edit-op log, a content-addressed body store, a lease registry, and the git hook chain that drives capture and the pre-commit gate.