synartesis 0.8.0 → 0.8.4
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 +176 -0
- package/README.md +195 -17
- package/SECURITY.md +2 -2
- package/dist/{chunk-O3QDPYEL.js → chunk-FIOKOIT6.js} +5 -1
- package/dist/cli.js +21 -20
- package/dist/proxy.js +1 -1
- package/manifests/git.yaml +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,182 @@
|
|
|
2
2
|
|
|
3
3
|
What changed, and why it mattered. Dates are release dates.
|
|
4
4
|
|
|
5
|
+
## 0.8.4 — 2026-09-21
|
|
6
|
+
|
|
7
|
+
Bugs, all of them found by using the thing rather than by a test going red:
|
|
8
|
+
the suite was green before this and is green after it.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **A server that was briefly unreachable no longer turns an ordinary undo
|
|
13
|
+
into one that needs `--force`.** The drift check reads the resource as it is
|
|
14
|
+
now, and when that read failed the action was written back as
|
|
15
|
+
`unrecoverable`. Nothing had been learned about the resource -- the read
|
|
16
|
+
never happened -- but the row now said otherwise, and the next plain
|
|
17
|
+
`undo` refused with "halted here on an earlier attempt" and offered
|
|
18
|
+
`--replan` or `--force`. So a server that was down for ten seconds escalated
|
|
19
|
+
an undo that would have worked into one that writes over whatever anybody
|
|
20
|
+
else had changed since.
|
|
21
|
+
|
|
22
|
+
The same write cost a second thing. A row that had already halted on real
|
|
23
|
+
drift carried the diff of what somebody had changed, which is the evidence
|
|
24
|
+
the person deciding is shown; retrying while the server was unreachable
|
|
25
|
+
replaced it with `server fs is not connected`. The halt then offered its
|
|
26
|
+
three-way choice with a transport error where the conflict used to be. The
|
|
27
|
+
read failure is now reported and not recorded, for the reason an unknown
|
|
28
|
+
outcome has never been recorded either: a rollback may not write down as
|
|
29
|
+
fact a thing it did not observe.
|
|
30
|
+
|
|
31
|
+
- **The way past a drift halt no longer sends you round in a circle.** It
|
|
32
|
+
printed `put it back as the run left it: synartesis undo <id> --replan`,
|
|
33
|
+
which reads as though the flag does the putting back. It does not: it
|
|
34
|
+
rebuilds each inverse from the current manifest, and the drift check then
|
|
35
|
+
runs again on a resource nobody has touched. Somebody who typed what that
|
|
36
|
+
line told them to got the identical halt, offering the identical three
|
|
37
|
+
options, including the one they had just chosen, for as long as they cared
|
|
38
|
+
to keep typing it. The restoring is the reader's half, as `--help` has
|
|
39
|
+
always said, and the line now says so: `put the resource back, then:`.
|
|
40
|
+
|
|
41
|
+
While there: the three options were spaced by hand and the middle one sat a
|
|
42
|
+
character left of the other two, which is enough to stop a menu reading as
|
|
43
|
+
a menu. They are padded to a column now, and a test fails if they drift
|
|
44
|
+
apart again.
|
|
45
|
+
|
|
46
|
+
- **`--to` past the end of a run answers in one line.** The message was
|
|
47
|
+
already exact -- `--to 99 is past the end of this run, which goes up to 1`
|
|
48
|
+
-- and was followed by five lines of unrelated commands. The bound in that
|
|
49
|
+
sentence was read off the journal, which is precisely the case the
|
|
50
|
+
command list was already meant to stay out of.
|
|
51
|
+
|
|
52
|
+
- **Counts agree with their nouns.** `1 runs and 1 actions removed`,
|
|
53
|
+
`1 actions: 1 gated`. Three places counted the same way and each had its own
|
|
54
|
+
copy of the arithmetic; there is one now, in `style.ts`, beside the other
|
|
55
|
+
decisions about how text is set.
|
|
56
|
+
|
|
57
|
+
- **The repository's social preview card is the whole card.** Every other
|
|
58
|
+
source in `brand/` states its size at 1x and gets its sharpness from the
|
|
59
|
+
shoot's scale factor; this one was written at 2560x1280 and shot at
|
|
60
|
+
1280x640, so what GitHub served when the repository was pasted into Slack
|
|
61
|
+
or a message was the top-left quarter of itself: three quarters empty
|
|
62
|
+
maroon with the tagline cropped off the bottom edge. It was also the only
|
|
63
|
+
asset here that nothing said how to rebuild, which is how it drifted.
|
|
64
|
+
`brand/README.md` now carries its command with the rest.
|
|
65
|
+
|
|
66
|
+
- **`og:image` states its dimensions.** LinkedIn and Slack lay a link card out
|
|
67
|
+
before the image arrives and held a grey box of the wrong shape until it
|
|
68
|
+
did.
|
|
69
|
+
|
|
70
|
+
## 0.8.3 — 2026-09-20
|
|
71
|
+
|
|
72
|
+
Documentation, brand and repository furniture. No code changed, and the
|
|
73
|
+
published package behaves exactly as 0.8.2 did.
|
|
74
|
+
|
|
75
|
+
0.8.2 was tagged and never published to npm; this goes out in its place rather
|
|
76
|
+
than publishing a tarball whose README differs from what its own tag points
|
|
77
|
+
at.
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- **Two drawings that move.** The README could say the value is kept before
|
|
82
|
+
the write and could show the result afterwards, and had no way to show the
|
|
83
|
+
sequence in between — which is the whole design. `synartesis-path-loop.svg`
|
|
84
|
+
draws where the proxy sits, which the README had described in prose and
|
|
85
|
+
never drawn at all; `synartesis-undo-loop.svg` is the three phases of an
|
|
86
|
+
undo. Hand-written SVG rather than GIF: two hundred times less weight and no
|
|
87
|
+
fuzziness on a retina screen, at the cost of the brand faces, since a
|
|
88
|
+
browser gives an SVG inside an `<img>` no network and blocks the webfont
|
|
89
|
+
import. Both carry their own ground so they read in either of GitHub's
|
|
90
|
+
themes, and both answer `prefers-reduced-motion` by settling on the end
|
|
91
|
+
state rather than disappearing.
|
|
92
|
+
|
|
93
|
+
- **A wiki, and a sponsor button.** Four pages that deliberately do not repeat
|
|
94
|
+
the documentation — a policy cookbook whose every example was run through
|
|
95
|
+
the real manifest loader first, troubleshooting organised by the symptom in
|
|
96
|
+
front of you, an FAQ, and a home page saying what lives where. Discussions
|
|
97
|
+
opened with a thread saying what is actually proven. Ko-fi produces the
|
|
98
|
+
button today; `github:` is in `FUNDING.yml` and starts working the moment
|
|
99
|
+
that account is enrolled.
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
|
|
103
|
+
- **The README's navigation is buttons rather than blue text.** The top was a
|
|
104
|
+
hand-written contents bar of eleven in-page anchors, most of which
|
|
105
|
+
duplicated GitHub's own table-of-contents button. One row of six now points
|
|
106
|
+
at the places somebody actually leaves for, with the status badges at half
|
|
107
|
+
the size underneath. Two details that had made the row look accidental: the
|
|
108
|
+
Actions badge is a different height from a flat-square row, and shields
|
|
109
|
+
picks text colour by background brightness, which had put white on the one
|
|
110
|
+
button meant to stand out.
|
|
111
|
+
|
|
112
|
+
## 0.8.2 — 2026-09-20
|
|
113
|
+
|
|
114
|
+
### Added
|
|
115
|
+
|
|
116
|
+
- **The git policy's undo is proven, not just plausible.** `check` starts the
|
|
117
|
+
server and confirms every tool the policy names exists, which proves the
|
|
118
|
+
policy loads and proves nothing about recovery: a compensation can resolve to
|
|
119
|
+
a real tool, take the arguments that tool really wants, put nothing back, and
|
|
120
|
+
report `rolled_back`. So `tests/adapter-git.test.ts` stages real changes in a
|
|
121
|
+
real repository, undoes them, and reads the index back with git itself rather
|
|
122
|
+
than through the server being tested.
|
|
123
|
+
|
|
124
|
+
It is a narrower claim than the filesystem and memory files make, and that is
|
|
125
|
+
the policy's shape rather than the test's. This server exposes nothing that
|
|
126
|
+
restores content — nearly every read it offers answers in prose written for a
|
|
127
|
+
person, so almost nothing can be inverted from a captured state — which
|
|
128
|
+
leaves exactly one compensation to prove. Proven: staging comes back off the
|
|
129
|
+
index and the working tree is untouched; a commit is held rather than
|
|
130
|
+
approximated; a branch switch goes through and undo reports it as permanent
|
|
131
|
+
instead of claiming to have reversed it.
|
|
132
|
+
|
|
133
|
+
**The overreach is pinned too.** This server's `git_reset` unstages
|
|
134
|
+
everything, not only what the call staged, so undoing the agent's `git_add`
|
|
135
|
+
also unstages work a person staged by hand. Nothing is lost — both edits are
|
|
136
|
+
still in the working tree — but it is a compensation and not an undo, and the
|
|
137
|
+
test now fails if the policy is ever changed to imply otherwise. Every guard
|
|
138
|
+
in the file was broken in turn to confirm it bites.
|
|
139
|
+
|
|
140
|
+
CI installs `uv`, because mcp-server-git is a Python package. Without it that
|
|
141
|
+
file fails rather than skipping, deliberately: a proof that quietly does not
|
|
142
|
+
run leaves the suite green and the claim unsupported.
|
|
143
|
+
|
|
144
|
+
### Changed
|
|
145
|
+
|
|
146
|
+
- The README, the user guide and the site said git was "checked only for tool
|
|
147
|
+
existence". That was true when written and is not now, so all three say what
|
|
148
|
+
is actually proven and what still is not. **github** remains untested.
|
|
149
|
+
|
|
150
|
+
## 0.8.1 — 2026-09-19
|
|
151
|
+
|
|
152
|
+
Documentation only. No code changed, and the published package behaves exactly
|
|
153
|
+
as 0.8.0 did.
|
|
154
|
+
|
|
155
|
+
### Changed
|
|
156
|
+
|
|
157
|
+
- **The README is laid out as a page rather than a document.** Somebody
|
|
158
|
+
arriving from a link wants to know within one screen what this is, that it
|
|
159
|
+
works, and which of the four ways in is theirs. None of that was visible
|
|
160
|
+
without scrolling past four screens of prose. The top is now a centred hero,
|
|
161
|
+
a badge row and a contents bar, then **Choose your path** — four cards, one
|
|
162
|
+
per way in, each carrying the command that starts it — and a short list of
|
|
163
|
+
first results that take the least time.
|
|
164
|
+
|
|
165
|
+
- **Two things came out of hiding, in the README and on the site.** The
|
|
166
|
+
interactive screen was a footnote inside Install despite being the interface
|
|
167
|
+
most people will use, and there was nothing anywhere about writing a policy
|
|
168
|
+
for a server that does not ship with one — which is the case for every server
|
|
169
|
+
but four, and the contribution most worth asking for. Both are now sections
|
|
170
|
+
of their own: `synartesis.online/install.html#write-a-policy` covers `init`
|
|
171
|
+
drafting fail-closed and `check` refusing a policy that names a tool the
|
|
172
|
+
server no longer has.
|
|
173
|
+
|
|
174
|
+
- **A contributing section, and the star asked for out loud.** The README
|
|
175
|
+
explained the tool thoroughly and then stopped, with no issue link and
|
|
176
|
+
nothing a reader who agreed with it could do next.
|
|
177
|
+
|
|
178
|
+
- `SECURITY.md` said the 0.6.x line was the supported one, two minors after it
|
|
179
|
+
stopped being.
|
|
180
|
+
|
|
5
181
|
## 0.8.0 — 2026-09-16
|
|
6
182
|
|
|
7
183
|
### Added
|
package/README.md
CHANGED
|
@@ -1,29 +1,75 @@
|
|
|
1
1
|
<!-- Absolute URLs, not relative paths: this README is also the npm package
|
|
2
2
|
page, and npm does not resolve relative image paths against the repo. -->
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
3
5
|
[](https://synartesis.online)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
**Give AI agents a way back.**
|
|
8
|
+
Synartesis sits between your MCP client and the servers it talks to, records
|
|
9
|
+
every tool call with the state that call replaced, and can put that state back.
|
|
10
|
+
What cannot be put back, it refuses to let an agent do unsupervised.
|
|
11
|
+
|
|
12
|
+
[](#install) [](#the-desktop-window) [](docs/synartesis-user-guide.md) [](https://github.com/ArhaanDev24/Synartesis/wiki) [](https://github.com/ArhaanDev24/Synartesis/discussions) [](https://github.com/ArhaanDev24/Synartesis)
|
|
13
|
+
|
|
14
|
+
[](https://github.com/ArhaanDev24/Synartesis/actions/workflows/check.yml) [](https://www.npmjs.com/package/synartesis) [](https://www.npmjs.com/package/synartesis) [](https://nodejs.org) [](LICENSE) [](https://synartesis.online)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
10
17
|
|
|
11
18
|
An agent with write access to a real system runs twenty steps, misreads step
|
|
12
19
|
seven, and applies the rest to the wrong records. Today your options are to
|
|
13
20
|
reverse it by hand from the transcript, restore a backup and lose every
|
|
14
21
|
legitimate change made in the same window, or accept the damage.
|
|
15
22
|
|
|
16
|
-
Synartesis sits between your MCP client and the servers it talks to. It records
|
|
17
|
-
every tool call with the state that call replaced, and it can put that state
|
|
18
|
-
back. What cannot be put back, it refuses to let an agent do unsupervised.
|
|
19
|
-
|
|
20
23
|
It is not a sandbox: the container your agent runs in is disposable, but the
|
|
21
24
|
CRM row it updated over the network is not. It is not a tracing tool: a trace
|
|
22
25
|
tells you `update_customer` ran forty times, not what the values were before.
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
**If this is a problem you have, [a star](https://github.com/ArhaanDev24/Synartesis) helps other people find it.**
|
|
30
|
+
It is a young project, and that is most of how anybody learns it exists.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Choose your path
|
|
35
|
+
|
|
36
|
+
<table>
|
|
37
|
+
<tr>
|
|
38
|
+
<td width="50%"><a href="#install"><img src="https://raw.githubusercontent.com/ArhaanDev24/Synartesis/main/brand/synartesis-card-install.png" alt="Cover what you already have — one command, every client. Finds what Claude Code, Claude Desktop, Cursor and Codex already list and points every entry at the proxy. $ synartesis install"></a></td>
|
|
39
|
+
<td width="50%"><a href="#the-screen"><img src="https://raw.githubusercontent.com/ArhaanDev24/Synartesis/main/brand/synartesis-card-screen.png" alt="The screen — everything, on the arrow keys. What your agents have done, what is held for approval, and the undo for any of it. $ synartesis"></a></td>
|
|
40
|
+
</tr>
|
|
41
|
+
<tr>
|
|
42
|
+
<td width="50%"><a href="#the-desktop-window"><img src="https://raw.githubusercontent.com/ArhaanDev24/Synartesis/main/brand/synartesis-card-desktop.png" alt="The desktop window — the same engine, with a conversation. Talk to any model. Every tool it calls goes out through the proxy on its way. $ synartesis desktop"></a></td>
|
|
43
|
+
<td width="50%"><a href="#writing-a-policy-for-your-own-server"><img src="https://raw.githubusercontent.com/ArhaanDev24/Synartesis/main/brand/synartesis-card-policy.png" alt="Your own server — four ship with a policy, hundreds do not. Introspect a server, draft a manifest from it, then correct it where you know better. $ synartesis init crm -- ./crm-mcp"></a></td>
|
|
44
|
+
</tr>
|
|
45
|
+
</table>
|
|
46
|
+
|
|
47
|
+
**Your first result**, in the order they take the least time:
|
|
48
|
+
|
|
49
|
+
- **Undo a real write:** [run the filesystem demo](demo/filesystem-demo.sh) — it
|
|
50
|
+
makes a file, has an agent overwrite it, and puts it back, in about a minute
|
|
51
|
+
and without touching anything of yours.
|
|
52
|
+
- **Cover the agents on this machine:** [`synartesis install`](#install), then
|
|
53
|
+
`synartesis status` to see what is now covered and what is not.
|
|
54
|
+
- **Watch one happen:** [`synartesis watch`](#commands) prints each call as it
|
|
55
|
+
goes out, and lets you answer an approval where you are standing.
|
|
56
|
+
- **Ask what an agent already did:** [`synartesis show <id> --live`](#has-anybody-touched-it-since)
|
|
57
|
+
reads every resource a session touched, as it is now, and says which still match.
|
|
58
|
+
|
|
59
|
+
Nothing above sends anything anywhere. The journal is a SQLite file on your
|
|
60
|
+
machine, and the proxy talks only to the servers your policy names.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## See it in action
|
|
65
|
+
|
|
66
|
+
**The order is the whole design.** The value is kept *before* the write, which
|
|
67
|
+
is what makes putting it back possible at all — and it is the one thing a
|
|
68
|
+
screenshot cannot show:
|
|
25
69
|
|
|
26
|
-
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
Both shots below are real output from [`./demo/filesystem-demo.sh`](demo/filesystem-demo.sh),
|
|
27
73
|
pasted rather than typeset. An agent overwrote a file and tried to move another.
|
|
28
74
|
One command puts the first back and reports that the second never happened:
|
|
29
75
|
|
|
@@ -41,6 +87,9 @@ It stops at the record that moved and exits non-zero. Anything already put back
|
|
|
41
87
|
stays put back, and it prints the three ways on: leave it, restore the resource
|
|
42
88
|
and `--replan`, or `--force` to overwrite deliberately.
|
|
43
89
|
|
|
90
|
+
**Next:** [the desktop window](#the-desktop-window) · [what it can and cannot do](#what-it-can-and-cannot-do) · [what it does not do](#what-it-does-not-do) · [the full guide](docs/synartesis-user-guide.md)
|
|
91
|
+
|
|
92
|
+
---
|
|
44
93
|
## Install
|
|
45
94
|
|
|
46
95
|
```bash
|
|
@@ -67,6 +116,12 @@ installed.**
|
|
|
67
116
|
Needs Node 22 or newer. npm ships a prebuilt SQLite binding, so no toolchain is
|
|
68
117
|
required unless you build from a clone.
|
|
69
118
|
|
|
119
|
+
**Setting it up by hand:** [the walkthrough](docs/synartesis-user-guide.md#install-it) · [Claude Desktop](docs/synartesis-user-guide.md#claude-desktop) · [Claude Code](docs/synartesis-user-guide.md#claude-code) · [any other client](docs/synartesis-user-guide.md#any-other-mcp-client)
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## The screen
|
|
124
|
+
|
|
70
125
|
Then just:
|
|
71
126
|
|
|
72
127
|
```bash
|
|
@@ -74,7 +129,17 @@ synartesis
|
|
|
74
129
|
```
|
|
75
130
|
|
|
76
131
|
One screen: what agents have done, what is held for approval, every AI on the
|
|
77
|
-
machine, and undo — all on the arrow keys.
|
|
132
|
+
machine, and undo — all on the arrow keys. `enter` opens a session, `u` undoes
|
|
133
|
+
it, `p` previews that undo without running it, `l` reads the world as it is now,
|
|
134
|
+
`f` expands every argument, `c` lists every AI on the machine, `g` shows what is
|
|
135
|
+
held. Nothing in it is a second implementation: it is the same journal and the
|
|
136
|
+
same planner the [commands](#commands) below use, so a session undone in the
|
|
137
|
+
screen and one undone in a script end the same way.
|
|
138
|
+
|
|
139
|
+
There is no mode in it that acts without telling you what it is about to do. An
|
|
140
|
+
undo shows you the plan first, and a held call shows you why it is held.
|
|
141
|
+
|
|
142
|
+
---
|
|
78
143
|
|
|
79
144
|
## The desktop window
|
|
80
145
|
|
|
@@ -143,6 +208,8 @@ want. The `release` workflow builds all three platforms on their own machines
|
|
|
143
208
|
and attaches the installers to the release for a tag. Both the window and the terminal share
|
|
144
209
|
one journal, so either can undo what the other did.
|
|
145
210
|
|
|
211
|
+
---
|
|
212
|
+
|
|
146
213
|
## What it can and cannot do
|
|
147
214
|
|
|
148
215
|
Every tool gets one of four classifications, written down in a manifest:
|
|
@@ -166,6 +233,45 @@ two: finding nothing is the reversible case, finding something is held for a
|
|
|
166
233
|
person and recorded with no inverse, so undo says it cannot be undone rather
|
|
167
234
|
than putting half of it back and calling that success.
|
|
168
235
|
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Writing a policy for your own server
|
|
239
|
+
|
|
240
|
+
Four servers ship with a policy. For anything else — a database, a ticketing
|
|
241
|
+
system, the MCP server you wrote last week — start by asking the server what it
|
|
242
|
+
has:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
synartesis init crm -- ./crm-mcp
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
That connects, lists every tool, and writes a manifest drafted from what the
|
|
249
|
+
server says about itself. If it recognises the server as one that already ships
|
|
250
|
+
with a policy, it adopts that one instead — but only when every tool the policy
|
|
251
|
+
calls is actually there, because a policy whose inverses cannot be called is
|
|
252
|
+
worse than a file of TODOs: it looks finished.
|
|
253
|
+
|
|
254
|
+
Drafted, not decided. A tool the server marks read-only is written `readonly`
|
|
255
|
+
with a comment telling you to check, since that hint is a statement of intent
|
|
256
|
+
and not a guarantee; everything else lands `irreversible` and gated. The draft
|
|
257
|
+
is fail-closed on purpose, and you open it up one tool at a time as you work
|
|
258
|
+
out how each of them is undone.
|
|
259
|
+
|
|
260
|
+
Then check it against the running server:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
synartesis check
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
It loads the manifest, connects to every server it names, and says which tools
|
|
267
|
+
your policy covers, which it does not, and where the two disagree. A policy
|
|
268
|
+
that names a tool the server no longer has is an error, not a warning.
|
|
269
|
+
|
|
270
|
+
**Going further:** [writing a manifest](docs/synartesis-user-guide.md#open-the-file) · [the four classes](docs/synartesis-user-guide.md#the-four-classes) · [checking it](docs/synartesis-user-guide.md#check-that-it-worked) · [pinning a tool's shape](#when-the-server-changes-underneath-you) · [the cookbook](https://github.com/ArhaanDev24/Synartesis/wiki/Policy-cookbook)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
169
275
|
## Has anybody touched it since?
|
|
170
276
|
|
|
171
277
|
Synartesis records what an agent does, not what happens to a file. Nothing you
|
|
@@ -187,6 +293,8 @@ answers. `l` in the screen does the same.
|
|
|
187
293
|
If you decide the recorded value is the one worth keeping, `undo --force` prints
|
|
188
294
|
every line it would write over and stops; `--force --yes` goes ahead.
|
|
189
295
|
|
|
296
|
+
---
|
|
297
|
+
|
|
190
298
|
## What each policy has actually been tested against
|
|
191
299
|
|
|
192
300
|
A policy that has met a real server and one written from its documentation are
|
|
@@ -212,6 +320,8 @@ wrote yourself: the tool has no business grading your work. Nothing is inferred
|
|
|
212
320
|
from silence, and all three states are printed, because if silence meant "fine"
|
|
213
321
|
then an ungraded policy and a known-untested one would look identical.
|
|
214
322
|
|
|
323
|
+
---
|
|
324
|
+
|
|
215
325
|
## Undoing something that was never read first
|
|
216
326
|
|
|
217
327
|
Most undo rides on a pre-read: the value before the write is captured, and
|
|
@@ -245,6 +355,8 @@ It is consulted only where there is no read already, so it can never displace a
|
|
|
245
355
|
working pre-read with a differently shaped one — which would make the post-state
|
|
246
356
|
and the snapshot incomparable and every later comparison meaningless.
|
|
247
357
|
|
|
358
|
+
---
|
|
359
|
+
|
|
248
360
|
## When the server changes underneath you
|
|
249
361
|
|
|
250
362
|
A policy is a claim about what a tool does, and a tool's name is a weak place to
|
|
@@ -284,6 +396,8 @@ reads as protected and is not. Tools that no policy matches need no pin: they ar
|
|
|
284
396
|
already fail-closed as irreversible and gated, so there is no classification for a
|
|
285
397
|
schema change to corrupt.
|
|
286
398
|
|
|
399
|
+
---
|
|
400
|
+
|
|
287
401
|
## Commands
|
|
288
402
|
|
|
289
403
|
`synartesis desktop` opens [the window](#the-desktop-window), and says where to
|
|
@@ -309,8 +423,8 @@ get it if it is not installed.
|
|
|
309
423
|
| `prune` | Delete sessions older than 30 days and reclaim the space |
|
|
310
424
|
| `close [id]` | End a session a killed proxy left open |
|
|
311
425
|
|
|
312
|
-
|
|
313
|
-
|
|
426
|
+
Every one of those can also be done from [the screen](#the-screen), on the
|
|
427
|
+
arrow keys.
|
|
314
428
|
|
|
315
429
|
`--manifest` and `--journal` are found rather than typed, from the current
|
|
316
430
|
directory upwards the way a version control tool finds its root, then from
|
|
@@ -320,6 +434,8 @@ and `gates`. Exit codes: `0` succeeded, `1` halted or refused, `2` bad usage.
|
|
|
320
434
|
**Full walkthrough, writing a manifest, and serving over HTTP for clients that
|
|
321
435
|
cannot start a process:** see the [user guide](docs/synartesis-user-guide.md).
|
|
322
436
|
|
|
437
|
+
---
|
|
438
|
+
|
|
323
439
|
## What it does not do
|
|
324
440
|
|
|
325
441
|
- **It cannot un-send what has been seen.** An email that has been read, a
|
|
@@ -341,12 +457,24 @@ cannot start a process:** see the [user guide](docs/synartesis-user-guide.md).
|
|
|
341
457
|
resolved when the call happens, so a mistake in a manifest is baked into every
|
|
342
458
|
run made under it. `undo --replan` rebuilds them from a corrected one.
|
|
343
459
|
|
|
344
|
-
|
|
460
|
+
Three bundled policies are tested against the real server, by making the change
|
|
345
461
|
and undoing it. **filesystem**: exact byte-for-byte restoration, drift refusal,
|
|
346
462
|
and absence told apart from a read that failed. **memory**: the graph is put
|
|
347
463
|
back as it was, entities the agent only tried to create are left alone, and a
|
|
348
|
-
delete of an entity is held rather than approximated.
|
|
349
|
-
|
|
464
|
+
delete of an entity is held rather than approximated.
|
|
465
|
+
|
|
466
|
+
**git** is the narrowest of the three, because the policy is: this server
|
|
467
|
+
exposes nothing that can restore content, so there is no restoration to prove.
|
|
468
|
+
What is proven is what it does claim — staging is taken back off the index and
|
|
469
|
+
the working tree is left alone; a commit is held rather than approximated; a
|
|
470
|
+
branch switch goes through and is reported as something undo cannot take back.
|
|
471
|
+
And the overreach is pinned too: this server's reset unstages *everything*, so
|
|
472
|
+
undoing the agent's `git_add` also unstages work a person staged by hand. That
|
|
473
|
+
is why it is recorded as a compensation and not an undo, and there is now a
|
|
474
|
+
test that fails if it ever gets described as one.
|
|
475
|
+
|
|
476
|
+
**github** is checked only for tool existence — its recovery guarantees are not
|
|
477
|
+
proven.
|
|
350
478
|
|
|
351
479
|
All three of filesystem, memory and git declare `provenance: live`, and that
|
|
352
480
|
word is narrower than it looks: it says the policy has met its server and the
|
|
@@ -361,6 +489,8 @@ means an agent stopping mid-task on a call nobody expected. `synartesis check`
|
|
|
361
489
|
names every such tool, and the proxy warns about them at startup, so you can
|
|
362
490
|
write a policy before meeting one rather than after.
|
|
363
491
|
|
|
492
|
+
---
|
|
493
|
+
|
|
364
494
|
## Trust
|
|
365
495
|
|
|
366
496
|
A manifest names commands and Synartesis runs them. Treat one you did not write
|
|
@@ -386,6 +516,8 @@ or of the CLI mid-undo loses nothing; only the machine losing power can cost the
|
|
|
386
516
|
tail of the write-ahead log. `SYNARTESIS_SYNC=full` asks for an fsync per commit
|
|
387
517
|
instead — worth it where fsync is cheap, and measurably not where it is not.
|
|
388
518
|
|
|
519
|
+
---
|
|
520
|
+
|
|
389
521
|
## Development
|
|
390
522
|
|
|
391
523
|
```bash
|
|
@@ -405,6 +537,52 @@ is expected to work, the code has no platform-specific paths outside
|
|
|
405
537
|
`src/locate.ts` and `src/install/clients.ts`, and nobody has proved it. If you
|
|
406
538
|
run Windows and something is wrong there, that is worth an issue.
|
|
407
539
|
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Contributing
|
|
543
|
+
|
|
544
|
+
The most useful thing you can send is a policy. Four servers ship with one;
|
|
545
|
+
there are hundreds that do not. If you run one — a database, a ticketing
|
|
546
|
+
system, your own — `synartesis init <name> -- <command>` drafts a
|
|
547
|
+
manifest by introspecting it, and a pull request adding that draft under
|
|
548
|
+
[`manifests/`](manifests) makes the tool cover a server it could not cover
|
|
549
|
+
before. Say in the description whether you ran it against the real thing; that
|
|
550
|
+
is what `provenance:` records, and an honest `documented` is worth more than an
|
|
551
|
+
optimistic `live`.
|
|
552
|
+
|
|
553
|
+
Also wanted, in rough order of how much they help:
|
|
554
|
+
|
|
555
|
+
- **Windows.** The installer is built and never tested — see
|
|
556
|
+
[Development](#development). A report either way is worth an issue.
|
|
557
|
+
- **A case where undo got it wrong.** The failure this project cares about most
|
|
558
|
+
is a confident wrong answer. If undo told you it reverted something and it had
|
|
559
|
+
not, that is the bug report to open.
|
|
560
|
+
- **A server whose tools do not fit the four classes.** The model has held so
|
|
561
|
+
far, and the first case it cannot express is worth knowing about.
|
|
562
|
+
|
|
563
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md) has the setup, and one command runs the
|
|
564
|
+
same gate a pull request has to pass:
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
pnpm check
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Where each thing goes.** A bug is an [issue](https://github.com/ArhaanDev24/Synartesis/issues). A question, an
|
|
571
|
+
idea, or something you built with it is a
|
|
572
|
+
[discussion](https://github.com/ArhaanDev24/Synartesis/discussions). A policy you wrote for a server that does not
|
|
573
|
+
ship with one, a symptom you worked out, or an answer worth keeping goes in the
|
|
574
|
+
[wiki](https://github.com/ArhaanDev24/Synartesis/wiki), which anybody can edit — it holds the things that are
|
|
575
|
+
better off community-maintained, and deliberately does not repeat the
|
|
576
|
+
documentation. A vulnerability goes in none of those:
|
|
577
|
+
[`SECURITY.md`](SECURITY.md) says where.
|
|
578
|
+
|
|
579
|
+
And if it saved you an afternoon, **[star the repo](https://github.com/ArhaanDev24/Synartesis)**; it costs you a
|
|
580
|
+
click and is most of how a project this size gets found. If it saved you more
|
|
581
|
+
than that, there is [a tip jar](https://ko-fi.com/synartesis) — entirely
|
|
582
|
+
optional, and nothing here is behind it.
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
408
586
|
## Licence
|
|
409
587
|
|
|
410
588
|
MIT. See [LICENSE](LICENSE).
|
package/SECURITY.md
CHANGED
|
@@ -150,6 +150,9 @@ function usable(stream) {
|
|
|
150
150
|
function spaced(text) {
|
|
151
151
|
return Array.from(text).join(" ");
|
|
152
152
|
}
|
|
153
|
+
function counted(n, noun) {
|
|
154
|
+
return `${String(n)} ${noun}${n === 1 ? "" : "s"}`;
|
|
155
|
+
}
|
|
153
156
|
function palette(on) {
|
|
154
157
|
const paint = (codes, text) => on ? `${codes}${text}${RESET}` : text;
|
|
155
158
|
return {
|
|
@@ -1973,6 +1976,7 @@ export {
|
|
|
1973
1976
|
pathBinaryMatches,
|
|
1974
1977
|
findManifest,
|
|
1975
1978
|
findJournal,
|
|
1979
|
+
counted,
|
|
1976
1980
|
style,
|
|
1977
1981
|
errorStyle,
|
|
1978
1982
|
WORDMARK,
|
|
@@ -2011,4 +2015,4 @@ export {
|
|
|
2011
2015
|
observeState,
|
|
2012
2016
|
connectStdioUpstream
|
|
2013
2017
|
};
|
|
2014
|
-
//# sourceMappingURL=chunk-
|
|
2018
|
+
//# sourceMappingURL=chunk-FIOKOIT6.js.map
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
canonical,
|
|
10
10
|
cliCommand,
|
|
11
11
|
connectStdioUpstream,
|
|
12
|
+
counted,
|
|
12
13
|
createPolicyResolver,
|
|
13
14
|
createRouter,
|
|
14
15
|
describeStanding,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
verifyAgainstServers,
|
|
39
40
|
warnUntested,
|
|
40
41
|
wasRefused
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-FIOKOIT6.js";
|
|
42
43
|
import {
|
|
43
44
|
DriftConflict,
|
|
44
45
|
ManifestError,
|
|
@@ -483,9 +484,6 @@ ${seen}` : seen;
|
|
|
483
484
|
const reason = `could not read current state to check for drift: ${describe(error)}`;
|
|
484
485
|
halted = { seq: action.seq, reason, detail: "" };
|
|
485
486
|
steps.push({ ...describeStep(action), kind: "halt", reason, verified: false });
|
|
486
|
-
if (!dryRun) {
|
|
487
|
-
journal.markUnrecoverable(action.id, reason);
|
|
488
|
-
}
|
|
489
487
|
break;
|
|
490
488
|
}
|
|
491
489
|
if (sameState(current, recordedPost.data)) {
|
|
@@ -2107,7 +2105,7 @@ function runsView(journal, screen, options) {
|
|
|
2107
2105
|
const here = index === at && canPress(options);
|
|
2108
2106
|
const name = (run.label ?? "an agent").padEnd(24);
|
|
2109
2107
|
const note = held === 0 ? "" : ` ${style.accent(`${String(held)} awaiting approval`)}`;
|
|
2110
|
-
return ` ${here ? style.accent(CURSOR) : " "} ${here ? style.accent(name) : style.strong(name)} ${style.quiet(shortTime(run.startedAt).trimEnd().padEnd(13))} ${style.quiet(run.status.padEnd(11))} ${style.quiet(
|
|
2108
|
+
return ` ${here ? style.accent(CURSOR) : " "} ${here ? style.accent(name) : style.strong(name)} ${style.quiet(shortTime(run.startedAt).trimEnd().padEnd(13))} ${style.quiet(run.status.padEnd(11))} ${style.quiet(counted(actions.length, "action"))}${note}`;
|
|
2111
2109
|
});
|
|
2112
2110
|
}
|
|
2113
2111
|
function standing2(actions) {
|
|
@@ -3408,12 +3406,12 @@ function laid(text, width) {
|
|
|
3408
3406
|
function runList(journal, asJson, journalPath) {
|
|
3409
3407
|
const runs = [...journal.listRuns()].reverse();
|
|
3410
3408
|
const tally2 = journal.tallyRuns();
|
|
3411
|
-
const
|
|
3409
|
+
const counted2 = (id) => tally2.get(id) ?? { actions: 0, unknown: 0, waiting: 0, applied: 0 };
|
|
3412
3410
|
if (asJson) {
|
|
3413
3411
|
out(
|
|
3414
3412
|
JSON.stringify(
|
|
3415
3413
|
runs.map((run) => {
|
|
3416
|
-
const actions =
|
|
3414
|
+
const actions = counted2(run.id).actions;
|
|
3417
3415
|
return { ...run, actions, actionCount: actions };
|
|
3418
3416
|
})
|
|
3419
3417
|
)
|
|
@@ -3435,7 +3433,7 @@ function runList(journal, asJson, journalPath) {
|
|
|
3435
3433
|
)
|
|
3436
3434
|
);
|
|
3437
3435
|
for (const run of runs) {
|
|
3438
|
-
const actions =
|
|
3436
|
+
const actions = counted2(run.id);
|
|
3439
3437
|
const { unknown, waiting } = actions;
|
|
3440
3438
|
const notes = [
|
|
3441
3439
|
unknown === 0 ? "" : `${String(unknown)} of unknown outcome`,
|
|
@@ -3658,7 +3656,7 @@ function summarise2(actions) {
|
|
|
3658
3656
|
}
|
|
3659
3657
|
const parts = [...counts].sort(([a], [b]) => a < b ? -1 : 1).map(([k, v]) => `${String(v)} ${k}`);
|
|
3660
3658
|
const undoable = actions.filter((a) => a.inverse !== void 0).length;
|
|
3661
|
-
return `${
|
|
3659
|
+
return `${counted(actions.length, "action")}: ${parts.join(", ")} | ${String(undoable)} with a recorded undo`;
|
|
3662
3660
|
}
|
|
3663
3661
|
var journalArg = "";
|
|
3664
3662
|
var manifestArg = "";
|
|
@@ -3718,13 +3716,13 @@ function runPrune(argv, journal, journalPath) {
|
|
|
3718
3716
|
for (const run of stale) {
|
|
3719
3717
|
actions += run.actions;
|
|
3720
3718
|
out(
|
|
3721
|
-
` ${style.strong((run.label ?? "an agent").padEnd(24))} ${style.quiet(run.at.slice(0, 19).replace("T", " "))} ${style.quiet(run.status.padEnd(11))} ${style.quiet(
|
|
3719
|
+
` ${style.strong((run.label ?? "an agent").padEnd(24))} ${style.quiet(run.at.slice(0, 19).replace("T", " "))} ${style.quiet(run.status.padEnd(11))} ${style.quiet(counted(run.actions, "action"))}`
|
|
3722
3720
|
);
|
|
3723
3721
|
}
|
|
3724
3722
|
out("");
|
|
3725
3723
|
if (planned) {
|
|
3726
3724
|
out(
|
|
3727
|
-
` ${style.accent(
|
|
3725
|
+
` ${style.accent(counted(stale.length, "run"))} ${style.quiet(`and ${counted(actions, "action")} would go. Nothing was changed.`)}`
|
|
3728
3726
|
);
|
|
3729
3727
|
out("");
|
|
3730
3728
|
return 0;
|
|
@@ -3732,7 +3730,7 @@ function runPrune(argv, journal, journalPath) {
|
|
|
3732
3730
|
const removed = journal.deleteRuns(stale.map((run) => run.id));
|
|
3733
3731
|
journal.vacuum();
|
|
3734
3732
|
out(
|
|
3735
|
-
` ${style.accent(
|
|
3733
|
+
` ${style.accent(counted(removed.runs, "run"))} ${style.quiet(`and ${counted(removed.actions, "action")} removed.`)}`
|
|
3736
3734
|
);
|
|
3737
3735
|
out(` ${style.quiet(`Journal ${sizeBefore} \u2192 ${sizeOf(journalPath)}.`)}`);
|
|
3738
3736
|
out("");
|
|
@@ -3903,13 +3901,15 @@ function report(result, alreadyForcing = false, as = "") {
|
|
|
3903
3901
|
const self = cliCommand();
|
|
3904
3902
|
const id = result.runId.slice(0, 8);
|
|
3905
3903
|
out("");
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
)
|
|
3904
|
+
const ways = [
|
|
3905
|
+
["keep the change, drop the undo:", style.quiet("nothing to do")],
|
|
3906
|
+
["put the resource back, then:", style.strong(`${self} undo ${id} --replan`)],
|
|
3907
|
+
["undo anyway, losing the change:", style.strong(`${self} undo ${id} --force`)]
|
|
3908
|
+
];
|
|
3909
|
+
const column = Math.max(...ways.map(([label]) => label.length));
|
|
3910
|
+
for (const [label, command] of ways) {
|
|
3911
|
+
out(` ${style.quiet(label.padEnd(column))} ${command}`);
|
|
3912
|
+
}
|
|
3913
3913
|
}
|
|
3914
3914
|
}
|
|
3915
3915
|
const permanent = result.steps.filter((step) => step.kind === "permanent");
|
|
@@ -4039,7 +4039,8 @@ async function runUndo(argv, journal) {
|
|
|
4039
4039
|
const highest = journal.getActions(runId).reduce((top, action) => Math.max(top, action.seq), 0);
|
|
4040
4040
|
if (toSeq > highest) {
|
|
4041
4041
|
throw new UsageError(
|
|
4042
|
-
`--to ${String(toSeq)} is past the end of this run, which goes up to ${String(highest)}
|
|
4042
|
+
`--to ${String(toSeq)} is past the end of this run, which goes up to ${String(highest)}`,
|
|
4043
|
+
false
|
|
4043
4044
|
);
|
|
4044
4045
|
}
|
|
4045
4046
|
}
|
package/dist/proxy.js
CHANGED
package/manifests/git.yaml
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Policy for mcp-server-git, the official git server, run through uvx.
|
|
2
2
|
#
|
|
3
|
-
# Verified against the real server on a real repository
|
|
3
|
+
# Verified against the real server on a real repository, and round-tripped by
|
|
4
|
+
# tests/adapter-git.test.ts: the one compensation below is run and its effect
|
|
5
|
+
# read back with git itself, and both gates are checked from both ends. Change
|
|
6
|
+
# a class or a gate here and that file fails.
|
|
4
7
|
#
|
|
5
8
|
# The finding worth carrying away from writing this one: nearly every read this
|
|
6
9
|
# server offers answers in prose meant for a person -- git_status says "On
|