awgit 0.2.0__tar.gz → 0.3.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.
- {awgit-0.2.0 → awgit-0.3.0}/PKG-INFO +3 -1
- {awgit-0.2.0 → awgit-0.3.0}/awgit/capture.py +42 -1
- {awgit-0.2.0 → awgit-0.3.0}/awgit/cli.py +30 -0
- awgit-0.3.0/awgit/graph.py +133 -0
- awgit-0.3.0/awgit/repowise_parser.py +107 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit.egg-info/PKG-INFO +3 -1
- {awgit-0.2.0 → awgit-0.3.0}/awgit.egg-info/SOURCES.txt +2 -0
- awgit-0.3.0/awgit.egg-info/requires.txt +4 -0
- {awgit-0.2.0 → awgit-0.3.0}/pyproject.toml +9 -1
- awgit-0.2.0/awgit.egg-info/requires.txt +0 -1
- {awgit-0.2.0 → awgit-0.3.0}/LICENSE +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/README.md +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/__init__.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/bodies.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/bridge.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/data_root.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/diff.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/hooks/chain.sh +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/hooks/post-commit.d/vcs-capture +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/hooks/pre-commit.d/vcs-lease-check +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/identity.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/leases.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/ledger.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/mcp.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/merge.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/nodeid.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/oplog.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/parser.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/schema.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit/sync.py +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit.egg-info/dependency_links.txt +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit.egg-info/entry_points.txt +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/awgit.egg-info/top_level.txt +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/setup.cfg +0 -0
- {awgit-0.2.0 → awgit-0.3.0}/tests/test_awgit_standalone.py +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awgit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
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
5
|
License: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Requires-Dist: httpx>=0.25.0
|
|
10
|
+
Provides-Extra: multilang
|
|
11
|
+
Requires-Dist: repowise>=0.31.0; extra == "multilang"
|
|
10
12
|
Dynamic: license-file
|
|
11
13
|
|
|
12
14
|
# awgit — Aither World-Graph git
|
|
@@ -128,10 +128,23 @@ def resolve_actor(
|
|
|
128
128
|
"""
|
|
129
129
|
github = _github_identity(data_root)
|
|
130
130
|
env_actor = os.environ.get("AITHER_ACTOR")
|
|
131
|
+
# The SESSION, before the GitHub login. Every agent on this box shares one
|
|
132
|
+
# GitHub identity, so resolving to `github` collapsed every session into one
|
|
133
|
+
# actor: measured 2026-08-09, an op-log of 188 ops across 189 files had
|
|
134
|
+
# exactly ONE actor, which makes "two actors touched this node" impossible to
|
|
135
|
+
# express and the collision view structurally blind. The schema already
|
|
136
|
+
# separates CLAIMED from VERIFIED — `verified_actor` still records the
|
|
137
|
+
# GitHub identity below — so the claimed actor is free to be the session
|
|
138
|
+
# that actually made the edit, which is what attribution is for. Matches
|
|
139
|
+
# the lease gate's `_actor()`, and the two disagreeing is what made leases
|
|
140
|
+
# discriminate per session while captures did not.
|
|
141
|
+
session = os.environ.get("CLAUDE_CODE_SESSION_ID")
|
|
131
142
|
if actor_arg:
|
|
132
143
|
actor, source = actor_arg, "arg"
|
|
133
144
|
elif env_actor:
|
|
134
145
|
actor, source = env_actor, "env"
|
|
146
|
+
elif session:
|
|
147
|
+
actor, source = f"claude:{session}", "session"
|
|
135
148
|
elif github:
|
|
136
149
|
actor, source = github, "github"
|
|
137
150
|
else:
|
|
@@ -154,6 +167,29 @@ def _ancestor_shas(repo: Path, sha: str) -> List[str]:
|
|
|
154
167
|
def _node_records(src: Optional[bytes], rel_path: str) -> List[Dict[str, Any]]:
|
|
155
168
|
if src is None:
|
|
156
169
|
return []
|
|
170
|
+
if not rel_path.endswith(".py"):
|
|
171
|
+
# NON-PYTHON: identity comes from repowise, which parses 75 extensions
|
|
172
|
+
# across 20+ languages and emits ids of the form `path::qualified_name`
|
|
173
|
+
# — verified stable across a symbol MOVING and its body changing, which
|
|
174
|
+
# is the property node-level merge rests on. Without this every
|
|
175
|
+
# .ts/.tsx/.go/.cs file was invisible to awgit: of the 20,451 files
|
|
176
|
+
# repowise has indexed in this repo, 12,600+ are not Python.
|
|
177
|
+
from awgit.repowise_parser import parse_symbols
|
|
178
|
+
|
|
179
|
+
text = src.decode("utf-8", errors="ignore").split("\n")
|
|
180
|
+
out: List[Dict[str, Any]] = []
|
|
181
|
+
for sym in parse_symbols(src, rel_path):
|
|
182
|
+
start, end = sym.get("start_line") or 0, sym.get("end_line") or 0
|
|
183
|
+
body = "\n".join(text[start - 1:end]) if start and end else ""
|
|
184
|
+
out.append({
|
|
185
|
+
"name": sym["symbol"] or sym["node_id"],
|
|
186
|
+
"path": rel_path,
|
|
187
|
+
"type": sym.get("kind") or "symbol",
|
|
188
|
+
"signature": "",
|
|
189
|
+
"body": body,
|
|
190
|
+
})
|
|
191
|
+
return out
|
|
192
|
+
|
|
157
193
|
# lazy: CodeGraph's import chain is ~15s (AitherConfig auto-tune etc.); only
|
|
158
194
|
# pay it when actually parsing (capture/diff/merge runtime), never for
|
|
159
195
|
# `vcs lease`/`status` which import this module but never parse.
|
|
@@ -401,8 +437,13 @@ def capture_ops(
|
|
|
401
437
|
manager = StableNodeIDManager(path=data / "nodes.json", persist=True)
|
|
402
438
|
store = BodyStore(data_root=data)
|
|
403
439
|
node_changes: List[NodeChange] = []
|
|
440
|
+
from awgit.repowise_parser import language_for
|
|
441
|
+
|
|
404
442
|
for rel in files:
|
|
405
|
-
|
|
443
|
+
# Python natively; anything else only when repowise can parse it,
|
|
444
|
+
# so a file type nobody can give node identity to is skipped rather
|
|
445
|
+
# than recorded as an empty change.
|
|
446
|
+
if not rel.endswith(".py") and not language_for(rel):
|
|
406
447
|
continue
|
|
407
448
|
old_src = git_blob(repo, parent, rel)
|
|
408
449
|
new_src = git_blob(repo, git_sha, rel)
|
|
@@ -223,6 +223,25 @@ def _cmd_lease_check(args: argparse.Namespace) -> int:
|
|
|
223
223
|
return 0
|
|
224
224
|
|
|
225
225
|
|
|
226
|
+
|
|
227
|
+
def _cmd_graph(args: argparse.Namespace) -> int:
|
|
228
|
+
from awgit.graph import build, to_json, to_mermaid
|
|
229
|
+
|
|
230
|
+
g = build(since=args.since, actor=args.actor)
|
|
231
|
+
text = to_json(g) if args.format == "json" else to_mermaid(g)
|
|
232
|
+
if args.out:
|
|
233
|
+
try:
|
|
234
|
+
Path(args.out).write_text(text, encoding="utf-8")
|
|
235
|
+
except OSError as exc:
|
|
236
|
+
print(f"vcs: cannot write {args.out}: {exc}", file=sys.stderr)
|
|
237
|
+
return 2
|
|
238
|
+
print(f"vcs: wrote {args.format} graph to {args.out} "
|
|
239
|
+
f"({g['ops']} ops, {len(g['collisions'])} collision(s))")
|
|
240
|
+
else:
|
|
241
|
+
print(text)
|
|
242
|
+
return 0
|
|
243
|
+
|
|
244
|
+
|
|
226
245
|
def _cmd_bodies(args: argparse.Namespace) -> int:
|
|
227
246
|
from awgit.bodies import BodyStore
|
|
228
247
|
|
|
@@ -405,6 +424,15 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
405
424
|
p_diff.add_argument("b", help="target sha")
|
|
406
425
|
|
|
407
426
|
sub.add_parser("status", help="op-log status")
|
|
427
|
+
p_graph = sub.add_parser(
|
|
428
|
+
"graph", help="render the op-log as a graph (mermaid or json)")
|
|
429
|
+
p_graph.add_argument("--format", choices=("mermaid", "json"),
|
|
430
|
+
default="mermaid")
|
|
431
|
+
p_graph.add_argument("--since", default=None,
|
|
432
|
+
help="ISO timestamp — only ops at or after it")
|
|
433
|
+
p_graph.add_argument("--actor", default=None, help="restrict to one actor")
|
|
434
|
+
p_graph.add_argument("--out", default=None,
|
|
435
|
+
help="write to a file instead of stdout")
|
|
408
436
|
|
|
409
437
|
p_mp = sub.add_parser("merge-preview", help="node-level merge preview of two shas")
|
|
410
438
|
p_mp.add_argument("a", help="base-side sha")
|
|
@@ -518,6 +546,8 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
518
546
|
return _cmd_lease(args)
|
|
519
547
|
if args.cmd == "lease-check":
|
|
520
548
|
return _cmd_lease_check(args)
|
|
549
|
+
if args.cmd == "graph":
|
|
550
|
+
return _cmd_graph(args)
|
|
521
551
|
if args.cmd == "bodies":
|
|
522
552
|
return _cmd_bodies(args)
|
|
523
553
|
if args.cmd == "dedupe":
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Render the op-log as a GRAPH — awgit's own data, drawn.
|
|
2
|
+
|
|
3
|
+
awgit is already a graph and was only ever printed as lines. Every edit op is
|
|
4
|
+
an edge: an ACTOR touched a NODE (a function or class with a stable id) that
|
|
5
|
+
lives in a FILE, at a commit. Rendering that answers, at a glance, the question
|
|
6
|
+
the whole lease plane exists for — *who is working where, and where are two
|
|
7
|
+
actors standing on the same node* — instead of making someone reconstruct it
|
|
8
|
+
from `awgit ledger` output.
|
|
9
|
+
|
|
10
|
+
Two shapes, deliberately:
|
|
11
|
+
|
|
12
|
+
- ``mermaid`` for humans: files as subgraphs, code nodes inside them, actors as
|
|
13
|
+
rounded nodes with edges into what they touched. A node TWO OR MORE actors
|
|
14
|
+
touched is drawn as a collision, because that is the only part of the picture
|
|
15
|
+
that means anything is wrong.
|
|
16
|
+
- ``json`` for machines: plain nodes/edges so the platform's graph plane can
|
|
17
|
+
ingest it. awgit's graph is one graph among many — code, services, memory —
|
|
18
|
+
and this is the seam that lets it be a subgraph of that larger graph rather
|
|
19
|
+
than a private format nothing else can read.
|
|
20
|
+
|
|
21
|
+
Deliberately NO layout, colours-by-actor or clustering heuristics: this renders
|
|
22
|
+
what the op-log says. If the picture is wrong, the op-log is wrong, and that is
|
|
23
|
+
worth seeing rather than smoothing over.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
from collections import defaultdict
|
|
29
|
+
from typing import Optional
|
|
30
|
+
|
|
31
|
+
from awgit.oplog import OpLog
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _short(text: str, keep: int = 28) -> str:
|
|
35
|
+
text = text or "?"
|
|
36
|
+
return text if len(text) <= keep else text[:keep - 1] + "…"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build(data_root=None, since: Optional[str] = None, actor: Optional[str] = None) -> dict:
|
|
40
|
+
"""Collect the op-log into {nodes, edges, collisions}. Pure data, no rendering."""
|
|
41
|
+
log = OpLog(data_root=data_root)
|
|
42
|
+
ops = log.ops_since(since) if since else log.all_ops()
|
|
43
|
+
if actor:
|
|
44
|
+
ops = [o for o in ops if getattr(o, "actor", None) == actor]
|
|
45
|
+
|
|
46
|
+
files: dict = defaultdict(set) # path -> {(node_id, symbol)}
|
|
47
|
+
touched: dict = defaultdict(set) # (path, node_id) -> {actor}
|
|
48
|
+
actors: set = set()
|
|
49
|
+
for op in ops:
|
|
50
|
+
who = getattr(op, "actor", None) or "unknown"
|
|
51
|
+
actors.add(who)
|
|
52
|
+
for ch in (getattr(op, "node_changes", None) or []):
|
|
53
|
+
nid = getattr(ch, "node_id", None)
|
|
54
|
+
if not nid:
|
|
55
|
+
continue
|
|
56
|
+
path = getattr(ch, "path", None) or "?"
|
|
57
|
+
files[path].add((nid, getattr(ch, "symbol", None) or nid[:12]))
|
|
58
|
+
touched[(path, nid)].add(who)
|
|
59
|
+
|
|
60
|
+
collisions = sorted(k for k, v in touched.items() if len(v) > 1)
|
|
61
|
+
return {
|
|
62
|
+
"ops": len(ops),
|
|
63
|
+
"actors": sorted(actors),
|
|
64
|
+
"files": {p: sorted(v) for p, v in files.items()},
|
|
65
|
+
"touched": {f"{p}::{n}": sorted(v) for (p, n), v in touched.items()},
|
|
66
|
+
"collisions": [{"path": p, "node_id": n, "actors": sorted(touched[(p, n)])}
|
|
67
|
+
for p, n in collisions],
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def to_mermaid(g: dict) -> str:
|
|
72
|
+
"""A mermaid `graph LR` of actors -> nodes, grouped by file."""
|
|
73
|
+
out = ["graph LR"]
|
|
74
|
+
if not g["files"]:
|
|
75
|
+
# An empty graph must SAY it is empty. A bare `graph LR` renders as a
|
|
76
|
+
# blank box, which reads as "nothing is wrong" rather than "no data".
|
|
77
|
+
out.append(' empty["op-log is empty — no captures yet"]')
|
|
78
|
+
return "\n".join(out)
|
|
79
|
+
|
|
80
|
+
ids: dict = {}
|
|
81
|
+
for fi, (path, nodes) in enumerate(sorted(g["files"].items())):
|
|
82
|
+
out.append(f' subgraph F{fi}["{_short(path, 40)}"]')
|
|
83
|
+
for ni, (nid, symbol) in enumerate(nodes):
|
|
84
|
+
key = f"N{fi}_{ni}"
|
|
85
|
+
ids[(path, nid)] = key
|
|
86
|
+
hits = g["touched"].get(f"{path}::{nid}", [])
|
|
87
|
+
marker = "💥 " if len(hits) > 1 else ""
|
|
88
|
+
out.append(f' {key}["{marker}{_short(symbol)}"]')
|
|
89
|
+
out.append(" end")
|
|
90
|
+
|
|
91
|
+
for ai, who in enumerate(g["actors"]):
|
|
92
|
+
out.append(f' A{ai}("{_short(who, 34)}")')
|
|
93
|
+
|
|
94
|
+
seen = set()
|
|
95
|
+
for key, whos in g["touched"].items():
|
|
96
|
+
path, _, nid = key.rpartition("::")
|
|
97
|
+
target = ids.get((path, nid))
|
|
98
|
+
if not target:
|
|
99
|
+
continue
|
|
100
|
+
for who in whos:
|
|
101
|
+
ai = g["actors"].index(who)
|
|
102
|
+
edge = (ai, target)
|
|
103
|
+
if edge in seen:
|
|
104
|
+
continue
|
|
105
|
+
seen.add(edge)
|
|
106
|
+
out.append(f" A{ai} --> {target}")
|
|
107
|
+
|
|
108
|
+
for c in g["collisions"]:
|
|
109
|
+
target = ids.get((c["path"], c["node_id"]))
|
|
110
|
+
if target:
|
|
111
|
+
out.append(f" style {target} fill:#a33,stroke:#f66,color:#fff")
|
|
112
|
+
return "\n".join(out)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def to_json(g: dict) -> str:
|
|
116
|
+
"""Node/edge form for ingestion into the platform's graph-of-graphs."""
|
|
117
|
+
nodes = [{"id": f"actor:{a}", "kind": "actor", "label": a} for a in g["actors"]]
|
|
118
|
+
edges = []
|
|
119
|
+
for path, entries in g["files"].items():
|
|
120
|
+
nodes.append({"id": f"file:{path}", "kind": "file", "label": path})
|
|
121
|
+
for nid, symbol in entries:
|
|
122
|
+
nodes.append({"id": f"node:{path}::{nid}", "kind": "code_node",
|
|
123
|
+
"label": symbol, "path": path})
|
|
124
|
+
edges.append({"src": f"node:{path}::{nid}", "dst": f"file:{path}",
|
|
125
|
+
"rel": "in_file"})
|
|
126
|
+
for key, whos in g["touched"].items():
|
|
127
|
+
for who in whos:
|
|
128
|
+
edges.append({"src": f"actor:{who}", "dst": f"node:{key}",
|
|
129
|
+
"rel": "touched"})
|
|
130
|
+
return json.dumps({
|
|
131
|
+
"graph": "awgit.oplog", "ops": g["ops"],
|
|
132
|
+
"nodes": nodes, "edges": edges, "collisions": g["collisions"],
|
|
133
|
+
}, indent=2)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Multi-language node identity for awgit, borrowed from repowise.
|
|
2
|
+
|
|
3
|
+
WHY (2026-08-09). awgit's node model is Python-only: ``parse_source_bytes``
|
|
4
|
+
routes to CPython's ``ast``, so every `.ts`, `.tsx`, `.go`, `.cs` file is
|
|
5
|
+
invisible to the diff, the merge engine and the graph. Measured on this repo,
|
|
6
|
+
that is not a corner case — repowise's index holds 20,451 files, of which
|
|
7
|
+
**7,824 are Python and 12,600+ are not** (2,859 `.tsx`, 2,163 `.ts`, 1,423
|
|
8
|
+
yaml, 1,015 json, …). The lease gate was widened to guard those files, but a
|
|
9
|
+
lease is all awgit could offer them: no node identity, nothing to merge,
|
|
10
|
+
nothing to draw.
|
|
11
|
+
|
|
12
|
+
Rather than grow a second tree-sitter stack inside awgit, this adapts the one
|
|
13
|
+
the platform already runs. repowise (PyPI, third-party) parses **75 extensions
|
|
14
|
+
across 20+ languages** and — the fact that decides it — emits symbol ids of the
|
|
15
|
+
form ``path::qualified_name``, which are STABLE. Verified before writing any of
|
|
16
|
+
this: a TypeScript function that MOVED position and had its BODY changed kept
|
|
17
|
+
the id ``probe.ts::alpha`` across both edits. Stability under movement is the
|
|
18
|
+
whole basis of node-level merge; without it this would be worthless.
|
|
19
|
+
|
|
20
|
+
Deliberately a THIN adapter:
|
|
21
|
+
* Python still goes to CodeGraph. It is the semantics awgit was built on, its
|
|
22
|
+
node ids are already in the op-log, and switching would orphan every op
|
|
23
|
+
recorded so far.
|
|
24
|
+
* repowise is OPTIONAL. It is a third-party dependency and the standalone
|
|
25
|
+
package must not hard-require it — `available()` is false when it is absent
|
|
26
|
+
and callers fall back, rather than awgit failing to import.
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from typing import List, Optional, Tuple
|
|
31
|
+
|
|
32
|
+
_PARSER = None
|
|
33
|
+
_UNAVAILABLE: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def available() -> Tuple[bool, str]:
|
|
37
|
+
"""(usable, why-not). Never raises: absence is a fallback, not a failure."""
|
|
38
|
+
global _PARSER, _UNAVAILABLE
|
|
39
|
+
if _PARSER is not None:
|
|
40
|
+
return True, ""
|
|
41
|
+
if _UNAVAILABLE is not None:
|
|
42
|
+
return False, _UNAVAILABLE
|
|
43
|
+
try:
|
|
44
|
+
from repowise.core.ingestion import ASTParser # noqa: PLC0415
|
|
45
|
+
_PARSER = ASTParser()
|
|
46
|
+
return True, ""
|
|
47
|
+
except Exception as exc:
|
|
48
|
+
_UNAVAILABLE = f"repowise unavailable ({type(exc).__name__})"
|
|
49
|
+
return False, _UNAVAILABLE
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def language_for(path: str) -> Optional[str]:
|
|
53
|
+
"""repowise's language for a path, or None when it does not handle it."""
|
|
54
|
+
ok, _ = available()
|
|
55
|
+
if not ok:
|
|
56
|
+
return None
|
|
57
|
+
try:
|
|
58
|
+
from repowise.core.ingestion import EXTENSION_TO_LANGUAGE # noqa: PLC0415
|
|
59
|
+
except Exception:
|
|
60
|
+
return None
|
|
61
|
+
_, _, ext = (path or "").rpartition(".")
|
|
62
|
+
return EXTENSION_TO_LANGUAGE.get("." + ext.lower()) if ext else None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def parse_symbols(content: bytes, path: str) -> List[dict]:
|
|
66
|
+
"""Symbols in `content` as awgit-shaped dicts. [] when it cannot parse.
|
|
67
|
+
|
|
68
|
+
Returns node_id/symbol/kind/start_line/end_line/language. The node_id is
|
|
69
|
+
repowise's own ``path::qualified_name``, NOT a hash of the body — that is
|
|
70
|
+
precisely why it survives a move, and why it can be compared across two
|
|
71
|
+
commits the way awgit's Python node ids are.
|
|
72
|
+
"""
|
|
73
|
+
ok, _ = available()
|
|
74
|
+
if not ok:
|
|
75
|
+
return []
|
|
76
|
+
lang = language_for(path)
|
|
77
|
+
if not lang:
|
|
78
|
+
return []
|
|
79
|
+
try:
|
|
80
|
+
from repowise.core.ingestion import FileInfo # noqa: PLC0415
|
|
81
|
+
|
|
82
|
+
info = FileInfo(
|
|
83
|
+
path=path, abs_path=path, language=lang, size_bytes=len(content),
|
|
84
|
+
git_hash="", last_modified=0.0, is_test=False, is_config=False,
|
|
85
|
+
is_api_contract=False, is_entry_point=False,
|
|
86
|
+
)
|
|
87
|
+
parsed = _PARSER.parse_file(info, content)
|
|
88
|
+
except Exception:
|
|
89
|
+
# A file repowise chokes on must degrade to "no symbols", never take
|
|
90
|
+
# the capture down: awgit records what it can see, and a parser crash
|
|
91
|
+
# on one file is not a reason to lose the whole commit's op.
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
out = []
|
|
95
|
+
for sym in (getattr(parsed, "symbols", None) or []):
|
|
96
|
+
nid = getattr(sym, "id", None)
|
|
97
|
+
if not nid:
|
|
98
|
+
continue
|
|
99
|
+
out.append({
|
|
100
|
+
"node_id": str(nid),
|
|
101
|
+
"symbol": getattr(sym, "qualified_name", None) or getattr(sym, "name", ""),
|
|
102
|
+
"kind": str(getattr(sym, "kind", "") or ""),
|
|
103
|
+
"start_line": getattr(sym, "start_line", 0),
|
|
104
|
+
"end_line": getattr(sym, "end_line", 0),
|
|
105
|
+
"language": lang,
|
|
106
|
+
})
|
|
107
|
+
return out
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awgit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
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
5
|
License: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Requires-Dist: httpx>=0.25.0
|
|
10
|
+
Provides-Extra: multilang
|
|
11
|
+
Requires-Dist: repowise>=0.31.0; extra == "multilang"
|
|
10
12
|
Dynamic: license-file
|
|
11
13
|
|
|
12
14
|
# awgit — Aither World-Graph git
|
|
@@ -8,6 +8,7 @@ awgit/capture.py
|
|
|
8
8
|
awgit/cli.py
|
|
9
9
|
awgit/data_root.py
|
|
10
10
|
awgit/diff.py
|
|
11
|
+
awgit/graph.py
|
|
11
12
|
awgit/identity.py
|
|
12
13
|
awgit/leases.py
|
|
13
14
|
awgit/ledger.py
|
|
@@ -16,6 +17,7 @@ awgit/merge.py
|
|
|
16
17
|
awgit/nodeid.py
|
|
17
18
|
awgit/oplog.py
|
|
18
19
|
awgit/parser.py
|
|
20
|
+
awgit/repowise_parser.py
|
|
19
21
|
awgit/schema.py
|
|
20
22
|
awgit/sync.py
|
|
21
23
|
awgit.egg-info/PKG-INFO
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "awgit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Aither World-Graph git — semantic version control on top of git: edit-ops keyed on stable node ids, content-addressed bodies, differential sync."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -13,6 +13,14 @@ dependencies = [
|
|
|
13
13
|
"httpx>=0.25.0",
|
|
14
14
|
]
|
|
15
15
|
|
|
16
|
+
# OPTIONAL on purpose. Python node identity is native (CPython's ast); repowise
|
|
17
|
+
# adds identity for the other 20+ languages it parses. Making it a hard
|
|
18
|
+
# dependency would drag a large third-party parser into every install of a tool
|
|
19
|
+
# whose core does not need it — and awgit must still import, and still guard
|
|
20
|
+
# Python, when it is absent (see awgit/repowise_parser.available()).
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
multilang = ["repowise>=0.31.0"]
|
|
23
|
+
|
|
16
24
|
[project.scripts]
|
|
17
25
|
awgit = "awgit.cli:main"
|
|
18
26
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
httpx>=0.25.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|