@ssheleg/agent-sync 1.3.1 → 1.3.3
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.
- package/CHANGELOG.md +46 -0
- package/README.md +7 -2
- package/package.json +2 -2
- package/plugins/agent-sync/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-sync/skills/agent-sync/SKILL.md +1 -1
- package/plugins/agent-sync/skills/agent-sync/scripts/__pycache__/agent_sync.cpython-311.pyc +0 -0
- package/plugins/agent-sync/skills/agent-sync/scripts/__pycache__/agent_sync.cpython-314.pyc +0 -0
- package/plugins/agent-sync/skills/agent-sync/scripts/__pycache__/agent_sync.cpython-39.pyc +0 -0
- package/plugins/agent-sync/skills/agent-sync/scripts/agent_sync.py +44 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,36 @@
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
This project adheres to [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## 1.3.3 — 2026-07-29
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- **The git lease backend could not take a lease on any machine without a git
|
|
10
|
+
identity.** Acquiring writes a lease object with `git commit-tree`, which
|
|
11
|
+
refuses when `user.email` is unset and cannot be auto-detected — CI runners,
|
|
12
|
+
containers, a freshly provisioned box. So the backend that advertises
|
|
13
|
+
*exclusive across machines* failed on precisely the machines least likely to
|
|
14
|
+
have a personal git config, with `could not create the lease object` and no
|
|
15
|
+
further detail. The lease object is plumbing, not authorship: it now carries a
|
|
16
|
+
fixed `agent-sync <agent-sync@localhost>` identity passed inline, so it never
|
|
17
|
+
depends on ambient config.
|
|
18
|
+
- The same failure now reports git's own last line instead of swallowing it. The
|
|
19
|
+
bug survived six red CI runs because the message named a possible cause and
|
|
20
|
+
showed no evidence.
|
|
21
|
+
|
|
22
|
+
## 1.3.2 — 2026-07-29
|
|
23
|
+
|
|
24
|
+
Open-source hygiene — the repo is public and ships in the `sshlg-skills` bundle,
|
|
25
|
+
so the files a first-time contributor looks for now exist.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- `CODE_OF_CONDUCT.md`, issue forms and a pull-request template. The forms ask
|
|
29
|
+
the question that actually matters for this project: **how many agents were
|
|
30
|
+
running, against what checkout** — a coordination bug reported without the
|
|
31
|
+
concurrency shape is not reproducible.
|
|
32
|
+
- The PR checklist requires a negative self-test with any new validator guard —
|
|
33
|
+
plant the defect, watch the check fail, then trust the green.
|
|
34
|
+
- README points at the code of conduct and at the family bundle.
|
|
35
|
+
|
|
6
36
|
## 1.3.1 — 2026-07-29
|
|
7
37
|
|
|
8
38
|
### The git lease was invisible to everything that reads a lease — fixed
|
|
@@ -24,6 +54,22 @@ already removed that note, which is why only one half of the loop was ever writt
|
|
|
24
54
|
now runs acquire → `whoami` → `guard` → release against **both** modes; against 1.3.0 it fails with
|
|
25
55
|
the two symptoms above, which is the point of adding it.
|
|
26
56
|
|
|
57
|
+
## 1.3.1 — 2026-07-29
|
|
58
|
+
|
|
59
|
+
### Ignoring the state directory does nothing once git is tracking it
|
|
60
|
+
|
|
61
|
+
Found in the project this plugin was built for: `.agent-sync/` was gitignored **and committed**,
|
|
62
|
+
because the files went in before the rule existed. Consequences, all of them silent:
|
|
63
|
+
|
|
64
|
+
- every tool call rewrites `last-renew`, so all three repositories were permanently dirty and no
|
|
65
|
+
run could ever report itself finished
|
|
66
|
+
- `run-id` is the checkout's **agent identity**. Committed, it reaches every clone — two machines
|
|
67
|
+
would then coordinate as one run, which is the failure 1.3.0 fixed at the other end
|
|
68
|
+
|
|
69
|
+
`init` now untracks the directory when it finds it tracked, and `check` reports it as a problem
|
|
70
|
+
rather than passing a project whose state is versioned. Probed: a repository with a committed
|
|
71
|
+
`.agent-sync/run-id` fails `check` with the exact removal command, and passes once it is untracked.
|
|
72
|
+
|
|
27
73
|
## 1.3.0 — 2026-07-29
|
|
28
74
|
|
|
29
75
|
### Two agents in one checkout were one identity — fixed
|
package/README.md
CHANGED
|
@@ -389,8 +389,13 @@ agent loads on their own trigger rather than by default:
|
|
|
389
389
|
| [`two-sources.md`](plugins/agent-sync/skills/agent-sync/references/two-sources.md) | before the first reconcile, or when deciding where a document belongs |
|
|
390
390
|
| [`roadmap.md`](plugins/agent-sync/skills/agent-sync/references/roadmap.md) | configuring `claimTags`, taking or closing a task, or re-planning a board |
|
|
391
391
|
|
|
392
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md).
|
|
393
|
-
|
|
392
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md). Everyone
|
|
393
|
+
taking part is expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
394
|
+
Security reports: [SECURITY.md](SECURITY.md).
|
|
395
|
+
|
|
396
|
+
`agent-sync` also ships in the [sshlg-skills](https://github.com/ssheleg/sshlg-skills)
|
|
397
|
+
bundle, which installs the whole family for Claude Code, Cursor, Codex and 70+
|
|
398
|
+
other agents with one command.
|
|
394
399
|
|
|
395
400
|
## License
|
|
396
401
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/agent-sync",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "Let concurrent coding agents share one project without colliding
|
|
3
|
+
"version": "1.3.3",
|
|
4
|
+
"description": "Let concurrent coding agents share one project without colliding — leases with TTL, race-free id reservation, a run journal and a generated board, over a pluggable knowledge cloud.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agent-sync": "bin/agent-sync.js"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sync",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Coordination layer for multi-agent repositories — leases with TTL, race-free ID reservation, a run journal, a cross-repo signal feed and a generated board, over a pluggable knowledge cloud.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "appvillis-com"
|
|
@@ -4,7 +4,7 @@ description: "Use when several coding agents work one repository at the same tim
|
|
|
4
4
|
compatibility: "Requires the task-pipeline skill for its stages (npx sshlg-skills install). Needs python3 3.9+ (stdlib only, HTTP included - nothing to pip install) and bash for the hooks. The knowledge backend is configured per project; with none configured it degrades to git-file leases. Enforcement hooks are Claude Code only - on other agents the same checks run as a self-check."
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
|
-
version: "1.3.
|
|
7
|
+
version: "1.3.3"
|
|
8
8
|
author: appvillis-com
|
|
9
9
|
---
|
|
10
10
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -32,7 +32,7 @@ from datetime import datetime, timezone
|
|
|
32
32
|
from pathlib import Path
|
|
33
33
|
from typing import Any
|
|
34
34
|
|
|
35
|
-
VERSION = "1.3.
|
|
35
|
+
VERSION = "1.3.3"
|
|
36
36
|
|
|
37
37
|
CONFIG_PATH = Path(".claude/agent-sync.json")
|
|
38
38
|
ENV_FILE = Path(".env.agent-sync")
|
|
@@ -813,10 +813,20 @@ class Sync:
|
|
|
813
813
|
payload = json.dumps({"run": self.rid, "ts": now_iso(), "ttl": self.ttl,
|
|
814
814
|
"repo": repo_name(), "host": os.uname().nodename})
|
|
815
815
|
empty_tree = git("hash-object", "-t", "tree", "/dev/null")
|
|
816
|
-
|
|
817
|
-
|
|
816
|
+
# A lease object is plumbing, not authorship, so it must not depend on the
|
|
817
|
+
# machine having a git identity. Without these `-c` flags `commit-tree`
|
|
818
|
+
# refuses wherever user.email is unset and cannot be auto-detected — CI
|
|
819
|
+
# runners, containers, a freshly provisioned box — and the lease backend
|
|
820
|
+
# silently becomes unusable on exactly the machines that need it most.
|
|
821
|
+
made = subprocess.run(
|
|
822
|
+
["git", "-c", "user.name=agent-sync", "-c", "user.email=agent-sync@localhost",
|
|
823
|
+
"commit-tree", empty_tree],
|
|
824
|
+
input=payload, capture_output=True, text=True)
|
|
825
|
+
commit = made.stdout.strip()
|
|
818
826
|
if not commit:
|
|
819
|
-
|
|
827
|
+
detail = (made.stderr or "").strip().splitlines()
|
|
828
|
+
why = detail[-1] if detail else "no output from git commit-tree"
|
|
829
|
+
raise Fail(f"could not create the lease object — is this a git repository? ({why})")
|
|
820
830
|
|
|
821
831
|
args = ["git", "push", remote, f"{commit}:{ref}"]
|
|
822
832
|
if held_sha: # stealing an expired lease, and only that
|
|
@@ -1778,6 +1788,7 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
1778
1788
|
|
|
1779
1789
|
ensure_gitignored(root, str(ENV_FILE))
|
|
1780
1790
|
ensure_gitignored(root, f"{STATE_DIR}/")
|
|
1791
|
+
ensure_untracked(root, f"{STATE_DIR}/")
|
|
1781
1792
|
|
|
1782
1793
|
print()
|
|
1783
1794
|
if backend == "outline":
|
|
@@ -1833,6 +1844,25 @@ def ensure_gitignored(root: Path, entry: str) -> None:
|
|
|
1833
1844
|
print(f"✓ added {entry} to .gitignore")
|
|
1834
1845
|
|
|
1835
1846
|
|
|
1847
|
+
def ensure_untracked(root: Path, entry: str) -> None:
|
|
1848
|
+
"""Ignoring a path does nothing once git is already tracking it.
|
|
1849
|
+
|
|
1850
|
+
Found in a real project: the state directory had been committed before the ignore rule
|
|
1851
|
+
existed, so `git status` reported it modified after **every** tool call — the repository was
|
|
1852
|
+
permanently dirty and no run could report itself finished. `run-id` is the worse half: it is
|
|
1853
|
+
this checkout's agent identity, and committed it reaches every clone, so two machines
|
|
1854
|
+
coordinate as one run.
|
|
1855
|
+
"""
|
|
1856
|
+
tracked = git("ls-files", "--", entry.rstrip("/"), cwd=root)
|
|
1857
|
+
if not tracked:
|
|
1858
|
+
return
|
|
1859
|
+
if git("rm", "-r", "--cached", "-q", "--", entry.rstrip("/"), cwd=root) is None:
|
|
1860
|
+
return
|
|
1861
|
+
n = len(tracked.split("\n"))
|
|
1862
|
+
print(f"✓ untracked {n} committed file(s) under {entry} — commit that removal; "
|
|
1863
|
+
"ignoring a tracked path has no effect")
|
|
1864
|
+
|
|
1865
|
+
|
|
1836
1866
|
# --------------------------------------------------------------------------- status
|
|
1837
1867
|
|
|
1838
1868
|
def cmd_status(_args: argparse.Namespace) -> int:
|
|
@@ -2714,6 +2744,16 @@ def cmd_check(_args: argparse.Namespace) -> int:
|
|
|
2714
2744
|
except Fail:
|
|
2715
2745
|
pass
|
|
2716
2746
|
|
|
2747
|
+
tracked_state = git("ls-files", "--", STATE_DIR)
|
|
2748
|
+
if tracked_state:
|
|
2749
|
+
problems.append(
|
|
2750
|
+
f"{STATE_DIR}/ is tracked by git ({len(tracked_state.split(chr(10)))} file(s)) — it is "
|
|
2751
|
+
"generated state, the repository is dirty after every tool call, and a committed "
|
|
2752
|
+
"run-id hands this checkout's identity to every clone. "
|
|
2753
|
+
f"Run: git rm -r --cached {STATE_DIR} && commit")
|
|
2754
|
+
else:
|
|
2755
|
+
ok.append(f"{STATE_DIR}/ is not tracked")
|
|
2756
|
+
|
|
2717
2757
|
for line in ok:
|
|
2718
2758
|
print(f" ✓ {line}")
|
|
2719
2759
|
for line in warn:
|