@fern-api/replay 0.13.0 → 0.14.1

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/dist/index.d.cts CHANGED
@@ -37,6 +37,31 @@ interface StoredPatch {
37
37
  * the generator will not start producing a file it never produced before.
38
38
  */
39
39
  user_owned?: boolean;
40
+ /**
41
+ * THEIRS snapshot — the post-customer-edit content of each file the patch
42
+ * touches. When present, replay's slow-path 3-way merge uses this directly
43
+ * as THEIRS, bypassing `applyPatchToContent(BASE, patch_content)`
44
+ * reconstruction.
45
+ *
46
+ * Why this exists: `patch_content` is a unified diff anchored to specific
47
+ * line numbers in BASE. When the generator structurally changes a file
48
+ * across regens (different types, dropped/added imports, etc.), those
49
+ * line anchors no longer survive — reconstruction fails or produces
50
+ * garbage, and the customer's customization gets lost. The snapshot
51
+ * stores the customer's intent at content level, not at line-anchor
52
+ * level. diff3 then operates on actual content (BASE vs OURS vs THEIRS)
53
+ * and naturally handles structural changes via merge semantics rather
54
+ * than line bookkeeping.
55
+ *
56
+ * Keys are post-rename paths (matching the `files` array). Files the
57
+ * patch deletes are omitted from the map; the `+++ /dev/null` marker
58
+ * in `patch_content` remains the canonical delete signal.
59
+ *
60
+ * Optional for backward compat. Existing lockfiles without this field
61
+ * auto-migrate on the first regen post-upgrade via
62
+ * `preGenerationRebase`'s migration pass.
63
+ */
64
+ theirs_snapshot?: Record<string, string>;
40
65
  }
