@1agh/maude 0.60.7 → 1.0.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.
Files changed (145) hide show
  1. package/apps/studio/acp/index.ts +1 -0
  2. package/apps/studio/ai-banner.tsx +1 -0
  3. package/apps/studio/annotations-context-toolbar.tsx +3 -1
  4. package/apps/studio/annotations-layer.tsx +33 -16
  5. package/apps/studio/api.ts +108 -18
  6. package/apps/studio/artboard-guides-overlay.tsx +5 -1
  7. package/apps/studio/assets-s3.ts +6 -1
  8. package/apps/studio/bin/_import-figma.mjs +8 -3
  9. package/apps/studio/build.ts +1 -1
  10. package/apps/studio/canvas-artifacts.ts +21 -0
  11. package/apps/studio/canvas-build.ts +14 -9
  12. package/apps/studio/canvas-comment-mount.tsx +27 -30
  13. package/apps/studio/canvas-icons.tsx +1 -1
  14. package/apps/studio/canvas-lib.tsx +94 -5
  15. package/apps/studio/canvas-list-watch.ts +14 -1
  16. package/apps/studio/canvas-shell.tsx +8 -0
  17. package/apps/studio/client/app.jsx +224 -45
  18. package/apps/studio/client/panels/GitPanel.jsx +121 -13
  19. package/apps/studio/client/panels/SettingsPanel.jsx +1 -1
  20. package/apps/studio/client/panels/SyncConsentDialog.jsx +182 -0
  21. package/apps/studio/client/panels/SyncPanel.jsx +595 -1
  22. package/apps/studio/client/styles/3-shell-maude.css +38 -0
  23. package/apps/studio/clip-ops.ts +8 -1
  24. package/apps/studio/cloud/endpoints.ts +265 -3
  25. package/apps/studio/collab/origins.ts +3 -1
  26. package/apps/studio/comments-overlay.tsx +5 -0
  27. package/apps/studio/config.schema.json +24 -0
  28. package/apps/studio/context-menu.tsx +40 -27
  29. package/apps/studio/context.ts +57 -8
  30. package/apps/studio/cursors-overlay.tsx +25 -13
  31. package/apps/studio/dist/client.bundle.js +686 -686
  32. package/apps/studio/dist/comment-mount.js +2 -2
  33. package/apps/studio/dist/styles.css +1 -1
  34. package/apps/studio/exporters/jobs.ts +77 -15
  35. package/apps/studio/exporters/remote.ts +190 -0
  36. package/apps/studio/exporters/video-encode-lib.ts +10 -4
  37. package/apps/studio/figma/to-strokes.ts +11 -9
  38. package/apps/studio/gifenc.d.ts +51 -0
  39. package/apps/studio/git/log-format.ts +88 -0
  40. package/apps/studio/git/safe-rel.ts +96 -0
  41. package/apps/studio/git/service.ts +46 -27
  42. package/apps/studio/hmr-broadcast.ts +10 -0
  43. package/apps/studio/http.ts +384 -6
  44. package/apps/studio/participants-chrome.tsx +1 -0
  45. package/apps/studio/photo-store.ts +7 -0
  46. package/apps/studio/react-augment.d.ts +17 -0
  47. package/apps/studio/runtime-bundle.ts +6 -1
  48. package/apps/studio/server.ts +43 -8
  49. package/apps/studio/sync/agent.ts +70 -82
  50. package/apps/studio/sync/asset-push.ts +28 -71
  51. package/apps/studio/sync/autocommit.ts +106 -5
  52. package/apps/studio/sync/cell-file-events.ts +117 -0
  53. package/apps/studio/sync/cell-pairing.ts +20 -5
  54. package/apps/studio/sync/cell-write-nudge.ts +244 -0
  55. package/apps/studio/sync/codec.ts +155 -3
  56. package/apps/studio/sync/cold-start-apply.ts +211 -0
  57. package/apps/studio/sync/ctl-heal.ts +253 -0
  58. package/apps/studio/sync/ctl-provider.ts +217 -0
  59. package/apps/studio/sync/decide-file.ts +335 -0
  60. package/apps/studio/sync/file-ledger.ts +581 -0
  61. package/apps/studio/sync/file-membership.ts +32 -0
  62. package/apps/studio/sync/file-plane.ts +1400 -0
  63. package/apps/studio/sync/file-pull.ts +41 -4
  64. package/apps/studio/sync/hub-link.ts +16 -1
  65. package/apps/studio/sync/hub-listing.ts +46 -0
  66. package/apps/studio/sync/hubs-config.ts +16 -0
  67. package/apps/studio/sync/index.ts +915 -308
  68. package/apps/studio/sync/journal-client.ts +200 -0
  69. package/apps/studio/sync/migrate-seed.ts +99 -67
  70. package/apps/studio/sync/poke.ts +50 -0
  71. package/apps/studio/sync/projection.ts +13 -0
  72. package/apps/studio/sync/pull-budget.ts +86 -0
  73. package/apps/studio/sync/settings.ts +110 -0
  74. package/apps/studio/sync/status.ts +68 -0
  75. package/apps/studio/sync/trash.ts +243 -0
  76. package/apps/studio/sync/untrusted.ts +30 -10
  77. package/apps/studio/test/_helpers.ts +8 -0
  78. package/apps/studio/test/canvas-build.test.ts +63 -0
  79. package/apps/studio/test/canvas-list-watch.test.ts +17 -0
  80. package/apps/studio/test/canvas-move-api.test.ts +31 -0
  81. package/apps/studio/test/canvas-origin-gate.test.ts +12 -0
  82. package/apps/studio/test/canvas-shell-build-error.test.ts +49 -0
  83. package/apps/studio/test/cloud-history-hardening.test.ts +165 -0
  84. package/apps/studio/test/cloud-history-posture.test.ts +230 -0
  85. package/apps/studio/test/cloud-session-role.test.ts +30 -0
  86. package/apps/studio/test/cloud-shell-surfaces.test.ts +39 -0
  87. package/apps/studio/test/cold-start-apply.test.ts +303 -0
  88. package/apps/studio/test/collab-stress.test.ts +9 -1
  89. package/apps/studio/test/export-lane.test.ts +245 -0
  90. package/apps/studio/test/fixtures/video-comp-fixture.tsx +1 -1
  91. package/apps/studio/test/git-log-format.test.ts +95 -0
  92. package/apps/studio/test/git-safe-rel.test.ts +132 -0
  93. package/apps/studio/test/hmr-broadcast.test.ts +26 -0
  94. package/apps/studio/test/peer-selection-follows-camera.test.tsx +131 -0
  95. package/apps/studio/test/shared-doc-cell-pairing.test.ts +5 -2
  96. package/apps/studio/test/sync-agent.test.ts +78 -0
  97. package/apps/studio/test/sync-asset-push.test.ts +71 -108
  98. package/apps/studio/test/sync-autocommit.test.ts +80 -0
  99. package/apps/studio/test/sync-cell-write-nudge.test.ts +346 -0
  100. package/apps/studio/test/sync-ctl-channel.test.ts +508 -0
  101. package/apps/studio/test/sync-decide-file.test.ts +420 -0
  102. package/apps/studio/test/sync-file-ledger.test.ts +334 -0
  103. package/apps/studio/test/sync-file-membership.test.ts +17 -1
  104. package/apps/studio/test/sync-file-plane.test.ts +976 -0
  105. package/apps/studio/test/sync-hub-listing.test.ts +46 -0
  106. package/apps/studio/test/sync-meta-codec.test.ts +76 -0
  107. package/apps/studio/test/sync-move-retirement.test.ts +231 -0
  108. package/apps/studio/test/sync-panel-surface.test.ts +20 -0
  109. package/apps/studio/test/sync-path-pull.test.ts +67 -1
  110. package/apps/studio/test/sync-pull-budget.test.ts +169 -0
  111. package/apps/studio/test/sync-seed-defers-to-hub.test.ts +83 -0
  112. package/apps/studio/test/sync-settings-routes.test.ts +195 -0
  113. package/apps/studio/test/sync-settings.test.ts +151 -0
  114. package/apps/studio/test/sync-status.test.ts +69 -0
  115. package/apps/studio/test/sync-trash.test.ts +132 -0
  116. package/apps/studio/test/workspace-containment.test.ts +45 -9
  117. package/apps/studio/tsconfig.json +9 -10
  118. package/apps/studio/use-annotation-resize.tsx +14 -3
  119. package/apps/studio/use-collab.tsx +3 -1
  120. package/apps/studio/whats-new.json +90 -0
  121. package/apps/studio/workspace-mode.ts +110 -62
  122. package/apps/studio/ws.ts +22 -1
  123. package/cli/bin/claude-design-server.mjs +19 -0
  124. package/cli/commands/design.mjs +25 -6
  125. package/cli/commands/hub-workspace.mjs +243 -22
  126. package/cli/commands/hub-workspace.test.mjs +171 -0
  127. package/cli/commands/hub.mjs +71 -1
  128. package/cli/lib/design-link.mjs +186 -1
  129. package/cli/lib/design-ownership.mjs +330 -0
  130. package/cli/lib/design-ownership.test.mjs +329 -0
  131. package/cli/lib/hubs-config.mjs +21 -0
  132. package/cli/lib/hubs-config.test.mjs +47 -1
  133. package/cli/lib/workspace-plan.mjs +298 -5
  134. package/cli/lib/workspace-plan.test.mjs +215 -1
  135. package/package.json +10 -10
  136. package/plugins/design/templates/_shell.html +43 -2
  137. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +1 -1
  138. package/plugins/design/templates/design-system-inspiration/core/preview/_motion-readme.md.tpl +1 -1
  139. package/apps/studio/server.mjs +0 -1312
  140. package/apps/studio/sync/asset-pull.ts +0 -210
  141. package/apps/studio/sync/asset-push-worker.ts +0 -84
  142. package/apps/studio/sync/asset-sweep.ts +0 -262
  143. package/apps/studio/test/sync-asset-pull.test.ts +0 -161
  144. package/apps/studio/test/sync-asset-push-worker.test.ts +0 -183
  145. package/apps/studio/test/sync-asset-sweep.test.ts +0 -243
