@ssheleg/agent-sync 1.2.2
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 +332 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/agent-sync.example.json +40 -0
- package/agent-sync.schema.json +144 -0
- package/bin/agent-sync.js +149 -0
- package/package.json +46 -0
- package/plugins/agent-sync/.claude-plugin/plugin.json +18 -0
- package/plugins/agent-sync/commands/agent-sync.md +16 -0
- package/plugins/agent-sync/hooks/_lib.sh +39 -0
- package/plugins/agent-sync/hooks/guard.sh +55 -0
- package/plugins/agent-sync/hooks/hooks.json +69 -0
- package/plugins/agent-sync/hooks/renew.sh +10 -0
- package/plugins/agent-sync/hooks/session-end.sh +14 -0
- package/plugins/agent-sync/hooks/session-start.sh +8 -0
- package/plugins/agent-sync/skills/agent-sync/SKILL.md +372 -0
- package/plugins/agent-sync/skills/agent-sync/references/adapter-contract.md +80 -0
- package/plugins/agent-sync/skills/agent-sync/references/backend-fs.md +57 -0
- package/plugins/agent-sync/skills/agent-sync/references/backend-outline.md +103 -0
- package/plugins/agent-sync/skills/agent-sync/references/hooks.md +99 -0
- package/plugins/agent-sync/skills/agent-sync/references/lease-protocol.md +145 -0
- package/plugins/agent-sync/skills/agent-sync/references/pipeline-binding.md +76 -0
- package/plugins/agent-sync/skills/agent-sync/references/roadmap.md +103 -0
- package/plugins/agent-sync/skills/agent-sync/references/two-sources.md +131 -0
- package/plugins/agent-sync/skills/agent-sync/scripts/agent_sync.py +2454 -0
package/README.md
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# agent-sync
|
|
2
|
+
|
|
3
|
+
[](https://github.com/appvillis-com/agent-sync/actions/workflows/validate.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@ssheleg/agent-sync)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**Several coding agents, one repository, no collisions — and each one can see what the
|
|
8
|
+
others are doing.**
|
|
9
|
+
|
|
10
|
+
`agent-sync` is an agent skill (plus a Claude Code plugin) that gives concurrent
|
|
11
|
+
coding agents a coordination plane: leases with a TTL, race-free id reservation, a run
|
|
12
|
+
journal, a cross-repo signal feed and a generated board.
|
|
13
|
+
|
|
14
|
+
- [The problem](#the-problem)
|
|
15
|
+
- [What you get](#what-you-get)
|
|
16
|
+
- [Requirements](#requirements)
|
|
17
|
+
- [Install](#install)
|
|
18
|
+
- [Update](#update)
|
|
19
|
+
- [Set up a project](#set-up-a-project)
|
|
20
|
+
- [Everyday use](#everyday-use)
|
|
21
|
+
- [Configuration](#configuration)
|
|
22
|
+
- [Backends](#backends)
|
|
23
|
+
- [Enforcement hooks](#enforcement-hooks)
|
|
24
|
+
- [Where it plugs into task-pipeline](#where-it-plugs-into-task-pipeline)
|
|
25
|
+
- [Limits, stated plainly](#limits-stated-plainly)
|
|
26
|
+
- [Troubleshooting](#troubleshooting)
|
|
27
|
+
- [Uninstall](#uninstall)
|
|
28
|
+
- [Develop and verify](#develop-and-verify)
|
|
29
|
+
|
|
30
|
+
## The problem
|
|
31
|
+
|
|
32
|
+
When more than one agent works a project at the same time, the coordination substrate
|
|
33
|
+
most teams already have — a decisions log, a roadmap, a board, per-repo task files —
|
|
34
|
+
stops being enough. Every one of those is a file edited by hand. That works for people
|
|
35
|
+
taking turns and fails for agents working at once:
|
|
36
|
+
|
|
37
|
+
| What goes wrong | Why |
|
|
38
|
+
|---|---|
|
|
39
|
+
| Two agents mint the same decision id | "Next free id" is a line in a file; reading it is not reserving it |
|
|
40
|
+
| A claim blocks a task forever | A role name is not a holder, and it has no expiry |
|
|
41
|
+
| Two agents start the same task | Git shows what was committed, never what is in flight |
|
|
42
|
+
| An agent is blocked but not informed | Knowing a task is taken is not knowing who has it or what they touch |
|
|
43
|
+
| Merge conflicts on every shared register | Everyone writes the same three files |
|
|
44
|
+
| A cross-repo dependency is never noticed | Filing one notifies nobody |
|
|
45
|
+
|
|
46
|
+
`agent-sync` closes exactly those, and nothing else.
|
|
47
|
+
|
|
48
|
+
## The idea
|
|
49
|
+
|
|
50
|
+
> **Git is the record plane. The cloud is the coordination plane.**
|
|
51
|
+
|
|
52
|
+
A fact that must survive is written to git first and referenced from the cloud. A fact
|
|
53
|
+
about *who is doing what right now* lives in the cloud and expires. No cloud object is
|
|
54
|
+
ever the only home of a durable fact, so your single-source-of-truth rules stay intact.
|
|
55
|
+
|
|
56
|
+
**The knowledge base never decides a lease.** It cannot: measured against a real
|
|
57
|
+
instance, twelve concurrent appends to one document returned twelve successes and left
|
|
58
|
+
three lines. Exclusion comes from something that genuinely has compare-and-swap — an
|
|
59
|
+
atomic file create on one machine (`leaseBackend: "local"`), or a pushed git ref across
|
|
60
|
+
machines (`leaseBackend: "git"`), where the remote's non-fast-forward rejection *is* the
|
|
61
|
+
CAS. Id reservation still replays the log, which is safe because allocation is positional
|
|
62
|
+
and every reader computes the same answer.
|
|
63
|
+
|
|
64
|
+
## What you get
|
|
65
|
+
|
|
66
|
+
- **Leases with a TTL** — claim a task, renew automatically, steal an expired one.
|
|
67
|
+
Exclusive across machines with `leaseBackend: "git"`; the tool always states which
|
|
68
|
+
guarantee you have rather than implying the stronger one.
|
|
69
|
+
- **The claim written through to the roadmap** — one row, one cell, refused on ambiguity,
|
|
70
|
+
and restored verbatim on release. Closing a task stays yours.
|
|
71
|
+
- **Awareness, not just exclusion** — `status` lists every *other* run's live holdings,
|
|
72
|
+
so an agent learns who holds a task and what they are touching, instead of only that
|
|
73
|
+
it is taken.
|
|
74
|
+
- **Race-free id reservation** — positional allocation over the log, so two agents
|
|
75
|
+
cannot be handed one number.
|
|
76
|
+
- **A run journal** — what each run did, with commits, gate results and evidence.
|
|
77
|
+
- **A cross-repo signal feed** — `filed → accepted → delivered → closed`. `status`
|
|
78
|
+
surfaces what landed since this run last looked, watermarked per run so it stays quiet
|
|
79
|
+
until something actually changes.
|
|
80
|
+
- **A generated board** — machine-written, commit-stamped, and it refuses to overwrite a
|
|
81
|
+
page a human took over.
|
|
82
|
+
- **Enforcement hooks** for Claude Code that deny an edit to a guarded register file, or
|
|
83
|
+
a commit staging one, without a live lease.
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
86
|
+
|
|
87
|
+
| Requirement | Why | Check |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| **python3 ≥ 3.9** | the coordinator is one stdlib-only script — HTTP included, nothing to `pip install` | `python3 --version` |
|
|
90
|
+
| **git** | the record plane, and the cross-machine lease store | `git --version` |
|
|
91
|
+
| **bash** | the four Claude Code hook scripts | `bash --version` |
|
|
92
|
+
| **Node ≥ 18** | only for the `npx @ssheleg/agent-sync` installer | `node --version` |
|
|
93
|
+
| **[task-pipeline](https://github.com/ssheleg/task-pipeline)** | `agent-sync` supplies stages, it does not define them — without it `status` prints one line and stops | `npx sshlg-skills install` |
|
|
94
|
+
| A knowledge-base instance *(optional)* | the shared record, awareness and board; without one the `fs` backend keeps leases but loses cross-agent visibility | — |
|
|
95
|
+
|
|
96
|
+
## Install
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx @ssheleg/agent-sync install
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Claude Code gets the plugin; every other agent gets the skill through the
|
|
103
|
+
[skills CLI](https://github.com/vercel-labs/skills). The duplicate plain copy in
|
|
104
|
+
`~/.claude/skills/` is pruned afterwards, because that shadow silently serves a stale
|
|
105
|
+
skill over the installed plugin — **one channel per agent** is the rule.
|
|
106
|
+
|
|
107
|
+
Restart Claude Code after installing, so it picks the plugin up.
|
|
108
|
+
|
|
109
|
+
<details>
|
|
110
|
+
<summary>Other install routes</summary>
|
|
111
|
+
|
|
112
|
+
Track `main` from GitHub instead of the npm release:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx github:appvillis-com/agent-sync install
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Claude Code only, no skills CLI:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npx @ssheleg/agent-sync install --claude-only
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Pick which agents the skills CLI installs for:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx @ssheleg/agent-sync install --agent cursor,codex
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Or add the plugin by hand — the full `<name>@<name>` form is required:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
claude plugin marketplace add appvillis-com/agent-sync && claude plugin install agent-sync@agent-sync
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
</details>
|
|
137
|
+
|
|
138
|
+
## Update
|
|
139
|
+
|
|
140
|
+
**agent-sync itself** — update every channel you installed, then restart Claude Code:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
claude plugin marketplace update agent-sync && claude plugin update agent-sync@agent-sync && npx --yes skills update agent-sync --global --yes
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Re-running the installer works too, but pin `@latest` or npx may serve you its cache:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npx @ssheleg/agent-sync@latest install
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Check what you are actually running — the plugin and the skill must report the same
|
|
153
|
+
version, and a mismatch means one channel is stale:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
claude plugin list | grep agent-sync
|
|
157
|
+
python3 ~/.claude/plugins/cache/*/agent-sync/*/skills/agent-sync/scripts/agent_sync.py --version
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Its dependencies** — `task-pipeline` (and the rest of the same family) come from one
|
|
161
|
+
installer, which also prunes the shadow copies:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npx sshlg-skills install
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Nothing else to update: the coordinator is stdlib-only python, and the npm package has
|
|
168
|
+
zero runtime dependencies.
|
|
169
|
+
|
|
170
|
+
## Set up a project
|
|
171
|
+
|
|
172
|
+
**Initialisation is the first command, and it asks a question rather than guessing.**
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
/agent-sync init
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The agent asks where coordination state should live — a knowledge cloud, or local files
|
|
179
|
+
— and, for the cloud, the instance URL. Then it writes two files:
|
|
180
|
+
|
|
181
|
+
| File | Holds | Committed? |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `.claude/agent-sync.json` | **shape** — backend, TTLs, guarded files, registers, gates | yes |
|
|
184
|
+
| `.env.agent-sync` | **identity** — instance URL, token, collection id | **no** — mode 600, added to `.gitignore` |
|
|
185
|
+
|
|
186
|
+
The token line is written **empty**. Creating the API token in your own instance and
|
|
187
|
+
pasting it into that line is your step, and it stays yours: the tool never asks for a
|
|
188
|
+
token in chat, never echoes one, and never passes one as a command-line argument.
|
|
189
|
+
|
|
190
|
+
Load the environment before running agents:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
set -a && . ./.env.agent-sync && set +a
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Then create the container the coordination log lives in, once per project, and paste the
|
|
197
|
+
id it prints into `AGENT_SYNC_OUTLINE_COLLECTION`:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
/agent-sync bootstrap
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Verify the setup — idempotent, repairs what is missing, and names exactly one next
|
|
204
|
+
action:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
/agent-sync status
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Everyday use
|
|
211
|
+
|
|
212
|
+
In an agent session you use the slash command (`/agent-sync claim ASC-072`); the same
|
|
213
|
+
commands run directly against the coordinator script, which is what the hooks and CI do:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
python3 "$SKILL_DIR/scripts/agent_sync.py" <command>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Command | Does |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `init` | **Run first.** Ask where state lives, write config + gitignored env file, print your step |
|
|
222
|
+
| `status` | Inspect, repair, report — including other runs' leases and signals new since you last looked |
|
|
223
|
+
| `bootstrap` | Create the cloud container and print the id to paste into the env file |
|
|
224
|
+
| `acquire <KEY>` | Take the lease on a task id. Prints `won`, or `lost <holder>` |
|
|
225
|
+
| `renew <KEY>` | Extend the lease. In Claude Code the `PostToolUse` hook does this for you |
|
|
226
|
+
| `release <KEY>` | Give the lease back. Always, including on failure |
|
|
227
|
+
| `reserve <REG>` | Reserve the next id in a register (`DEC`, `OQ`, `DEP`, …). Prints the id |
|
|
228
|
+
| `release-id <REG> <ID>` | Return an id you did not end up writing to git |
|
|
229
|
+
| `journal <text>` | Append one line to this run's journal |
|
|
230
|
+
| `signal <DEP-ID> <state>` | Move a cross-repo dependency: `filed`/`accepted`/`delivered`/`closed`/`refused` |
|
|
231
|
+
| `guard <path>` | May this run write that path? Exit 0 = yes, 2 = no |
|
|
232
|
+
| `board` | Regenerate the read-only board and the mirror from git |
|
|
233
|
+
| `whoami` | Print this run's id and its held leases |
|
|
234
|
+
|
|
235
|
+
The shape that matters:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
acquire → do the work → release
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Never skip `release`, including when the work failed. An abandoned lease blocks the task
|
|
242
|
+
until its TTL expires, and the next agent cannot tell "in progress" from "crashed an hour
|
|
243
|
+
ago". A lease is a promise to come back.
|
|
244
|
+
|
|
245
|
+
**The lease is not the claim.** The lease says who holds the task *now* and expires; the
|
|
246
|
+
durable claim is the tag in git (`[name]`, `todo (claimed: <role>)`). `acquire` writes
|
|
247
|
+
that tag through and `release` clears it, so one fact keeps one home.
|
|
248
|
+
|
|
249
|
+
## Configuration
|
|
250
|
+
|
|
251
|
+
`.claude/agent-sync.json` — committed, validated against
|
|
252
|
+
[`agent-sync.schema.json`](agent-sync.schema.json), starting point in
|
|
253
|
+
[`agent-sync.example.json`](agent-sync.example.json):
|
|
254
|
+
|
|
255
|
+
| Key | Meaning |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `backend` | `outline` or `fs` (required) |
|
|
258
|
+
| `leaseTtlSeconds` | how long a lease survives without a renew (default 2700) |
|
|
259
|
+
| `renewIntervalSeconds` | how often a live run renews (default 300) |
|
|
260
|
+
| `gated` | whether runs may be recorded as enforced at all |
|
|
261
|
+
| `idRegisters` | register → the git file that owns it, and its "next free id" pattern |
|
|
262
|
+
| `guardedFiles` | registry files no run may edit without a live lease |
|
|
263
|
+
| `claimTags` | file → the durable claim tag `acquire`/`release` writes through |
|
|
264
|
+
| `gates` | commands the pipeline stages run as gates |
|
|
265
|
+
| `mirror` | which git files are rendered into the read-only mirror |
|
|
266
|
+
|
|
267
|
+
`.env.agent-sync` — gitignored, mode 600:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
AGENT_SYNC_BACKEND=outline
|
|
271
|
+
AGENT_SYNC_OUTLINE_URL=https://<your-instance>
|
|
272
|
+
AGENT_SYNC_OUTLINE_TOKEN= # you fill this line, nobody else
|
|
273
|
+
AGENT_SYNC_OUTLINE_COLLECTION= # printed by `bootstrap`
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Never write a host name or a token into the config, a test, an example or a commit. If
|
|
277
|
+
an agent offers to handle a token for you, that is the wrong answer.
|
|
278
|
+
|
|
279
|
+
**A submodule's config declares only its own registers.** Cross-repository facts belong
|
|
280
|
+
to the parent repository; a service repo listing the parent's decision register is a
|
|
281
|
+
configuration defect.
|
|
282
|
+
|
|
283
|
+
## Backends
|
|
284
|
+
|
|
285
|
+
The knowledge store is a **pluggable adapter** — six primitives, three declared
|
|
286
|
+
capabilities. Nothing about a specific vendor is baked in, and no instance address ships
|
|
287
|
+
in this repository.
|
|
288
|
+
|
|
289
|
+
| Backend | Lease authority | Notes |
|
|
290
|
+
|---|---|---|
|
|
291
|
+
| `outline` | yes | [Outline](https://www.getoutline.com), hosted or self-hosted. Server-side append gives a total order without compare-and-swap |
|
|
292
|
+
| `fs` | no — **degraded** | Local files. Real mutual exclusion between agents on one machine, none across machines. Every run is recorded `ungated` |
|
|
293
|
+
|
|
294
|
+
**A backend that cannot arbitrate says so.** When the adapter is not the lease authority,
|
|
295
|
+
`agent-sync` announces it, falls back to git-file leases, and marks runs `ungated` —
|
|
296
|
+
because a lease that is not actually exclusive is worse than none, and the other agent
|
|
297
|
+
has stopped checking.
|
|
298
|
+
|
|
299
|
+
Adding one: read
|
|
300
|
+
[`references/adapter-contract.md`](plugins/agent-sync/skills/agent-sync/references/adapter-contract.md).
|
|
301
|
+
|
|
302
|
+
## Enforcement hooks
|
|
303
|
+
|
|
304
|
+
Installed with the Claude Code plugin. Every hook exits immediately in projects without
|
|
305
|
+
`.claude/agent-sync.json`, so installing globally changes nothing elsewhere.
|
|
306
|
+
|
|
307
|
+
| Hook | Runs | Effect |
|
|
308
|
+
|---|---|---|
|
|
309
|
+
| `SessionStart` | startup, resume | `status` — the board summary, other runs, one next action |
|
|
310
|
+
| `PreToolUse` | `Edit`/`Write`/`MultiEdit`/`NotebookEdit`, and `git commit` | Denies the edit (exit 2) when the path is guarded and this run holds no lease; a `git commit` is checked against every staged path |
|
|
311
|
+
| `PostToolUse` | every tool call | Throttled `renew` — touches the network at most once per `renewIntervalSeconds` |
|
|
312
|
+
| `SessionEnd` | session end | Releases every lease this run holds |
|
|
313
|
+
|
|
314
|
+
Details and removal:
|
|
315
|
+
[`references/hooks.md`](plugins/agent-sync/skills/agent-sync/references/hooks.md).
|
|
316
|
+
|
|
317
|
+
## Where it plugs into task-pipeline
|
|
318
|
+
|
|
319
|
+
`agent-sync` supplies stages; it does not define them. It binds to task-pipeline's
|
|
320
|
+
stages 0, 3, 4, 5, 9 and 10 — lease before the brief is committed, reserve ids before
|
|
321
|
+
they reach git, register file ownership for parallel groups, signal and regenerate the
|
|
322
|
+
board at docs, release everything at acceptance. Wiring:
|
|
323
|
+
[`references/pipeline-binding.md`](plugins/agent-sync/skills/agent-sync/references/pipeline-binding.md).
|
|
324
|
+
|
|
325
|
+
## Limits, stated plainly
|
|
326
|
+
|
|
327
|
+
- **Hooks are Claude Code only.** On Cursor, Codex and the rest there is no `PreToolUse`,
|
|
328
|
+
so nothing blocks a guarded edit; the same checks run as a self-check and the run is
|
|
329
|
+
recorded `ungated`. Read the board's column rather than assuming.
|
|
330
|
+
- **Ordering, not clocks.** Document order decides who holds a lease; timestamps only
|
|
331
|
+
expire one. Agents' clocks differ and the protocol does not depend on them.
|
|
332
|
+
- **A reserved id that never reaches git is reported, not reclaimed.** A half-written
|
|
333
|
+
decision on a branch is not an unused number.
|
|
334
|
+
- **`fs` is not cross-machine.** It is a real mutex between agents on one host and
|
|
335
|
+
nothing more, which is why it never claims lease authority.
|
|
336
|
+
|
|
337
|
+
## Troubleshooting
|
|
338
|
+
|
|
339
|
+
| Symptom | Cause and fix |
|
|
340
|
+
|---|---|
|
|
341
|
+
| `task-pipeline is not installed` and `status` stops | Intentional — there are no stages to bind to. `npx sshlg-skills install` |
|
|
342
|
+
| `⚠ ungated backend — this lease is advisory` | The `fs` backend, or missing credentials. Expected; configure `outline` for enforced leases |
|
|
343
|
+
| Every `acquire` reports `lost` | Check the holder in `status`. If the log itself is unreadable, `acquire` raises instead — that is a parse failure, not a race |
|
|
344
|
+
| Guarded edit blocked in Claude Code | Working as designed: `acquire` the key first, or unstage the file |
|
|
345
|
+
| Guarded edit *not* blocked | You are not on Claude Code. Run `guard <path>` yourself; the run is `ungated` |
|
|
346
|
+
| `AGENT_SYNC_OUTLINE_COLLECTION is not set` | Run `bootstrap` and paste the printed id into `.env.agent-sync` |
|
|
347
|
+
| An HTTP `400`/`403` from the backend | The response body is surfaced verbatim — read it; a bad collection id and a bad token look nothing alike |
|
|
348
|
+
| A stale skill after updating | Two channels serving one skill. Delete `~/.claude/skills/agent-sync` and keep the plugin |
|
|
349
|
+
| The board refuses to write | A human took the page over (no generated marker on line 1). Reported, never overwritten |
|
|
350
|
+
|
|
351
|
+
## Uninstall
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
claude plugin uninstall agent-sync@agent-sync
|
|
355
|
+
npx --yes skills remove agent-sync --global --yes
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Project files stay where they are; delete `.claude/agent-sync.json`, `.env.agent-sync`
|
|
359
|
+
and `.agent-sync/` if you want the project clean too.
|
|
360
|
+
|
|
361
|
+
## Develop and verify
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
python3 test/validate.py # manifests, version sync, no host/credential leaks
|
|
365
|
+
python3 test/validate.py --self-test # the validator must still be able to fail
|
|
366
|
+
npm test # both of the above
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
What ships: one skill (`agent-sync`), `scripts/agent_sync.py` (stdlib only), four hook
|
|
370
|
+
scripts, the slash command, `agent-sync.schema.json`, and eight reference contracts the
|
|
371
|
+
agent loads on their own trigger rather than by default:
|
|
372
|
+
|
|
373
|
+
| Reference | Read it when |
|
|
374
|
+
|---|---|
|
|
375
|
+
| [`adapter-contract.md`](plugins/agent-sync/skills/agent-sync/references/adapter-contract.md) | adding or auditing a knowledge backend |
|
|
376
|
+
| [`lease-protocol.md`](plugins/agent-sync/skills/agent-sync/references/lease-protocol.md) | changing acquisition, expiry, stealing or id allocation |
|
|
377
|
+
| [`backend-outline.md`](plugins/agent-sync/skills/agent-sync/references/backend-outline.md) | making any Outline API call, or debugging one |
|
|
378
|
+
| [`backend-fs.md`](plugins/agent-sync/skills/agent-sync/references/backend-fs.md) | running without a cloud backend, or explaining degraded mode |
|
|
379
|
+
| [`pipeline-binding.md`](plugins/agent-sync/skills/agent-sync/references/pipeline-binding.md) | wiring `pipeline.json`, or adding a stage hook |
|
|
380
|
+
| [`hooks.md`](plugins/agent-sync/skills/agent-sync/references/hooks.md) | installing, debugging or removing the Claude Code hooks |
|
|
381
|
+
| [`two-sources.md`](plugins/agent-sync/skills/agent-sync/references/two-sources.md) | before the first reconcile, or when deciding where a document belongs |
|
|
382
|
+
| [`roadmap.md`](plugins/agent-sync/skills/agent-sync/references/roadmap.md) | configuring `claimTags`, taking or closing a task, or re-planning a board |
|
|
383
|
+
|
|
384
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md). Security
|
|
385
|
+
reports: [SECURITY.md](SECURITY.md).
|
|
386
|
+
|
|
387
|
+
## License
|
|
388
|
+
|
|
389
|
+
MIT © Appvillis
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/appvillis-com/agent-sync/main/agent-sync.schema.json",
|
|
3
|
+
"backend": "outline",
|
|
4
|
+
"leaseTtlSeconds": 2700,
|
|
5
|
+
"renewIntervalSeconds": 300,
|
|
6
|
+
"gated": true,
|
|
7
|
+
"idRegisters": {
|
|
8
|
+
"DEC": {
|
|
9
|
+
"file": "docs/DECISIONS.md",
|
|
10
|
+
"nextFreeIdPattern": "\\*\\*Next free ID:\\*\\* `DEC-(\\d{4})`"
|
|
11
|
+
},
|
|
12
|
+
"OQ": {
|
|
13
|
+
"file": "docs/OPEN_QUESTIONS.md",
|
|
14
|
+
"nextFreeIdPattern": "\\*\\*Next free ID:\\*\\* `OQ-(\\d{4})`"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"guardedFiles": [
|
|
18
|
+
"docs/DECISIONS.md",
|
|
19
|
+
"docs/OPEN_QUESTIONS.md",
|
|
20
|
+
"docs/ROADMAP.md"
|
|
21
|
+
],
|
|
22
|
+
"claimTags": {
|
|
23
|
+
"docs/ROADMAP.md": {
|
|
24
|
+
"mode": "cell",
|
|
25
|
+
"cell": -1,
|
|
26
|
+
"held": "{prev} (claimed: {holder})"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"gates": [
|
|
30
|
+
"bash scripts/check-docs.sh"
|
|
31
|
+
],
|
|
32
|
+
"mirror": {
|
|
33
|
+
"enabled": true,
|
|
34
|
+
"sources": [
|
|
35
|
+
"docs/DECISIONS.md",
|
|
36
|
+
"docs/ROADMAP.md"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"leaseBackend": "git"
|
|
40
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/appvillis-com/agent-sync/main/agent-sync.schema.json",
|
|
4
|
+
"title": "agent-sync project configuration",
|
|
5
|
+
"description": "Shape only. Identity (instance URL, token, container id) lives in .env.agent-sync and is never committed.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"backend"
|
|
9
|
+
],
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": {
|
|
12
|
+
"$schema": {
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"backend": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"enum": [
|
|
18
|
+
"outline",
|
|
19
|
+
"fs"
|
|
20
|
+
],
|
|
21
|
+
"description": "Which adapter holds coordination state. 'fs' is degraded: not the lease authority across machines."
|
|
22
|
+
},
|
|
23
|
+
"leaseTtlSeconds": {
|
|
24
|
+
"type": "integer",
|
|
25
|
+
"minimum": 60,
|
|
26
|
+
"default": 2700
|
|
27
|
+
},
|
|
28
|
+
"renewIntervalSeconds": {
|
|
29
|
+
"type": "integer",
|
|
30
|
+
"minimum": 30,
|
|
31
|
+
"default": 300
|
|
32
|
+
},
|
|
33
|
+
"gated": {
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"default": true,
|
|
36
|
+
"description": "Whether runs may be reported as enforced. Effective only when the backend is the lease authority."
|
|
37
|
+
},
|
|
38
|
+
"idRegisters": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"description": "Registers whose next free id must be reserved before it is written to git.",
|
|
41
|
+
"additionalProperties": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": [
|
|
44
|
+
"file",
|
|
45
|
+
"nextFreeIdPattern"
|
|
46
|
+
],
|
|
47
|
+
"additionalProperties": false,
|
|
48
|
+
"properties": {
|
|
49
|
+
"file": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Repo-relative path to the register file."
|
|
52
|
+
},
|
|
53
|
+
"nextFreeIdPattern": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "Regex with one capture group holding the next free number."
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"guardedFiles": {
|
|
61
|
+
"type": "array",
|
|
62
|
+
"items": {
|
|
63
|
+
"type": "string"
|
|
64
|
+
},
|
|
65
|
+
"description": "Glob patterns that require a live lease before being written. A submodule lists only its own registers; cross-repository files belong to the parent repository."
|
|
66
|
+
},
|
|
67
|
+
"claimTags": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"description": "How a lease is written through to the roadmap. See references/roadmap.md.",
|
|
70
|
+
"additionalProperties": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"required": [
|
|
73
|
+
"mode"
|
|
74
|
+
],
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"properties": {
|
|
77
|
+
"mode": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"enum": [
|
|
80
|
+
"cell"
|
|
81
|
+
],
|
|
82
|
+
"description": "'cell' edits one cell of the single table row containing the task id."
|
|
83
|
+
},
|
|
84
|
+
"cell": {
|
|
85
|
+
"type": "integer",
|
|
86
|
+
"description": "0-based cell index in that row; negative counts from the end."
|
|
87
|
+
},
|
|
88
|
+
"held": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"description": "Template for the held value. {prev} is the current cell text, {holder} the run id."
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"gates": {
|
|
96
|
+
"type": "array",
|
|
97
|
+
"items": {
|
|
98
|
+
"type": "string"
|
|
99
|
+
},
|
|
100
|
+
"description": "Shell commands run at the pipeline's lint stage; each result is journaled."
|
|
101
|
+
},
|
|
102
|
+
"mirror": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"properties": {
|
|
106
|
+
"enabled": {
|
|
107
|
+
"type": "boolean",
|
|
108
|
+
"default": false
|
|
109
|
+
},
|
|
110
|
+
"sources": {
|
|
111
|
+
"type": "array",
|
|
112
|
+
"items": {
|
|
113
|
+
"type": "string"
|
|
114
|
+
},
|
|
115
|
+
"description": "Files or directories rendered one-way into the knowledge store, stamped with the source commit."
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"setupFile": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Where the generated setup snapshot is written. Defaults to docs/AGENT_SYNC.md when docs/ exists, else AGENT_SYNC.md."
|
|
122
|
+
},
|
|
123
|
+
"leaseBackend": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"enum": [
|
|
126
|
+
"local",
|
|
127
|
+
"git"
|
|
128
|
+
],
|
|
129
|
+
"default": "local",
|
|
130
|
+
"description": "Where a contended lease is decided. 'local' is an atomic file create \u2014 exclusive between processes on one filesystem. 'git' pushes a ref, and the remote's non-fast-forward rule is a real compare-and-swap, exclusive across machines."
|
|
131
|
+
},
|
|
132
|
+
"leaseRemote": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"default": "origin",
|
|
135
|
+
"description": "Git remote holding the lease refs when leaseBackend is 'git'."
|
|
136
|
+
},
|
|
137
|
+
"settleSeconds": {
|
|
138
|
+
"type": "number",
|
|
139
|
+
"minimum": 0,
|
|
140
|
+
"default": 3.0,
|
|
141
|
+
"description": "Unused by the git and local backends; retained for adapters that must wait for writes to become visible."
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|