synartesis 0.8.1 → 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 CHANGED
@@ -2,6 +2,151 @@
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
+
5
150
  ## 0.8.1 — 2026-09-19
6
151
 
7
152
  Documentation only. No code changed, and the published package behaves exactly
package/README.md CHANGED
@@ -9,23 +9,9 @@ Synartesis sits between your MCP client and the servers it talks to, records
9
9
  every tool call with the state that call replaced, and can put that state back.
10
10
  What cannot be put back, it refuses to let an agent do unsupervised.
11
11
 
12
- ### [Install it, then read the five minutes ahead of you →](docs/synartesis-user-guide.md#the-five-minutes-ahead-of-you)
13
-
14
- [![check](https://github.com/ArhaanDev24/Synartesis/actions/workflows/check.yml/badge.svg)](https://github.com/ArhaanDev24/Synartesis/actions/workflows/check.yml)
15
- [![npm](https://img.shields.io/npm/v/synartesis?color=5e1420&label=npm)](https://www.npmjs.com/package/synartesis)
16
- [![downloads](https://img.shields.io/npm/dm/synartesis?color=5e1420&label=downloads)](https://www.npmjs.com/package/synartesis)
17
- [![node](https://img.shields.io/node/v/synartesis?color=5e1420)](https://nodejs.org)
18
- [![MIT](https://img.shields.io/badge/licence-MIT-5e1420.svg)](LICENSE)
19
- [![site](https://img.shields.io/badge/synartesis.online-5e1420)](https://synartesis.online)
20
- [![star this repo](https://img.shields.io/badge/★_star_this_repo-2c080f)](https://github.com/ArhaanDev24/Synartesis)
21
-
22
- [Choose your path](#choose-your-path) · [See it in action](#see-it-in-action) ·
23
- [Install](#install) · [The screen](#the-screen) ·
24
- [The desktop window](#the-desktop-window) ·
25
- [The four classes](#what-it-can-and-cannot-do) ·
26
- [Your own server](#writing-a-policy-for-your-own-server) ·
27
- [Commands](#commands) · [What it does not do](#what-it-does-not-do) ·
28
- [Trust](#trust) · [Contributing](#contributing)
12
+ [![Get started](https://img.shields.io/badge/Get%20started-5e1420?style=for-the-badge&labelColor=5e1420&color=5e1420&logoColor=f6e9e5)](#install) [![Desktop app](https://img.shields.io/badge/Desktop%20app-5e1420?style=for-the-badge&labelColor=5e1420&color=5e1420&logoColor=f6e9e5)](#the-desktop-window) [![User guide](https://img.shields.io/badge/User%20guide-5e1420?style=for-the-badge&labelColor=5e1420&color=5e1420&logoColor=f6e9e5)](docs/synartesis-user-guide.md) [![Wiki](https://img.shields.io/badge/Wiki-5e1420?style=for-the-badge&labelColor=5e1420&color=5e1420&logoColor=f6e9e5)](https://github.com/ArhaanDev24/Synartesis/wiki) [![Discussions](https://img.shields.io/badge/Discussions-5e1420?style=for-the-badge&labelColor=5e1420&color=5e1420&logoColor=f6e9e5)](https://github.com/ArhaanDev24/Synartesis/discussions) [![★ Star](https://img.shields.io/badge/%E2%98%85%20Star-f1b696?style=for-the-badge&labelColor=f1b696&color=f1b696)](https://github.com/ArhaanDev24/Synartesis)
13
+
14
+ [![check](https://img.shields.io/github/actions/workflow/status/ArhaanDev24/Synartesis/check.yml?branch=main&label=check&style=flat-square)](https://github.com/ArhaanDev24/Synartesis/actions/workflows/check.yml) [![npm](https://img.shields.io/npm/v/synartesis?color=5e1420&label=npm&style=flat-square)](https://www.npmjs.com/package/synartesis) [![downloads](https://img.shields.io/npm/dm/synartesis?color=5e1420&label=downloads&style=flat-square)](https://www.npmjs.com/package/synartesis) [![node](https://img.shields.io/node/v/synartesis?color=5e1420&style=flat-square)](https://nodejs.org) [![licence MIT](https://img.shields.io/badge/licence-MIT-5e1420?style=flat-square)](LICENSE) [![synartesis.online](https://img.shields.io/badge/synartesis.online-5e1420?style=flat-square)](https://synartesis.online)
29
15
 
30
16
  </div>
31
17
 
@@ -38,6 +24,8 @@ It is not a sandbox: the container your agent runs in is disposable, but the
38
24
  CRM row it updated over the network is not. It is not a tracing tool: a trace
39
25
  tells you `update_customer` ran forty times, not what the values were before.
40
26
 
27
+ ![Where Synartesis sits: a tool call goes from your agent through the proxy, which writes the state it is about to replace into a local journal, and on to your tools](https://raw.githubusercontent.com/ArhaanDev24/Synartesis/main/brand/synartesis-path-loop.svg)
28
+
41
29
  **If this is a problem you have, [a star](https://github.com/ArhaanDev24/Synartesis) helps other people find it.**
42
30
  It is a young project, and that is most of how anybody learns it exists.
43
31
 
@@ -75,7 +63,13 @@ machine, and the proxy talks only to the servers your policy names.
75
63
 
76
64
  ## See it in action
77
65
 
78
- Both shots are real output from [`./demo/filesystem-demo.sh`](demo/filesystem-demo.sh),
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:
69
+
70
+ ![Capture, change, put back: the value 412,800 is copied into the journal before the agent writes North to 0, and the captured copy is what comes back](https://raw.githubusercontent.com/ArhaanDev24/Synartesis/main/brand/synartesis-undo-loop.svg)
71
+
72
+ Both shots below are real output from [`./demo/filesystem-demo.sh`](demo/filesystem-demo.sh),
79
73
  pasted rather than typeset. An agent overwrote a file and tried to move another.
80
74
  One command puts the first back and reports that the second never happened:
81
75
 
@@ -93,7 +87,7 @@ It stops at the record that moved and exits non-zero. Anything already put back
93
87
  stays put back, and it prints the three ways on: leave it, restore the resource
94
88
  and `--replan`, or `--force` to overwrite deliberately.
95
89
 
96
- [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) · [User guide](docs/synartesis-user-guide.md)
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)
97
91
 
98
92
  ---
99
93
  ## Install
@@ -122,7 +116,7 @@ installed.**
122
116
  Needs Node 22 or newer. npm ships a prebuilt SQLite binding, so no toolchain is
123
117
  required unless you build from a clone.
124
118
 
125
- [User guide](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)
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)
126
120
 
127
121
  ---
128
122
 
@@ -273,7 +267,7 @@ It loads the manifest, connects to every server it names, and says which tools
273
267
  your policy covers, which it does not, and where the two disagree. A policy
274
268
  that names a tool the server no longer has is an error, not a warning.
275
269
 
276
- [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)
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)
277
271
 
278
272
 
279
273
  ---
@@ -463,12 +457,24 @@ cannot start a process:** see the [user guide](docs/synartesis-user-guide.md).
463
457
  resolved when the call happens, so a mistake in a manifest is baked into every
464
458
  run made under it. `undo --replan` rebuilds them from a corrected one.
465
459
 
466
- Two bundled policies are tested against the real server, by making the change
460
+ Three bundled policies are tested against the real server, by making the change
467
461
  and undoing it. **filesystem**: exact byte-for-byte restoration, drift refusal,
468
462
  and absence told apart from a read that failed. **memory**: the graph is put
469
463
  back as it was, entities the agent only tried to create are left alone, and a
470
- delete of an entity is held rather than approximated. **git and github** are
471
- checked only for tool existence — their recovery guarantees are not yet proven.
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.
472
478
 
473
479
  All three of filesystem, memory and git declare `provenance: live`, and that
474
480
  word is narrower than it looks: it says the policy has met its server and the
@@ -561,11 +567,19 @@ same gate a pull request has to pass:
561
567
  pnpm check
562
568
  ```
563
569
 
564
- Bugs and questions go in [issues](https://github.com/ArhaanDev24/Synartesis/issues).
565
- Security reports do not — [`SECURITY.md`](SECURITY.md) says where those go
566
- instead. And if it saved you an afternoon, **[star the
567
- repo](https://github.com/ArhaanDev24/Synartesis)**; it costs you a click and is
568
- most of how a project this size gets found.
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.
569
583
 
570
584
  ---
571
585
 
@@ -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-O3QDPYEL.js.map
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-O3QDPYEL.js";
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(`${String(actions.length)} actions`)}${note}`;
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 counted = (id) => tally2.get(id) ?? { actions: 0, unknown: 0, waiting: 0, applied: 0 };
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 = counted(run.id).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 = counted(run.id);
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 `${String(actions.length)} actions: ${parts.join(", ")} | ${String(undoable)} with a recorded undo`;
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(`${String(run.actions)} actions`)}`
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(`${String(stale.length)} runs`)} ${style.quiet(`and ${String(actions)} actions would go. Nothing was changed.`)}`
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(`${String(removed.runs)} runs`)} ${style.quiet(`and ${String(removed.actions)} actions removed.`)}`
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
- out(` ${style.quiet("keep the change, drop the undo:")} ${style.quiet("nothing to do")}`);
3907
- out(
3908
- ` ${style.quiet("put it back as the run left it:")} ${style.strong(`${self} undo ${id} --replan`)}`
3909
- );
3910
- out(
3911
- ` ${style.quiet("undo anyway, losing the change:")} ${style.strong(`${self} undo ${id} --force`)}`
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
@@ -25,7 +25,7 @@ import {
25
25
  verifyAgainstServers,
26
26
  warnUntested,
27
27
  withIdempotencyKey
28
- } from "./chunk-O3QDPYEL.js";
28
+ } from "./chunk-FIOKOIT6.js";
29
29
  import {
30
30
  SnapshotError,
31
31
  UpstreamError,
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synartesis",
3
- "version": "0.8.1",
3
+ "version": "0.8.4",
4
4
  "description": "An undo layer for AI agents.",
5
5
  "type": "module",
6
6
  "private": false,