@@ -0,0 +1,335 @@
1
+ // The ONE file decision — Sync v2 Increment 3 (DDR-226 §4).
2
+ //
3
+ // Every non-CRDT file in a linked project resolves through this function, and
4
+ // through nothing else. It is pure, total, and Y-free, in the shape
5
+ // `cold-start.ts` established: the table is the specification, and the caller
6
+ // executes what it returns.
7
+ //
8
+ // ── Why three inputs, and why the third one matters most ────────────────────
9
+ //
10
+ // Two-way sync (local vs remote) cannot tell "I changed it" from "they changed
11
+ // it" — both look like "the two sides differ", and every answer to that is a
12
+ // coin flip that sometimes erases a person's work. The third input is the
13
+ // ANCESTOR: the hash this machine last reconciled through. With it, the same
14
+ // difference becomes a fact rather than a guess —
15
+ //
16
+ // ancestor == local → they moved, I did not → PULL
17
+ // ancestor == remote → I moved, they did not → PUSH
18
+ // ancestor == neither → we both moved → CONFLICT
19
+ //
20
+ // That is the entire idea, and it is the same one Dropbox, Syncthing and
21
+ // CouchDB converged on. Everything below is the edges.
22
+ //
23
+ // ── The rules that are not negotiable ───────────────────────────────────────
24
+ //
25
+ // **Bytes are never merged.** A conflict parks the local copy aside under a
26
+ // name both ends can see and then adopts the remote — never a silent
27
+ // overwrite, never a three-way text merge of a PNG or a stylesheet.
28
+ //
29
+ // **Absence is never authority** (DDR-076, generalized). A file missing from
30
+ // the hub does not mean "delete yours"; it means the hub does not have it. Only
31
+ // an explicit tombstone row can propagate a deletion, and even then only when
32
+ // the remote is still at the ancestor — an edit beats a delete (the Syncthing
33
+ // rule).
34
+ //
35
+ // **Unstamped emptiness never beats content** (DDR-223, generalized). Emptiness
36
+ // is just a hash here; it wins only where any other hash would, through
37
+ // ancestor equality. There is no branch that treats "nothing" as newer.
38
+ //
39
+ // **A degraded epoch downgrades every overwrite.** When the hub's epoch no
40
+ // longer matches what our ancestors were anchored against, those ancestors
41
+ // describe a log that no longer exists — so they stop being overwrite
42
+ // authority. Every PULL that would land on top of a local change becomes
43
+ // keep-local + park-the-remote-copy + push-local-up.
44
+
45
+ /** What the caller must do with this path. */
46
+ export type FileAction =
47
+ /** Nothing to move. Adopt the ancestor so the next pass is cheap. */
48
+ | 'noop'
49
+ /** Fetch the remote bytes and materialize them. */
50
+ | 'pull'
51
+ /** Upload the local bytes (CAS'd against the head we decided from). */
52
+ | 'push'
53
+ /** Park local aside, then adopt remote. Both ends end up seeing both. */
54
+ | 'conflict-aside'
55
+ /** A tombstone we agree with: quarantine the local copy to `_trash/`. */
56
+ | 'quarantine'
57
+ /** Local is gone and the hub still has what we last saw: state the delete. */
58
+ | 'propagate-delete'
59
+ /** A tombstone we disagree with: keep local and push it back up. */
60
+ | 'revive';
61
+
62
+ export interface FileState {
63
+ /** designRoot-relative path, for the reason string only. */
64
+ path: string;
65
+ /** sha256 of the local file, or null when it is not on disk. */
66
+ local: string | null;
67
+ /** sha256 the hub's latest row carries, or null when it has no row. */
68
+ remote: string | null;
69
+ /**
70
+ * The ancestor: the hash this machine last reconciled through for this path.
71
+ * Null means "never reconciled here" — a first anchor, not an empty file.
72
+ */
73
+ ancestor: string | null;
74
+ /** The hub's latest row for this path is a tombstone. */
75
+ remoteTombstone?: boolean;
76
+ /**
77
+ * The hub's epoch differs from the one our ancestors were anchored against,
78
+ * and the journal could not reconstruct the gap. Ancestors stop being
79
+ * overwrite authority — see the header.
80
+ */
81
+ epochChanged?: boolean;
82
+ /**
83
+ * The remote hash equals something this machine has in flight. The "remote
84
+ * change" is our own write coming back to us; adopt it and move on rather
85
+ * than manufacturing a conflict with ourselves.
86
+ */
87
+ selfInFlight?: boolean;
88
+ /**
89
+ * Deletion EMISSION is off until Increment 6. When false, a local absence
90
+ * that would propagate a delete is held instead — the row exists in the
91
+ * table from day one so the behaviour is a flag, not a missing branch.
92
+ */
93
+ propagateDeletes?: boolean;
94
+ }
95
+
96
+ export interface FileDecision {
97
+ action: FileAction;
98
+ reason: string;
99
+ /**
100
+ * Park the LOCAL copy aside before anything else happens. Set on
101
+ * `conflict-aside`. If the park fails the caller must refuse the overwrite —
102
+ * DDR-102's fail-closed rule, applied to files.
103
+ */
104
+ parkLocal?: boolean;
105
+ /**
106
+ * Park the REMOTE copy aside instead of landing it. Set on the
107
+ * epoch-degraded rows, where the remote is worth keeping a copy of but is
108
+ * not allowed to overwrite anything.
109
+ */
110
+ parkRemote?: boolean;
111
+ /** The decision moves no bytes; just record the ancestor. */
112
+ adoptAncestor?: boolean;
113
+ }
114
+
115
+ /**
116
+ * The shapes the (local, remote, ancestor, tombstone) space collapses into.
117
+ *
118
+ * Naming the shape BEFORE deciding is what makes the switch below exhaustive
119
+ * with a compile-time `never`: the classification is total by construction, and
120
+ * a new shape cannot be added without the compiler demanding a row for it.
121
+ */
122
+ type Shape =
123
+ | 'both-absent'
124
+ | 'equal'
125
+ | 'self-echo'
126
+ | 'local-anchored'
127
+ | 'remote-anchored'
128
+ | 'create-down'
129
+ | 'create-up'
130
+ | 'remote-regressed'
131
+ | 'diverged'
132
+ | 'local-deleted-clean'
133
+ | 'local-deleted-but-remote-moved'
134
+ | 'tombstone-agreed'
135
+ | 'tombstone-contested';
136
+
137
+ function classify(s: FileState): Shape {
138
+ const { local, remote, ancestor } = s;
139
+
140
+ // A tombstone is a statement about the path, so it outranks hash comparison.
141
+ if (s.remoteTombstone) {
142
+ if (local === null) return 'tombstone-agreed';
143
+ // We still hold what the tombstone describes ⇒ agree with it.
144
+ if (ancestor !== null && local === ancestor) return 'tombstone-agreed';
145
+ // We hold something the deleter never saw ⇒ an edit beats their delete.
146
+ return 'tombstone-contested';
147
+ }
148
+
149
+ if (local === null && remote === null) return 'both-absent';
150
+
151
+ // Our own write echoing back off the hub. Checked before the difference
152
+ // rows, because to every one of them this looks like a remote change.
153
+ if (s.selfInFlight === true && remote !== null && remote !== local) return 'self-echo';
154
+
155
+ if (local !== null && local === remote) return 'equal';
156
+
157
+ if (local === null) {
158
+ if (ancestor === null) return 'create-down'; // remote !== null here
159
+ // We had it, we reconciled it, and now it is gone from disk.
160
+ return remote === ancestor ? 'local-deleted-clean' : 'local-deleted-but-remote-moved';
161
+ }
162
+
163
+ if (remote === null) {
164
+ if (ancestor === null) return 'create-up';
165
+ // The hub had it and no longer does, with no tombstone to say why.
166
+ // Absence is not authority (DDR-076): put it back.
167
+ return 'remote-regressed';
168
+ }
169
+
170
+ // Both present and different.
171
+ if (ancestor !== null && ancestor === local) return 'local-anchored';
172
+ if (ancestor !== null && ancestor === remote) return 'remote-anchored';
173
+ return 'diverged';
174
+ }
175
+
176
+ /**
177
+ * Decide what happens to one file. Pure and total.
178
+ */
179
+ export function decideFile(state: FileState): FileDecision {
180
+ const shape = classify(state);
181
+ const degraded = state.epochChanged === true;
182
+
183
+ switch (shape) {
184
+ case 'both-absent':
185
+ return { action: 'noop', reason: 'neither side has this path', adoptAncestor: true };
186
+
187
+ case 'equal':
188
+ return { action: 'noop', reason: 'local and hub already agree', adoptAncestor: true };
189
+
190
+ case 'self-echo':
191
+ // The hub is telling us about our own in-flight write. Adopting rather
192
+ // than conflicting is what stops a push from fighting its own echo —
193
+ // the reason the old fast lane needed a probe-guard at all.
194
+ return {
195
+ action: 'noop',
196
+ reason: 'the hub row is this machine’s own in-flight write coming back',
197
+ adoptAncestor: true,
198
+ };
199
+
200
+ case 'local-anchored':
201
+ // They moved, we did not — the clean download.
202
+ return degraded
203
+ ? {
204
+ action: 'noop',
205
+ reason:
206
+ 'the hub changed, but the epoch no longer matches our anchor — keeping local and parking their copy rather than overwriting on a stale ancestor',
207
+ parkRemote: true,
208
+ }
209
+ : { action: 'pull', reason: 'the hub moved and this machine did not' };
210
+
211
+ case 'remote-anchored':
212
+ // We moved, they did not — the clean upload.
213
+ return { action: 'push', reason: 'this machine moved and the hub did not' };
214
+
215
+ case 'create-down':
216
+ // Nothing local to lose. Never degraded: there is no local change for a
217
+ // stale ancestor to endanger.
218
+ return { action: 'pull', reason: 'the hub offers a file this machine has never had' };
219
+
220
+ case 'create-up':
221
+ return { action: 'push', reason: 'this machine has a file the hub has never had' };
222
+
223
+ case 'remote-regressed':
224
+ // DDR-076 generalized. A hub that lost a file gets it back; it does not
225
+ // get to take ours.
226
+ return {
227
+ action: 'push',
228
+ reason:
229
+ 'the hub no longer has a file it once did, and said no tombstone — absence is not authority',
230
+ };
231
+
232
+ case 'diverged':
233
+ // Both moved. Never a merge, never a silent winner: park ours under a
234
+ // name both ends can see, adopt theirs, and let the copy travel too.
235
+ return degraded
236
+ ? {
237
+ action: 'noop',
238
+ reason:
239
+ 'both sides moved and the epoch is degraded — keeping local, parking the hub copy, and pushing local up',
240
+ parkRemote: true,
241
+ }
242
+ : {
243
+ action: 'conflict-aside',
244
+ reason: 'both sides changed this file since they last agreed',
245
+ parkLocal: true,
246
+ };
247
+
248
+ case 'local-deleted-clean':
249
+ // The Syncthing rule. The deletion is real, but EMITTING it is Increment
250
+ // 6's job — until then we hold rather than pretend the file is back.
251
+ return state.propagateDeletes === true
252
+ ? {
253
+ action: 'propagate-delete',
254
+ reason: 'deleted here, and the hub still has what we last saw',
255
+ }
256
+ : {
257
+ action: 'noop',
258
+ reason:
259
+ 'deleted here, but delete propagation is off — holding rather than resurrecting',
260
+ };
261
+
262
+ case 'local-deleted-but-remote-moved':
263
+ // An edit beats a delete: somebody changed the file after we last saw
264
+ // it, so bring it back rather than deleting their work.
265
+ return {
266
+ action: 'pull',
267
+ reason: 'deleted here, but the hub has newer content — an edit beats a delete',
268
+ };
269
+
270
+ case 'tombstone-agreed':
271
+ if (state.local === null) {
272
+ return {
273
+ action: 'noop',
274
+ reason: 'the hub tombstoned a file this machine does not have',
275
+ adoptAncestor: true,
276
+ };
277
+ }
278
+ // B6 (post-1.0 burn-down) — a degraded epoch damps the deletion row
279
+ // like every overwrite row above it. The "agreement" here is
280
+ // `local === ancestor`, and the degrade just disqualified exactly those
281
+ // ancestors — honouring a tombstone on their strength would let a hub
282
+ // whose log rewound (or a hostile one that answered `reanchor`) delete
283
+ // files using authority it no longer has. Hold; a later pass on a
284
+ // matching epoch re-decides with real ancestors.
285
+ if (degraded) {
286
+ return {
287
+ action: 'noop',
288
+ reason:
289
+ 'the hub tombstoned this file, but the epoch is degraded — the ancestors that would justify agreeing were just disqualified; keeping local until a clean pass',
290
+ };
291
+ }
292
+ // Quarantine, never unlink — the recoverability spine.
293
+ return {
294
+ action: 'quarantine',
295
+ reason: 'the hub deleted a file this machine still holds unchanged',
296
+ };
297
+
298
+ case 'tombstone-contested':
299
+ return {
300
+ action: 'revive',
301
+ reason:
302
+ 'the hub deleted a file this machine has changed since — keeping local and pushing it back',
303
+ };
304
+
305
+ default: {
306
+ // Adding a shape without a row is a compile error, never a silent
307
+ // fallthrough. (This is the mistake `migrate-seed` made with
308
+ // `recover-seed-dup`, and it cost a duplicated canvas body.)
309
+ const never: never = shape;
310
+ throw new Error(`decideFile: unhandled shape ${String(never)}`);
311
+ }
312
+ }
313
+ }
314
+
315
+ /** The conflict-copy name. NEVER `*.sync-conflict-*` — see the note below. */
316
+ export function conflictCopyName(rel: string, ts: number, label: string): string {
317
+ // `~/git` runs real Syncthing, whose own conflict artifacts are
318
+ // `*.sync-conflict-*`. Emitting the identical pattern would make a conflict
319
+ // unattributable — you could not tell which program parked the file, and the
320
+ // classifier would happily sync Syncthing's copies as if they were ours.
321
+ const safeLabel = label.replace(/[^A-Za-z0-9-]/g, '').slice(0, 32) || 'peer';
322
+ const stamp = new Date(ts).toISOString().replace(/[:.]/g, '-').replace(/Z$/, '');
323
+ const slash = rel.lastIndexOf('/');
324
+ const dir = slash === -1 ? '' : rel.slice(0, slash + 1);
325
+ const base = slash === -1 ? rel : rel.slice(slash + 1);
326
+ const dot = base.indexOf('.');
327
+ const stem = dot === -1 ? base : base.slice(0, dot);
328
+ const ext = dot === -1 ? '' : base.slice(dot);
329
+ return `${dir}${stem}.maude-conflict-${stamp}-${safeLabel}${ext}`;
330
+ }
331
+
332
+ /** Is this path one of OUR conflict copies? */
333
+ export function isMaudeConflictCopy(rel: string): boolean {
334
+ return /\.maude-conflict-/.test(rel);
335
+ }