@fern-api/replay 0.13.0 → 0.14.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/dist/index.d.cts CHANGED
@@ -69,6 +69,15 @@ interface FileResult {
69
69
  conflicts?: ConflictRegion[];
70
70
  conflictReason?: ConflictReason;
71
71
  conflictMetadata?: ConflictMetadata;
72
+ /**
73
+ * Lines that appeared as unchanged context in the customer's THEIRS (also
74
+ * present in BASE) but were absent from the merged output because OURS
75
+ * deleted them. diff3 sides with the deletion correctly, but the customer
76
+ * may not have realized those lines depended on the generator never
77
+ * removing them. Surfaced as a warning so the customer can re-add them
78
+ * if needed. Empty/undefined when no such lines exist.
79
+ */
80
+ droppedContextLines?: string[];
72
81
  }
73
82
  interface ConflictRegion {
74
83
  startLine: number;
@@ -80,6 +89,14 @@ interface MergeResult {
80
89
  content: string;
81
90
  hasConflicts: boolean;
82
91
  conflicts: ConflictRegion[];
92
+ /**
93
+ * Lines present in BOTH base and theirs but absent from the merged output.
94
+ * These are lines the customer's THEIRS treated as context; OURS deleted
95
+ * them; diff3 sided with the deletion. Empty when no such lines exist.
96
+ * Only populated when `hasConflicts` is false — when conflicts surface,
97
+ * the customer already knows something needs review.
98
+ */
99
+ droppedContextLines?: string[];
83
100
  }
84
101
  interface CommitInfo {
85
102
  sha: string;
@@ -346,6 +363,19 @@ declare class ReplayApplicator {
346
363
  private renameCache;
347
364
  private treeExistsCache;
348
365
  private fileTheirsAccumulator;
366
+ /**
367
+ * Apply mode for the current `applyPatches` invocation. Set at the start
368
+ * of `applyPatches`, read by `mergeFile` to decide:
369
+ * - whether to skip the intra-loop marker strip (kept in `applyPatches`)
370
+ * - whether to populate the accumulator after a conflicted merge
371
+ * (resolve mode populates so subsequent patches on the same file
372
+ * can use patch A's THEIRS as a structurally-correct merge base
373
+ * when their diff was authored against post-A structure)
374
+ * - whether to retry THEIRS reconstruction against the accumulator
375
+ * when the BASE-relative reconstruction produced markers from a
376
+ * cross-patch context mismatch
377
+ */
378
+ private currentApplyMode;
349
379
  constructor(git: GitClient, lockManager: LockfileManager, outputDir: string);
350
380
  /**
351
381
  * Resolve the GenerationRecord for a patch's base_generation.
@@ -369,9 +399,31 @@ declare class ReplayApplicator {
369
399
  /**
370
400
  * Apply all patches, returning results for each.
371
401
  * Skips patches that match exclude patterns in replay.yml
402
+ *
403
+ * `applyMode` controls the post-conflict marker strategy:
404
+ * - `"replay"` (default): strip conflict markers from disk between
405
+ * iterations whenever a later patch touches the same file. Lets the
406
+ * follow-up patch's `git apply --3way` see clean OURS content,
407
+ * which is what the silent-loss fix (PR #73) relies on. The replay
408
+ * pipeline calls `revertConflictingFiles` after the loop to clean
409
+ * any markers that survive.
410
+ * - `"resolve"`: keep markers on disk. The resolve command needs the
411
+ * customer to see them. Slow-path 3-way merge naturally preserves
412
+ * A's markers and B's clean writes when their regions don't overlap;
413
+ * when they do overlap, the customer gets nested markers and
414
+ * resolves manually. Either way they aren't silently dropped.
415
+ */
416
+ applyPatches(patches: StoredPatch[], opts?: {
417
+ applyMode?: "replay" | "resolve";
418
+ }): Promise<ReplayResult[]>;
419
+ /**
420
+ * Populate accumulator after git apply succeeds, AND collect per-file
421
+ * results including any "dropped context lines" — lines that were
422
+ * present in BASE and THEIRS (unchanged context) but absent from the
423
+ * final on-disk state because OURS deleted them. This is the fast-path
424
+ * counterpart to ThreeWayMerge.computeDroppedContextLines used by the
425
+ * 3-way slow path; both must surface the same warning.
372
426
  */
373
- applyPatches(patches: StoredPatch[]): Promise<ReplayResult[]>;
374
- /** Populate accumulator after git apply succeeds. */
375
427
  private populateAccumulatorForPatch;
376
428
  private applyPatchWithFallback;
377
429
  private applyWithThreeWayMerge;
@@ -403,7 +455,13 @@ declare class ReplayCommitter {
403
455
  private outputDir;
404
456
  constructor(git: GitClient, outputDir: string);
405
457
  commitGeneration(message: string, options?: CommitOptions): Promise<string>;
406
- commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string): Promise<string>;
458
+ commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string, options?: {
459
+ buckets?: {
460
+ applied: StoredPatch[];
461
+ unresolved: StoredPatch[];
462
+ absorbed: StoredPatch[];
463
+ };
464
+ }): Promise<string>;
407
465
  createGenerationRecord(options?: CommitOptions): Promise<GenerationRecord>;
408
466
  stageAll(): Promise<void>;
409
467
  hasStagedChanges(): Promise<boolean>;
@@ -668,6 +726,15 @@ declare class ReplayService {
668
726
  * Called BEFORE commitGeneration() while HEAD has customer code.
669
727
  */
670
728
  private preGenerationRebase;
729
+ /**
730
+ * After applyPatches(), surface a warning for each (patch × file) where
731
+ * diff3 silently dropped lines that were unchanged context in the
732
+ * customer's THEIRS. The deletion stays on disk (correct diff3 outcome),
733
+ * but the customer must learn so they can re-add the lines if they
734
+ * relied on them. This is the "surface or preserve, never silently drop"
735
+ * contract for legitimate-deletion cases.
736
+ */
737
+ private recordDroppedContextLineWarnings;
671
738
  /**
672
739
  * After applyPatches(), strip conflict markers from conflicting files
673
740
  * so only clean content is committed. Keeps the Generated (OURS) side.
@@ -839,6 +906,8 @@ interface ResolveResult {
839
906
  patchesApplied?: number;
840
907
  /** Number of patches resolved and committed (phase 2) */
841
908
  patchesResolved?: number;
909
+ /** Non-fatal diagnostic messages (e.g., per-patch fallbacks) */
910
+ warnings?: string[];
842
911
  }
843
912
  declare function resolve(outputDir: string, options?: ResolveOptions): Promise<ResolveResult>;
844
913
 
package/dist/index.d.ts CHANGED
@@ -69,6 +69,15 @@ interface FileResult {
69
69
  conflicts?: ConflictRegion[];
70
70
  conflictReason?: ConflictReason;
71
71
  conflictMetadata?: ConflictMetadata;
72
+ /**
73
+ * Lines that appeared as unchanged context in the customer's THEIRS (also
74
+ * present in BASE) but were absent from the merged output because OURS
75
+ * deleted them. diff3 sides with the deletion correctly, but the customer
76
+ * may not have realized those lines depended on the generator never
77
+ * removing them. Surfaced as a warning so the customer can re-add them
78
+ * if needed. Empty/undefined when no such lines exist.
79
+ */
80
+ droppedContextLines?: string[];
72
81
  }
73
82
  interface ConflictRegion {
74
83
  startLine: number;
@@ -80,6 +89,14 @@ interface MergeResult {
80
89
  content: string;
81
90
  hasConflicts: boolean;
82
91
  conflicts: ConflictRegion[];
92
+ /**
93
+ * Lines present in BOTH base and theirs but absent from the merged output.
94
+ * These are lines the customer's THEIRS treated as context; OURS deleted
95
+ * them; diff3 sided with the deletion. Empty when no such lines exist.
96
+ * Only populated when `hasConflicts` is false — when conflicts surface,
97
+ * the customer already knows something needs review.
98
+ */
99
+ droppedContextLines?: string[];
83
100
  }
84
101
  interface CommitInfo {
85
102
  sha: string;
@@ -346,6 +363,19 @@ declare class ReplayApplicator {
346
363
  private renameCache;
347
364
  private treeExistsCache;
348
365
  private fileTheirsAccumulator;
366
+ /**
367
+ * Apply mode for the current `applyPatches` invocation. Set at the start
368
+ * of `applyPatches`, read by `mergeFile` to decide:
369
+ * - whether to skip the intra-loop marker strip (kept in `applyPatches`)
370
+ * - whether to populate the accumulator after a conflicted merge
371
+ * (resolve mode populates so subsequent patches on the same file
372
+ * can use patch A's THEIRS as a structurally-correct merge base
373
+ * when their diff was authored against post-A structure)
374
+ * - whether to retry THEIRS reconstruction against the accumulator
375
+ * when the BASE-relative reconstruction produced markers from a
376
+ * cross-patch context mismatch
377
+ */
378
+ private currentApplyMode;
349
379
  constructor(git: GitClient, lockManager: LockfileManager, outputDir: string);
350
380
  /**
351
381
  * Resolve the GenerationRecord for a patch's base_generation.
@@ -369,9 +399,31 @@ declare class ReplayApplicator {
369
399
  /**
370
400
  * Apply all patches, returning results for each.
371
401
  * Skips patches that match exclude patterns in replay.yml
402
+ *
403
+ * `applyMode` controls the post-conflict marker strategy:
404
+ * - `"replay"` (default): strip conflict markers from disk between
405
+ * iterations whenever a later patch touches the same file. Lets the
406
+ * follow-up patch's `git apply --3way` see clean OURS content,
407
+ * which is what the silent-loss fix (PR #73) relies on. The replay
408
+ * pipeline calls `revertConflictingFiles` after the loop to clean
409
+ * any markers that survive.
410
+ * - `"resolve"`: keep markers on disk. The resolve command needs the
411
+ * customer to see them. Slow-path 3-way merge naturally preserves
412
+ * A's markers and B's clean writes when their regions don't overlap;
413
+ * when they do overlap, the customer gets nested markers and
414
+ * resolves manually. Either way they aren't silently dropped.
415
+ */
416
+ applyPatches(patches: StoredPatch[], opts?: {
417
+ applyMode?: "replay" | "resolve";
418
+ }): Promise<ReplayResult[]>;
419
+ /**
420
+ * Populate accumulator after git apply succeeds, AND collect per-file
421
+ * results including any "dropped context lines" — lines that were
422
+ * present in BASE and THEIRS (unchanged context) but absent from the
423
+ * final on-disk state because OURS deleted them. This is the fast-path
424
+ * counterpart to ThreeWayMerge.computeDroppedContextLines used by the
425
+ * 3-way slow path; both must surface the same warning.
372
426
  */
373
- applyPatches(patches: StoredPatch[]): Promise<ReplayResult[]>;
374
- /** Populate accumulator after git apply succeeds. */
375
427
  private populateAccumulatorForPatch;
376
428
  private applyPatchWithFallback;
377
429
  private applyWithThreeWayMerge;
@@ -403,7 +455,13 @@ declare class ReplayCommitter {
403
455
  private outputDir;
404
456
  constructor(git: GitClient, outputDir: string);
405
457
  commitGeneration(message: string, options?: CommitOptions): Promise<string>;
406
- commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string): Promise<string>;
458
+ commitReplay(_patchCount: number, patches?: StoredPatch[], message?: string, options?: {
459
+ buckets?: {
460
+ applied: StoredPatch[];
461
+ unresolved: StoredPatch[];
462
+ absorbed: StoredPatch[];
463
+ };
464
+ }): Promise<string>;
407
465
  createGenerationRecord(options?: CommitOptions): Promise<GenerationRecord>;
408
466
  stageAll(): Promise<void>;
409
467
  hasStagedChanges(): Promise<boolean>;
@@ -668,6 +726,15 @@ declare class ReplayService {
668
726
  * Called BEFORE commitGeneration() while HEAD has customer code.
669
727
  */
670
728
  private preGenerationRebase;
729
+ /**
730
+ * After applyPatches(), surface a warning for each (patch × file) where
731
+ * diff3 silently dropped lines that were unchanged context in the
732
+ * customer's THEIRS. The deletion stays on disk (correct diff3 outcome),
733
+ * but the customer must learn so they can re-add the lines if they
734
+ * relied on them. This is the "surface or preserve, never silently drop"
735
+ * contract for legitimate-deletion cases.
736
+ */
737
+ private recordDroppedContextLineWarnings;
671
738
  /**
672
739
  * After applyPatches(), strip conflict markers from conflicting files
673
740
  * so only clean content is committed. Keeps the Generated (OURS) side.
@@ -839,6 +906,8 @@ interface ResolveResult {
839
906
  patchesApplied?: number;
840
907
  /** Number of patches resolved and committed (phase 2) */
841
908
  patchesResolved?: number;
909
+ /** Non-fatal diagnostic messages (e.g., per-patch fallbacks) */
910
+ warnings?: string[];
842
911
  }
843
912
  declare function resolve(outputDir: string, options?: ResolveOptions): Promise<ResolveResult>;
844
913