41
66
  interface CustomizationsConfig {
42
67
  exclude?: string[];
@@ -69,6 +94,15 @@ interface FileResult {
69
94
  conflicts?: ConflictRegion[];
70
95
  conflictReason?: ConflictReason;
71
96
  conflictMetadata?: ConflictMetadata;
97
+ /**
98
+ * Lines that appeared as unchanged context in the customer's THEIRS (also
99
+ * present in BASE) but were absent from the merged output because OURS
100
+ * deleted them. diff3 sides with the deletion correctly, but the customer
101
+ * may not have realized those lines depended on the generator never
102
+ * removing them. Surfaced as a warning so the customer can re-add them
103
+ * if needed. Empty/undefined when no such lines exist.
104
+ */
105
+ droppedContextLines?: string[];
72
106
  }
73
107
  interface ConflictRegion {
74
108
  startLine: number;
@@ -80,6 +114,14 @@ interface MergeResult {
80
114
  content: string;
81
115
  hasConflicts: boolean;
82
116
  conflicts: ConflictRegion[];
117
+ /**
118
+ * Lines present in BOTH base and theirs but absent from the merged output.
119
+ * These are lines the customer's THEIRS treated as context; OURS deleted
120
+ * them; diff3 sided with the deletion. Empty when no such lines exist.
121
+ * Only populated when `hasConflicts` is false — when conflicts surface,
122
+ * the customer already knows something needs review.
123
+ */
124
+ droppedContextLines?: string[];
83
125
  }
84
126
  interface CommitInfo {
85
127
  sha: string;
@@ -182,14 +224,14 @@ declare class LockfileManager {
182
224
  save(): void;
183
225
  addGeneration(record: GenerationRecord): void;
184
226
  addPatch(patch: StoredPatch): void;
185
- updatePatch(patchId: string, updates: Partial<Pick<StoredPatch, "base_generation" | "patch_content" | "content_hash" | "files" | "status" | "user_owned">>): void;
227
+ updatePatch(patchId: string, updates: Partial<Pick<StoredPatch, "base_generation" | "patch_content" | "content_hash" | "files" | "status" | "user_owned" | "theirs_snapshot">>): void;
186
228
  removePatch(patchId: string): void;
187
229
  clearPatches(): void;
188
230
  addForgottenHash(hash: string): void;
189
231
  getUnresolvedPatches(): StoredPatch[];
190
232
  getResolvingPatches(): StoredPatch[];
191
233
  markPatchUnresolved(patchId: string): void;
192
- markPatchResolved(patchId: string, updates: Pick<StoredPatch, "patch_content" | "content_hash" | "base_generation" | "files">): void;
234
+ markPatchResolved(patchId: string, updates: Pick<StoredPatch, "patch_content" | "content_hash" | "base_generation" | "files" | "theirs_snapshot">): void;
193
235
  getPatches(): StoredPatch[];
194
236
  setReplaySkippedAt(timestamp: string): void;
195
237
  clearReplaySkippedAt(): void;
@@ -346,6 +388,39 @@ declare class ReplayApplicator {
346
388
  private renameCache;
347
389
  private treeExistsCache;
348
390
  private fileTheirsAccumulator;
391
+ /**
392
+ * Apply mode for the current `applyPatches` invocation. Set at the start
393
+ * of `applyPatches`, read by `mergeFile` to decide:
394
+ * - whether to skip the intra-loop marker strip (kept in `applyPatches`)
395
+ * - whether to populate the accumulator after a conflicted merge
396
+ * (resolve mode populates so subsequent patches on the same file
397
+ * can use patch A's THEIRS as a structurally-correct merge base
398
+ * when their diff was authored against post-A structure)
399
+ * - whether to retry THEIRS reconstruction against the accumulator
400
+ * when the BASE-relative reconstruction produced markers from a
401
+ * cross-patch context mismatch
402
+ */
403
+ private currentApplyMode;
404
+ /**
405
+ * Set of files that appear in 2+ patches in the current `applyPatches`
406
+ * invocation. Computed at the top of `applyPatches`. Read by `mergeFile`
407
+ * and `populateAccumulatorForPatch` to decide whether to use the patch's
408
+ * `theirs_snapshot` directly as THEIRS (snapshot-as-primary) or fall
409
+ * back to line-anchored reconstruction (FER-9525 cross-patch isolation).
410
+ *
411
+ * Snapshot-as-primary is correct when the patch owns its file (no other
412
+ * patch in this run touches it): the snapshot is what the customer
413
+ * intends for that file post-regen, regardless of generator structural
414
+ * changes that would invalidate the diff's line anchors.
415
+ *
416
+ * When a file IS shared, snapshots are CUMULATIVE across the patches
417
+ * touching it (each one's snapshot includes prior patches' contributions).
418
+ * Using a later patch's cumulative snapshot as its individual THEIRS
419
+ * would propagate prior patches' edits into the merge — surfacing nested
420
+ * conflicts at regions the patch alone never touched. Reconstruction
421
+ * via `applyPatchToContent` is required there.
422
+ */
423
+ private sharedFiles;
349
424
  constructor(git: GitClient, lockManager: LockfileManager, outputDir: string);
350
425
  /**
351
426
  * Resolve the GenerationRecord for a patch's base_generation.
@@ -369,9 +444,31 @@ declare class ReplayApplicator {
369
444
  /**
370
445
  * Apply all patches, returning results for each.
371
446
  * Skips patches that match exclude patterns in replay.yml
447
+ *
448
+ * `applyMode` controls the post-conflict marker strategy:
449
+ * - `"replay"` (default): strip conflict markers from disk between
450
+ * iterations whenever a later patch touches the same file. Lets the
451
+ * follow-up patch's `git apply --3way` see clean OURS content,
452
+ * which is what the silent-loss fix (PR #73) relies on. The replay
453
+ * pipeline calls `revertConflictingFiles` after the loop to clean
454
+ * any markers that survive.
455
+ * - `"resolve"`: keep markers on disk. The resolve command needs the
456
+ * customer to see them. Slow-path 3-way merge naturally preserves
457
+ * A's markers and B's clean writes when their regions don't overlap;
458
+ * when they do overlap, the customer gets nested markers and
459
+ * resolves manually. Either way they aren't silently dropped.
460
+ */
461
+ applyPatches(patches: StoredPatch[], opts?: {
462
+ applyMode?: "replay" | "resolve";
463
+ }): Promise<ReplayResult[]>;
464
+ /**
465
+ * Populate accumulator after git apply succeeds, AND collect per-file
466
+ * results including any "dropped context lines" — lines that were
467
+ * present in BASE and THEIRS (unchanged context) but absent from the
468
+ * final on-disk state because OURS deleted them. This is the fast-path
469
+ * counterpart to ThreeWayMerge.computeDroppedContextLines used by the
470
+ * 3-way slow path; both must surface the same warning.
372
471
  */
373
- applyPatches(patches: StoredPatch[]): Promise<ReplayResult[]>;
374
- /** Populate accumulator after git apply succeeds. */
375
472
  private populateAccumulatorForPatch;
376
473
  private applyPatchWithFallback;
377
474
  private applyWithThreeWayMerge;
@@ -403,7 +500,13 @@ declare class ReplayCommitter {
403
500
  private outputDir;
404
501
  constructor(git: GitClient, outputDir: string);
405
502
  commitGeneration(message: string, options?: CommitOptions): Promise<string>;
406
- commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string): Promise<string>;
503
+ commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string, options?: {
504
+ buckets?: {
505
+ applied: StoredPatch[];
506
+ unresolved: StoredPatch[];
507
+ absorbed: StoredPatch[];
508
+ };
509
+ }): Promise<string>;
407
510
  createGenerationRecord(options?: CommitOptions): Promise<GenerationRecord>;
408
511
  stageAll(): Promise<void>;
409
512
  hasStagedChanges(): Promise<boolean>;
@@ -622,6 +725,45 @@ declare class ReplayService {
622
725
  * Returns the number of patches rebased.
623
726
  */
624
727
  private rebasePatches;
728
+ /**
729
+ * Read THEIRS snapshot (post-customer-edit content) for a list of files
730
+ * from the working tree on disk. Used by `rebasePatches` after a clean
731
+ * apply — disk = correctly-merged customer state at the new generation.
732
+ * Skips binary files (matches `mergeFile`/`populateAccumulatorForPatch`
733
+ * policy — binary content as `utf-8` is lossy and snapshots of it would
734
+ * never be used during apply anyway).
735
+ */
736
+ private readSnapshotFromDisk;
737
+ /**
738
+ * Read THEIRS snapshot from a git tree-ish (commit, branch, or tree).
739
+ * Used when the diff that produced `patch_content` was relative to that
740
+ * tree (e.g., `git diff currentGen HEAD --` → snapshot from HEAD).
741
+ * Skips binary files.
742
+ */
743
+ private readSnapshotFromTree;
744
+ /**
745
+ * One-shot auto-migration: backfill `theirs_snapshot` on existing
746
+ * patches that predate the snapshot encoding. Computes the snapshot by
747
+ * applying the patch's `patch_content` to its `base_generation` tree —
748
+ * yielding the patch's INDIVIDUAL contribution.
749
+ *
750
+ * **Skips patches sharing files with other patches.** HEAD's content
751
+ * for a file shared by N patches is cumulative across all of them, so
752
+ * a HEAD-fallback would falsely encode other patches' contributions
753
+ * into one patch's snapshot — breaking FER-9525 cross-patch isolation
754
+ * if the snapshot fallback later fires. Per-patch snapshots only
755
+ * make sense when the patch owns its file. Multi-patch-same-file
756
+ * legacy lockfiles keep using line-anchored reconstruction (the
757
+ * existing path); they're no worse than pre-upgrade.
758
+ *
759
+ * **Does not fall back to HEAD when BASE-reconstruction fails.** Same
760
+ * reason — HEAD content can be cumulative. Patches whose
761
+ * reconstruction fails just don't get a snapshot; legacy paths
762
+ * (PR #73's by-association gate, accumulator fallback) handle them.
763
+ *
764
+ * Skips patches that already have a snapshot.
765
+ */
766
+ private migratePatchesToSnapshot;
625
767
  /**
626
768
  * Determine whether `file` is user-owned (never produced by the generator).
627
769
  *
@@ -668,6 +810,15 @@ declare class ReplayService {
668
810
  * Called BEFORE commitGeneration() while HEAD has customer code.
669
811
  */
670
812
  private preGenerationRebase;
813
+ /**
814
+ * After applyPatches(), surface a warning for each (patch × file) where
815
+ * diff3 silently dropped lines that were unchanged context in the
816
+ * customer's THEIRS. The deletion stays on disk (correct diff3 outcome),
817
+ * but the customer must learn so they can re-add the lines if they
818
+ * relied on them. This is the "surface or preserve, never silently drop"
819
+ * contract for legitimate-deletion cases.
820
+ */
821
+ private recordDroppedContextLineWarnings;
671
822
  /**
672
823
  * After applyPatches(), strip conflict markers from conflicting files
673
824
  * so only clean content is committed. Keeps the Generated (OURS) side.
@@ -839,6 +990,8 @@ interface ResolveResult {
839
990
  patchesApplied?: number;
840
991
  /** Number of patches resolved and committed (phase 2) */
841
992
  patchesResolved?: number;
993
+ /** Non-fatal diagnostic messages (e.g., per-patch fallbacks) */
994
+ warnings?: string[];
842
995
  }
843
996
  declare function resolve(outputDir: string, options?: ResolveOptions): Promise<ResolveResult>;
844
997
 
package/dist/index.d.ts CHANGED
@@ -37,6 +37,31 @@ interface StoredPatch {
37
37
  * the generator will not start producing a file it never produced before.
38
38
  */
39
39
  user_owned?: boolean;
40
+ /**
41
+ * THEIRS snapshot — the post-customer-edit content of each file the patch
42
+ * touches. When present, replay's slow-path 3-way merge uses this directly
43
+ * as THEIRS, bypassing `applyPatchToContent(BASE, patch_content)`
44
+ * reconstruction.
45
+ *
46
+ * Why this exists: `patch_content` is a unified diff anchored to specific
47
+ * line numbers in BASE. When the generator structurally changes a file
48
+ * across regens (different types, dropped/added imports, etc.), those
49
+ * line anchors no longer survive — reconstruction fails or produces
50
+ * garbage, and the customer's customization gets lost. The snapshot
51
+ * stores the customer's intent at content level, not at line-anchor
52
+ * level. diff3 then operates on actual content (BASE vs OURS vs THEIRS)
53
+ * and naturally handles structural changes via merge semantics rather
54
+ * than line bookkeeping.
55
+ *
56
+ * Keys are post-rename paths (matching the `files` array). Files the
57
+ * patch deletes are omitted from the map; the `+++ /dev/null` marker
58
+ * in `patch_content` remains the canonical delete signal.
59
+ *
60
+ * Optional for backward compat. Existing lockfiles without this field
61
+ * auto-migrate on the first regen post-upgrade via
62
+ * `preGenerationRebase`'s migration pass.
63
+ */
64
+ theirs_snapshot?: Record<string, string>;
40
65
  }
41
66
  interface CustomizationsConfig {
42
67
  exclude?: string[];
@@ -69,6 +94,15 @@ interface FileResult {
69
94
  conflicts?: ConflictRegion[];
70
95
  conflictReason?: ConflictReason;
71
96
  conflictMetadata?: ConflictMetadata;
97
+ /**
98
+ * Lines that appeared as unchanged context in the customer's THEIRS (also
99
+ * present in BASE) but were absent from the merged output because OURS
100
+ * deleted them. diff3 sides with the deletion correctly, but the customer
101
+ * may not have realized those lines depended on the generator never
102
+ * removing them. Surfaced as a warning so the customer can re-add them
103
+ * if needed. Empty/undefined when no such lines exist.
104
+ */
105
+ droppedContextLines?: string[];
72
106
  }
73
107
  interface ConflictRegion {
74
108
  startLine: number;
@@ -80,6 +114,14 @@ interface MergeResult {
80
114
  content: string;
81
115
  hasConflicts: boolean;
82
116
  conflicts: ConflictRegion[];
117
+ /**
118
+ * Lines present in BOTH base and theirs but absent from the merged output.
119
+ * These are lines the customer's THEIRS treated as context; OURS deleted
120
+ * them; diff3 sided with the deletion. Empty when no such lines exist.
121
+ * Only populated when `hasConflicts` is false — when conflicts surface,
122
+ * the customer already knows something needs review.
123
+ */
124
+ droppedContextLines?: string[];
83
125
  }
84
126
  interface CommitInfo {
85
127
  sha: string;
@@ -182,14 +224,14 @@ declare class LockfileManager {
182
224
  save(): void;
183
225
  addGeneration(record: GenerationRecord): void;
184
226
  addPatch(patch: StoredPatch): void;
185
- updatePatch(patchId: string, updates: Partial<Pick<StoredPatch, "base_generation" | "patch_content" | "content_hash" | "files" | "status" | "user_owned">>): void;
227
+ updatePatch(patchId: string, updates: Partial<Pick<StoredPatch, "base_generation" | "patch_content" | "content_hash" | "files" | "status" | "user_owned" | "theirs_snapshot">>): void;
186
228
  removePatch(patchId: string): void;
187
229
  clearPatches(): void;
188
230
  addForgottenHash(hash: string): void;
189
231
  getUnresolvedPatches(): StoredPatch[];
190
232
  getResolvingPatches(): StoredPatch[];
191
233
  markPatchUnresolved(patchId: string): void;
192
- markPatchResolved(patchId: string, updates: Pick<StoredPatch, "patch_content" | "content_hash" | "base_generation" | "files">): void;
234
+ markPatchResolved(patchId: string, updates: Pick<StoredPatch, "patch_content" | "content_hash" | "base_generation" | "files" | "theirs_snapshot">): void;
193
235
  getPatches(): StoredPatch[];
194
236
  setReplaySkippedAt(timestamp: string): void;
195
237
  clearReplaySkippedAt(): void;
@@ -346,6 +388,39 @@ declare class ReplayApplicator {
346
388
  private renameCache;
347
389
  private treeExistsCache;
348
390
  private fileTheirsAccumulator;
391
+ /**
392
+ * Apply mode for the current `applyPatches` invocation. Set at the start
393
+ * of `applyPatches`, read by `mergeFile` to decide:
394
+ * - whether to skip the intra-loop marker strip (kept in `applyPatches`)
395
+ * - whether to populate the accumulator after a conflicted merge
396
+ * (resolve mode populates so subsequent patches on the same file
397
+ * can use patch A's THEIRS as a structurally-correct merge base
398
+ * when their diff was authored against post-A structure)
399
+ * - whether to retry THEIRS reconstruction against the accumulator
400
+ * when the BASE-relative reconstruction produced markers from a
401
+ * cross-patch context mismatch
402
+ */
403
+ private currentApplyMode;
404
+ /**
405
+ * Set of files that appear in 2+ patches in the current `applyPatches`
406
+ * invocation. Computed at the top of `applyPatches`. Read by `mergeFile`
407
+ * and `populateAccumulatorForPatch` to decide whether to use the patch's
408
+ * `theirs_snapshot` directly as THEIRS (snapshot-as-primary) or fall
409
+ * back to line-anchored reconstruction (FER-9525 cross-patch isolation).
410
+ *
411
+ * Snapshot-as-primary is correct when the patch owns its file (no other
412
+ * patch in this run touches it): the snapshot is what the customer
413
+ * intends for that file post-regen, regardless of generator structural
414
+ * changes that would invalidate the diff's line anchors.
415
+ *
416
+ * When a file IS shared, snapshots are CUMULATIVE across the patches
417
+ * touching it (each one's snapshot includes prior patches' contributions).
418
+ * Using a later patch's cumulative snapshot as its individual THEIRS
419
+ * would propagate prior patches' edits into the merge — surfacing nested
420
+ * conflicts at regions the patch alone never touched. Reconstruction
421
+ * via `applyPatchToContent` is required there.
422
+ */
423
+ private sharedFiles;
349
424
  constructor(git: GitClient, lockManager: LockfileManager, outputDir: string);
350
425
  /**
351
426
  * Resolve the GenerationRecord for a patch's base_generation.
@@ -369,9 +444,31 @@ declare class ReplayApplicator {
369
444
  /**
370
445
  * Apply all patches, returning results for each.
371
446
  * Skips patches that match exclude patterns in replay.yml
447
+ *
448
+ * `applyMode` controls the post-conflict marker strategy:
449
+ * - `"replay"` (default): strip conflict markers from disk between
450
+ * iterations whenever a later patch touches the same file. Lets the
451
+ * follow-up patch's `git apply --3way` see clean OURS content,
452
+ * which is what the silent-loss fix (PR #73) relies on. The replay
453
+ * pipeline calls `revertConflictingFiles` after the loop to clean
454
+ * any markers that survive.
455
+ * - `"resolve"`: keep markers on disk. The resolve command needs the
456
+ * customer to see them. Slow-path 3-way merge naturally preserves
457
+ * A's markers and B's clean writes when their regions don't overlap;
458
+ * when they do overlap, the customer gets nested markers and
459
+ * resolves manually. Either way they aren't silently dropped.
460
+ */
461
+ applyPatches(patches: StoredPatch[], opts?: {
462
+ applyMode?: "replay" | "resolve";
463
+ }): Promise<ReplayResult[]>;
464
+ /**
465
+ * Populate accumulator after git apply succeeds, AND collect per-file
466
+ * results including any "dropped context lines" — lines that were
467
+ * present in BASE and THEIRS (unchanged context) but absent from the
468
+ * final on-disk state because OURS deleted them. This is the fast-path
469
+ * counterpart to ThreeWayMerge.computeDroppedContextLines used by the
470
+ * 3-way slow path; both must surface the same warning.
372
471
  */
373
- applyPatches(patches: StoredPatch[]): Promise<ReplayResult[]>;
374
- /** Populate accumulator after git apply succeeds. */
375
472
  private populateAccumulatorForPatch;
376
473
  private applyPatchWithFallback;
377
474
  private applyWithThreeWayMerge;
@@ -403,7 +500,13 @@ declare class ReplayCommitter {
403
500
  private outputDir;
404
501
  constructor(git: GitClient, outputDir: string);
405
502
  commitGeneration(message: string, options?: CommitOptions): Promise<string>;
406
- commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string): Promise<string>;
503
+ commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string, options?: {
504
+ buckets?: {
505
+ applied: StoredPatch[];
506
+ unresolved: StoredPatch[];
507
+ absorbed: StoredPatch[];
508
+ };
509
+ }): Promise<string>;
407
510
  createGenerationRecord(options?: CommitOptions): Promise<GenerationRecord>;
408
511
  stageAll(): Promise<void>;
409
512
  hasStagedChanges(): Promise<boolean>;
@@ -622,6 +725,45 @@ declare class ReplayService {
622
725
  * Returns the number of patches rebased.
623
726
  */
624
727
  private rebasePatches;
728
+ /**
729
+ * Read THEIRS snapshot (post-customer-edit content) for a list of files
730
+ * from the working tree on disk. Used by `rebasePatches` after a clean
731
+ * apply — disk = correctly-merged customer state at the new generation.
732
+ * Skips binary files (matches `mergeFile`/`populateAccumulatorForPatch`
733
+ * policy — binary content as `utf-8` is lossy and snapshots of it would
734
+ * never be used during apply anyway).
735
+ */
736
+ private readSnapshotFromDisk;
737
+ /**
738
+ * Read THEIRS snapshot from a git tree-ish (commit, branch, or tree).
739
+ * Used when the diff that produced `patch_content` was relative to that
740
+ * tree (e.g., `git diff currentGen HEAD --` → snapshot from HEAD).
741
+ * Skips binary files.
742
+ */
743
+ private readSnapshotFromTree;
744
+ /**
745
+ * One-shot auto-migration: backfill `theirs_snapshot` on existing
746
+ * patches that predate the snapshot encoding. Computes the snapshot by
747
+ * applying the patch's `patch_content` to its `base_generation` tree —
748
+ * yielding the patch's INDIVIDUAL contribution.
749
+ *
750
+ * **Skips patches sharing files with other patches.** HEAD's content
751
+ * for a file shared by N patches is cumulative across all of them, so
752
+ * a HEAD-fallback would falsely encode other patches' contributions
753
+ * into one patch's snapshot — breaking FER-9525 cross-patch isolation
754
+ * if the snapshot fallback later fires. Per-patch snapshots only
755
+ * make sense when the patch owns its file. Multi-patch-same-file
756
+ * legacy lockfiles keep using line-anchored reconstruction (the
757
+ * existing path); they're no worse than pre-upgrade.
758
+ *
759
+ * **Does not fall back to HEAD when BASE-reconstruction fails.** Same
760
+ * reason — HEAD content can be cumulative. Patches whose
761
+ * reconstruction fails just don't get a snapshot; legacy paths
762
+ * (PR #73's by-association gate, accumulator fallback) handle them.
763
+ *
764
+ * Skips patches that already have a snapshot.
765
+ */
766
+ private migratePatchesToSnapshot;
625
767
  /**
626
768
  * Determine whether `file` is user-owned (never produced by the generator).
627
769
  *
@@ -668,6 +810,15 @@ declare class ReplayService {
668
810
  * Called BEFORE commitGeneration() while HEAD has customer code.
669
811
  */
670
812
  private preGenerationRebase;
813
+ /**
814
+ * After applyPatches(), surface a warning for each (patch × file) where
815
+ * diff3 silently dropped lines that were unchanged context in the
816
+ * customer's THEIRS. The deletion stays on disk (correct diff3 outcome),
817
+ * but the customer must learn so they can re-add the lines if they
818
+ * relied on them. This is the "surface or preserve, never silently drop"
819
+ * contract for legitimate-deletion cases.
820
+ */
821
+ private recordDroppedContextLineWarnings;
671
822
  /**
672
823
  * After applyPatches(), strip conflict markers from conflicting files
673
824
  * so only clean content is committed. Keeps the Generated (OURS) side.
@@ -839,6 +990,8 @@ interface ResolveResult {
839
990
  patchesApplied?: number;
840
991
  /** Number of patches resolved and committed (phase 2) */
841
992
  patchesResolved?: number;
993
+ /** Non-fatal diagnostic messages (e.g., per-patch fallbacks) */
994
+ warnings?: string[];
842
995
  }
843
996
  declare function resolve(outputDir: string, options?: ResolveOptions): Promise<ResolveResult>;
844
997