@codyswann/lisa 4.36.1 → 4.37.0
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/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +2 -2
- package/all/copy-overwrite/scripts/lisa-hooks/block-blind-automerge.sh +626 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +10 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +26 -7
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/strategies/package-lisa.js +10 -0
- package/dist/strategies/package-lisa.js.map +1 -1
- package/package.json +5 -4
- package/plugins/lisa/.claude-plugin/plugin.json +5 -1
- package/plugins/lisa/.codex-plugin/hooks.json +4 -0
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-blind-automerge.agy.sh +54 -0
- package/plugins/lisa/hooks/block-blind-automerge.sh +623 -0
- package/plugins/lisa-agy/hooks/block-blind-automerge.agy.sh +54 -0
- package/plugins/lisa-agy/hooks/block-blind-automerge.sh +623 -0
- package/plugins/lisa-agy/hooks.json +13 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +5 -1
- package/plugins/lisa-copilot/hooks/block-blind-automerge.sh +623 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-blind-automerge.sh +623 -0
- package/plugins/lisa-cursor/hooks/hooks.json +4 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/materialized-artifacts.json +1 -0
- package/plugins/src/base/.claude-plugin/plugin.json +4 -0
- package/plugins/src/base/hooks/block-blind-automerge.agy.sh +54 -0
- package/plugins/src/base/hooks/block-blind-automerge.sh +623 -0
- package/scripts/build-plugins.sh +1 -1
- package/scripts/check-self-dependency-pin.mjs +401 -0
- package/scripts/generate-agy-plugin-artifacts.mjs +11 -0
- package/scripts/lib/per-agent-hook-filter.mjs +10 -0
- package/scripts/lisa-enforcement-fallback.sh +2 -2
- package/scripts/merge-generated-artifact.mjs +182 -20
- package/scripts/two-channel-couplings.json +1 -1
|
@@ -31,10 +31,26 @@
|
|
|
31
31
|
* For a pointwise function, **a pointwise 3-way merge of the OUTPUT equals the
|
|
32
32
|
* function applied to the merged INPUT**. If a key changed on our side only,
|
|
33
33
|
* the merged tree has our version of the file it describes, so our entry is the
|
|
34
|
-
* right one; symmetrically for theirs
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
34
|
+
* right one; symmetrically for theirs. So the result is not one side's blob —
|
|
35
|
+
* it is a reconstruction of what regeneration would produce.
|
|
36
|
+
*
|
|
37
|
+
* ## The one case where reconstruction is impossible
|
|
38
|
+
*
|
|
39
|
+
* If both sides changed the same key differently, the merged tree has a file
|
|
40
|
+
* that neither side described, so there is no right answer to reconstruct.
|
|
41
|
+
* CodySwannGT/lisa#3822 measured what that costs: it is not an exotic case but
|
|
42
|
+
* **the guaranteed consequence of two agents editing one source file**, because
|
|
43
|
+
* the manifest collapses a whole file to a single line. On the real repository,
|
|
44
|
+
* two branches editing different regions of one hook script auto-merge with
|
|
45
|
+
* both edits present and the manifest is the ONLY conflicting path — a derived
|
|
46
|
+
* file turning a clean merge into a failed one.
|
|
47
|
+
*
|
|
48
|
+
* So the two artifacts diverge here, and only here.
|
|
49
|
+
* {@link DERIVED_ENTRY_RESOLUTION_PATHS} names the manifest: a contested entry
|
|
50
|
+
* takes our side, the merge finishes, and the file is announced as stale until
|
|
51
|
+
* the generator runs against the whole tree. The ledger keeps conflicting,
|
|
52
|
+
* because it has never had this problem to solve — both decisions are argued at
|
|
53
|
+
* that constant.
|
|
38
54
|
*
|
|
39
55
|
* ## Why the driver cannot simply run the generators
|
|
40
56
|
*
|
|
@@ -87,7 +103,9 @@
|
|
|
87
103
|
* node scripts/merge-generated-artifact.mjs --base %O --ours %A --theirs %B --path %P
|
|
88
104
|
*
|
|
89
105
|
* Exit codes:
|
|
90
|
-
* 0 — merged
|
|
106
|
+
* 0 — merged; the result was written over the `--ours` file. For the manifest
|
|
107
|
+
* this can mean "merged and deliberately stale" — the driver says so on
|
|
108
|
+
* stderr and names the regeneration command.
|
|
91
109
|
* 1 — conflict. `--ours` is left untouched (a parseable artifact plus a
|
|
92
110
|
* precise stderr reason beats a corrupted one; both sides remain in the
|
|
93
111
|
* index at stages 2 and 3), and git records the path as unmerged.
|
|
@@ -117,6 +135,63 @@ const ENTRY_LINE = /^(\s+)("(?:[^"\\]|\\.)*"|[A-Za-z_$][\w$]*)\s*:/u;
|
|
|
117
135
|
/** Header of an append-only array entry, e.g. `"dest": Object.freeze([`. */
|
|
118
136
|
const ARRAY_OPEN = /Object\.freeze\(\[\s*$/u;
|
|
119
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Artifacts whose same-key collisions are resolved to one side instead of
|
|
140
|
+
* conflicting (CodySwannGT/lisa#3822).
|
|
141
|
+
*
|
|
142
|
+
* ## Why only this file
|
|
143
|
+
*
|
|
144
|
+
* The manifest collapses each whole file to ONE line — a path and a content
|
|
145
|
+
* hash — so **two concurrent edits to the same source file are a guaranteed
|
|
146
|
+
* textual collision here**, whether or not the edits themselves conflict.
|
|
147
|
+
* Measured on the real repository: two branches editing different regions of
|
|
148
|
+
* `all/copy-overwrite/scripts/lib/kill-marks.mjs` auto-merge with both edits
|
|
149
|
+
* present, and the manifest is the ONLY conflicting path. The artifact converts
|
|
150
|
+
* a clean merge into a failed one.
|
|
151
|
+
*
|
|
152
|
+
* Neither side is right in that case and neither is wrong: the merged tree has
|
|
153
|
+
* a file that neither side described, so the correct hash is a RECOMPUTATION,
|
|
154
|
+
* not a choice. The driver cannot recompute it — measured in #3816, a driver
|
|
155
|
+
* that shells out to the generator reads a half-merged working tree and is
|
|
156
|
+
* guaranteed to be wrong about exactly the paths that caused the conflict. So
|
|
157
|
+
* the driver resolves to one side and the artifact is stale BY CONSTRUCTION
|
|
158
|
+
* until regeneration, which is announced on stderr and enforced elsewhere:
|
|
159
|
+
* `tests/unit/scripts/upstream-evidence-manifest.test.ts` asserts the
|
|
160
|
+
* generator's own `--check`, and `test-correctness` is a required gate at push
|
|
161
|
+
* and on the pull request. A stale manifest cannot reach `main`.
|
|
162
|
+
*
|
|
163
|
+
* ## Why NOT the hash ledger
|
|
164
|
+
*
|
|
165
|
+
* `src/core/lisa-owned-hash-ledger.ts` appears in **0 of 7** historical
|
|
166
|
+
* conflicts despite a 43% touch rate, and merged cleanly on both sides of two
|
|
167
|
+
* live PRs the day #3822 was filed. Its entries are append-only arrays that
|
|
168
|
+
* `unionArrayEntry` already merges exactly. It needs nothing, and a same-key
|
|
169
|
+
* collision there means something this driver does not understand — so it keeps
|
|
170
|
+
* conflicting, which is the safe answer for an artifact with no measured
|
|
171
|
+
* problem. Measuring TOUCH RATE would have ranked it a major offender;
|
|
172
|
+
* measuring CONFLICTS shows it is not an offender at all.
|
|
173
|
+
* @type {ReadonlySet<string>}
|
|
174
|
+
*/
|
|
175
|
+
export const DERIVED_ENTRY_RESOLUTION_PATHS = Object.freeze(
|
|
176
|
+
new Set(["src/core/upstream-evidence-manifest.ts"])
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Whether same-key collisions in this artifact resolve instead of conflicting.
|
|
181
|
+
*
|
|
182
|
+
* An unknown path (git supplied no `%P`, or a fixture under another name) is
|
|
183
|
+
* strict. Defaulting the other way would extend a deliberately narrow exception
|
|
184
|
+
* to every file the driver is ever mapped to, silently.
|
|
185
|
+
* @param {string | undefined} artifactPath - Repo-relative path git is merging
|
|
186
|
+
* @returns {boolean} True when one side may be taken for a contested entry
|
|
187
|
+
*/
|
|
188
|
+
export function resolvesDerivedEntries(artifactPath) {
|
|
189
|
+
return (
|
|
190
|
+
artifactPath !== undefined &&
|
|
191
|
+
DERIVED_ENTRY_RESOLUTION_PATHS.has(artifactPath)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
120
195
|
/**
|
|
121
196
|
* Compare two keys the way both generators sort theirs.
|
|
122
197
|
*
|
|
@@ -334,38 +409,76 @@ function mergeSide(hasBase, base, ours, theirs, key) {
|
|
|
334
409
|
/** Identity of an entry body (or its absence) for three-way comparison. */
|
|
335
410
|
const entryKey = lines => (lines === undefined ? ABSENT : lines.join("\n"));
|
|
336
411
|
|
|
412
|
+
/**
|
|
413
|
+
* Decide what one entry both sides changed becomes.
|
|
414
|
+
*
|
|
415
|
+
* Three outcomes, in the order they are tried: an append-only union when both
|
|
416
|
+
* sides grew the same array; a resolution to OUR side when the artifact is one
|
|
417
|
+
* whose entries are recomputed rather than authored (see
|
|
418
|
+
* {@link DERIVED_ENTRY_RESOLUTION_PATHS}); a conflict otherwise.
|
|
419
|
+
*
|
|
420
|
+
* Resolution requires BOTH sides to have the entry. A key one side deleted and
|
|
421
|
+
* the other changed says the source file was removed on one branch and edited
|
|
422
|
+
* on the other — a real disagreement about the tree, which git itself surfaces
|
|
423
|
+
* on the source path. Taking a side there would answer a question nobody asked
|
|
424
|
+
* this driver.
|
|
425
|
+
* @param {string[] | undefined} base - Merge-base entry lines
|
|
426
|
+
* @param {string[] | undefined} ours - Our entry lines
|
|
427
|
+
* @param {string[] | undefined} theirs - Their entry lines
|
|
428
|
+
* @param {boolean} resolveDerived - Whether one side may be taken
|
|
429
|
+
* @returns {{kind: "unioned" | "derived", lines: string[]} | {kind: "conflict"}} Outcome
|
|
430
|
+
*/
|
|
431
|
+
function resolveContestedEntry(base, ours, theirs, resolveDerived) {
|
|
432
|
+
if (ours === undefined || theirs === undefined) return { kind: "conflict" };
|
|
433
|
+
const unioned = unionArrayEntry(base, ours, theirs);
|
|
434
|
+
if (unioned !== undefined) return { kind: "unioned", lines: unioned };
|
|
435
|
+
if (resolveDerived) return { kind: "derived", lines: ours };
|
|
436
|
+
return { kind: "conflict" };
|
|
437
|
+
}
|
|
438
|
+
|
|
337
439
|
/**
|
|
338
440
|
* Merge one entries block.
|
|
339
441
|
* @param {Map<string, string[]> | undefined} base - Merge-base entries
|
|
340
442
|
* @param {Map<string, string[]>} ours - Our entries
|
|
341
443
|
* @param {Map<string, string[]>} theirs - Their entries
|
|
342
|
-
* @
|
|
444
|
+
* @param {boolean} resolveDerived - Whether a contested entry may take our side
|
|
445
|
+
* @returns {{ok: true, entries: Map<string, string[]>, resolved: string[]} | {ok: false, reason: string}} Merged block
|
|
343
446
|
*/
|
|
344
|
-
function mergeEntries(base, ours, theirs) {
|
|
447
|
+
function mergeEntries(base, ours, theirs, resolveDerived) {
|
|
345
448
|
const merged = new Map();
|
|
449
|
+
const resolved = [];
|
|
346
450
|
const keys = [
|
|
347
451
|
...new Set([...(base?.keys() ?? []), ...ours.keys(), ...theirs.keys()]),
|
|
348
452
|
];
|
|
349
453
|
for (const name of keys.sort(compareKeys)) {
|
|
350
454
|
const b = base?.get(name);
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
455
|
+
const straight = mergeSide(
|
|
456
|
+
base !== undefined,
|
|
457
|
+
b,
|
|
458
|
+
ours.get(name),
|
|
459
|
+
theirs.get(name),
|
|
460
|
+
entryKey
|
|
461
|
+
);
|
|
354
462
|
if (straight.ok) {
|
|
355
463
|
if (straight.value !== undefined) merged.set(name, straight.value);
|
|
356
464
|
continue;
|
|
357
465
|
}
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
466
|
+
const outcome = resolveContestedEntry(
|
|
467
|
+
b,
|
|
468
|
+
ours.get(name),
|
|
469
|
+
theirs.get(name),
|
|
470
|
+
resolveDerived
|
|
471
|
+
);
|
|
472
|
+
if (outcome.kind === "conflict") {
|
|
361
473
|
return {
|
|
362
474
|
ok: false,
|
|
363
475
|
reason: `both sides changed the entry for ${name} in incompatible ways`,
|
|
364
476
|
};
|
|
365
477
|
}
|
|
366
|
-
|
|
478
|
+
if (outcome.kind === "derived") resolved.push(name);
|
|
479
|
+
merged.set(name, outcome.lines);
|
|
367
480
|
}
|
|
368
|
-
return { ok: true, entries: merged };
|
|
481
|
+
return { ok: true, entries: merged, resolved };
|
|
369
482
|
}
|
|
370
483
|
|
|
371
484
|
/**
|
|
@@ -377,9 +490,13 @@ function mergeEntries(base, ours, theirs) {
|
|
|
377
490
|
* @param {string} base - `%O` contents ("" when the file is new on both sides)
|
|
378
491
|
* @param {string} ours - `%A` contents
|
|
379
492
|
* @param {string} theirs - `%B` contents
|
|
380
|
-
* @
|
|
493
|
+
* @param {string} [artifactPath] - `%P`, the path being merged. Omitted means
|
|
494
|
+
* unknown, which is strict: only a path in
|
|
495
|
+
* {@link DERIVED_ENTRY_RESOLUTION_PATHS} resolves a contested entry.
|
|
496
|
+
* @returns {{ok: true, text: string, resolved: string[]} | {ok: false, reason: string}} Merge result
|
|
381
497
|
*/
|
|
382
|
-
export function mergeGeneratedArtifact(base, ours, theirs) {
|
|
498
|
+
export function mergeGeneratedArtifact(base, ours, theirs, artifactPath) {
|
|
499
|
+
const resolveDerived = resolvesDerivedEntries(artifactPath);
|
|
383
500
|
const parsedOurs = parseArtifact(ours);
|
|
384
501
|
const parsedTheirs = parseArtifact(theirs);
|
|
385
502
|
if (!parsedOurs.ok)
|
|
@@ -406,6 +523,7 @@ export function mergeGeneratedArtifact(base, ours, theirs) {
|
|
|
406
523
|
parsedBase = undefined;
|
|
407
524
|
}
|
|
408
525
|
const merged = [];
|
|
526
|
+
const resolved = [];
|
|
409
527
|
for (const [index, ourChunk] of parsedOurs.chunks.entries()) {
|
|
410
528
|
const theirChunk = parsedTheirs.chunks[index];
|
|
411
529
|
const baseChunk = parsedBase?.[index];
|
|
@@ -429,16 +547,18 @@ export function mergeGeneratedArtifact(base, ours, theirs) {
|
|
|
429
547
|
const result = mergeEntries(
|
|
430
548
|
baseChunk?.entries,
|
|
431
549
|
ourChunk.entries,
|
|
432
|
-
theirChunk.entries
|
|
550
|
+
theirChunk.entries,
|
|
551
|
+
resolveDerived
|
|
433
552
|
);
|
|
434
553
|
if (!result.ok) return result;
|
|
554
|
+
resolved.push(...result.resolved);
|
|
435
555
|
merged.push({
|
|
436
556
|
kind: "entries",
|
|
437
557
|
indent: ourChunk.indent,
|
|
438
558
|
entries: result.entries,
|
|
439
559
|
});
|
|
440
560
|
}
|
|
441
|
-
return { ok: true, text: renderArtifact(merged) };
|
|
561
|
+
return { ok: true, text: renderArtifact(merged), resolved };
|
|
442
562
|
}
|
|
443
563
|
|
|
444
564
|
/**
|
|
@@ -474,6 +594,41 @@ export function parseArgs(argv) {
|
|
|
474
594
|
return options;
|
|
475
595
|
}
|
|
476
596
|
|
|
597
|
+
/** Most contested paths to name before the notice starts summarising. */
|
|
598
|
+
const NAMED_RESOLVED = 3;
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* The notice a resolved-to-one-side merge prints (CodySwannGT/lisa#3822).
|
|
602
|
+
*
|
|
603
|
+
* Says the merge SUCCEEDED and the file is nonetheless wrong, which is not the
|
|
604
|
+
* usual shape of a merge message and is the whole point: the merged tree
|
|
605
|
+
* contains bytes neither side hashed, so the recorded hash is stale by
|
|
606
|
+
* construction until the generator runs against the whole tree. Regeneration is
|
|
607
|
+
* the operator's next action and the command is named here rather than left to
|
|
608
|
+
* be looked up.
|
|
609
|
+
* @param {string} label - Path git is merging
|
|
610
|
+
* @param {readonly string[]} resolved - Keys taken from our side
|
|
611
|
+
* @returns {string} Operator-facing notice, newline-terminated
|
|
612
|
+
*/
|
|
613
|
+
export function describeResolved(label, resolved) {
|
|
614
|
+
const shown = resolved.slice(0, NAMED_RESOLVED).join(", ");
|
|
615
|
+
const rest =
|
|
616
|
+
resolved.length > NAMED_RESOLVED
|
|
617
|
+
? ` and ${resolved.length - NAMED_RESOLVED} more`
|
|
618
|
+
: "";
|
|
619
|
+
return (
|
|
620
|
+
`merge-generated-artifact: ${label} merged, and is now STALE on purpose.\n` +
|
|
621
|
+
` ${resolved.length} entr${resolved.length === 1 ? "y" : "ies"} changed on both sides (${shown}${rest}).\n` +
|
|
622
|
+
` Each records a whole file as one hash, so any two edits to that file collide here\n` +
|
|
623
|
+
` even when the edits themselves merge cleanly — as they just did. The merged tree\n` +
|
|
624
|
+
` describes bytes neither side hashed, so no side is the right answer and this\n` +
|
|
625
|
+
` driver took ours to let the merge finish. Recompute before you commit:\n` +
|
|
626
|
+
` bun run build:upstream-evidence-manifest && git add ${label}\n` +
|
|
627
|
+
` Forgetting is caught, not silent: the unit suite asserts the generator's --check,\n` +
|
|
628
|
+
` and test-correctness is required at push and on the pull request.\n`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
477
632
|
/**
|
|
478
633
|
* Run the driver against git-supplied side files.
|
|
479
634
|
* @param {readonly string[]} argv - Arguments after the script name
|
|
@@ -501,7 +656,12 @@ export function runMergeGeneratedArtifact(
|
|
|
501
656
|
return 1;
|
|
502
657
|
}
|
|
503
658
|
const [baseText, oursText, theirsText] = sides.map(side => side.content);
|
|
504
|
-
const result = mergeGeneratedArtifact(
|
|
659
|
+
const result = mergeGeneratedArtifact(
|
|
660
|
+
baseText,
|
|
661
|
+
oursText,
|
|
662
|
+
theirsText,
|
|
663
|
+
options.path
|
|
664
|
+
);
|
|
505
665
|
if (!result.ok) {
|
|
506
666
|
error(
|
|
507
667
|
`merge-generated-artifact: ${label} could not be merged mechanically — ${result.reason}.\n` +
|
|
@@ -511,6 +671,8 @@ export function runMergeGeneratedArtifact(
|
|
|
511
671
|
return 1;
|
|
512
672
|
}
|
|
513
673
|
writeFileSync(ours, result.text);
|
|
674
|
+
if (result.resolved.length > 0)
|
|
675
|
+
error(describeResolved(label, result.resolved));
|
|
514
676
|
return 0;
|
|
515
677
|
}
|
|
516
678
|
|