@fern-api/replay 0.15.0 → 0.15.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
@@ -133,6 +133,16 @@ interface CommitInfo {
133
133
  interface ReplayConfig {
134
134
  enabled: boolean;
135
135
  }
136
+ /**
137
+ * The four mutually-exclusive code paths a `runReplay()` invocation can take.
138
+ * Selected by `ReplayService.selectFlow()` based on lockfile state and options.
139
+ *
140
+ * - `first-generation` — no lockfile yet; this run creates the initial generation record.
141
+ * - `no-patches` — lockfile exists with zero patches; only newly detected patches apply.
142
+ * - `normal-regeneration` — lockfile exists with patches; full pre-rebase + apply + post-rebase pipeline.
143
+ * - `skip-application` — `--no-replay` mode; commit generation, set replay-skipped marker, return early.
144
+ */
145
+ type FlowKind = "first-generation" | "no-patches" | "normal-regeneration" | "skip-application";
136
146
 
137
147
  /**
138
148
  * Credential detection patterns for the replay pipeline.
@@ -143,8 +153,6 @@ interface ReplayConfig {
143
153
  *
144
154
  * These patterns mirror the invariant assertions in __tests__/invariants/i4-credential-containment.ts.
145
155
  * If they diverge, the invariant tests independently catch real leaks.
146
- *
147
- * @security FER-9807
148
156
  */
149
157
  interface CredentialPattern {
150
158
  name: string;
@@ -253,7 +261,7 @@ declare class ReplayDetector {
253
261
  constructor(git: GitClient, lockManager: LockfileManager, sdkOutputDir: string);
254
262
  detectNewPatches(): Promise<DetectionResult>;
255
263
  /**
256
- * FER-10201 — Non-linear-history detection via `merge-base(prevGen, HEAD)`.
264
+ * Non-linear-history detection via `merge-base(prevGen, HEAD)`.
257
265
  *
258
266
  * After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
259
267
  * `[fern-generated]`) is orphaned but still in git's object database. The
@@ -268,7 +276,7 @@ declare class ReplayDetector {
268
276
  */
269
277
  private detectPatchesViaMergeBase;
270
278
  /**
271
- * FER-10201 — Walk `${rangeStart}..HEAD`, classify each commit, emit one
279
+ * Walk `${rangeStart}..HEAD`, classify each commit, emit one
272
280
  * `StoredPatch` per customer commit. Body shared by the linear path
273
281
  * (rangeStart = lastGen.commit_sha) and the merge-base path.
274
282
  *
@@ -292,12 +300,12 @@ declare class ReplayDetector {
292
300
  *
293
301
  * This ensures content hashes match across the no-patches and normal-
294
302
  * regeneration flows, which store formatPatch and git-diff formats
295
- * respectively (FER-9850, D5-D9).
303
+ * respectively.
296
304
  */
297
305
  computeContentHash(patchContent: string): string;
298
306
  /**
299
- * FER-9805 — Drop patches whose files were transiently created and then
300
- * deleted within the current linear detection window, and are absent from
307
+ * Drop patches whose files were transiently created and then deleted
308
+ * within the current linear detection window, and are absent from
301
309
  * HEAD at the end of the window.
302
310
  *
303
311
  * Rationale: on a merge commit, createMergeCompositePatch already diffs
@@ -322,8 +330,8 @@ declare class ReplayDetector {
322
330
  * single files from a multi-file patch would require a follow-up that
323
331
  * regenerates the patch content via `git format-patch -- <files>`. No
324
332
  * current failing test exercises this, and leaving the patch intact
325
- * preserves today's behaviour. TODO(FER-9805): revisit if a future
326
- * adversarial test demands per-file stripping.
333
+ * preserves today's behaviour. Revisit if a future adversarial test
334
+ * demands per-file stripping.
327
335
  */
328
336
  private collapseNetZeroFiles;
329
337
  /**
@@ -381,46 +389,27 @@ declare class ReplayDetector {
381
389
  */
382
390
  declare function threeWayMerge(base: string, ours: string, theirs: string): MergeResult;
383
391
 
392
+ interface AccumulatorEntry {
393
+ readonly content: string;
394
+ readonly baseGeneration: string;
395
+ }
396
+
384
397
  declare class ReplayApplicator {
385
398
  private git;
386
399
  private lockManager;
387
400
  private outputDir;
388
401
  private renameCache;
389
402
  private treeExistsCache;
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
403
  /**
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).
404
+ * Inter-patch THEIRS coordination. Created fresh in each `applyPatches`
405
+ * invocation with the run's apply mode + precomputed shared-file set.
406
+ * See `src/accumulator/MergeAccumulator.ts` for the full contract.
410
407
  *
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.
408
+ * Initialized to an empty default in the constructor so type narrowing
409
+ * works for callers that read it before any `applyPatches` invocation
410
+ * (e.g., the `getAccumulatorSnapshot` test accessor).
422
411
  */
423
- private sharedFiles;
412
+ private accumulator;
424
413
  constructor(git: GitClient, lockManager: LockfileManager, outputDir: string);
425
414
  /**
426
415
  * Resolve the GenerationRecord for a patch's base_generation.
@@ -428,19 +417,14 @@ declare class ReplayApplicator {
428
417
  * when base_generation isn't a tracked generation (commit-scan patches).
429
418
  */
430
419
  private resolveBaseGeneration;
431
- /** Reset inter-patch accumulator for a new cycle. */
432
- private resetAccumulator;
433
420
  /**
434
421
  * @internal Test-only.
435
- * Returns a defensive copy of the inter-patch accumulator. Used by the
436
- * adversarial test suite (FER-9791) to assert invariant I2: after every
437
- * applied patch, the accumulator has an entry for each file the patch
438
- * touched, and the entry's content matches on-disk.
422
+ * Returns a defensive copy of the inter-patch accumulator. Used by
423
+ * invariant I2 to assert that after every applied patch, the
424
+ * accumulator has an entry for each file the patch touched, and the
425
+ * entry's content matches on-disk.
439
426
  */
440
- getAccumulatorSnapshot(): ReadonlyMap<string, {
441
- content: string;
442
- baseGeneration: string;
443
- }>;
427
+ getAccumulatorSnapshot(): ReadonlyMap<string, AccumulatorEntry>;
444
428
  /**
445
429
  * Apply all patches, returning results for each.
446
430
  * Skips patches that match exclude patterns in replay.yml
@@ -513,6 +497,59 @@ declare class ReplayCommitter {
513
497
  getTreeHash(commitSha: string): Promise<string>;
514
498
  }
515
499
 
500
+ /**
501
+ * Inputs for one ownership resolution call.
502
+ *
503
+ * `originalFileName` is the pre-rename path used when the generator moved
504
+ * the file between generations — tree lookups at ancestor generations need
505
+ * the original path because that's where the generator produced the content.
506
+ *
507
+ * `warnedGens` and `warnings` are mutable accumulators threaded across calls
508
+ * within a single replay run. The canonical path appends a one-time warning
509
+ * when a base generation tree is unreachable (shallow clone / aggressive GC)
510
+ * so the customer sees a single message per affected base, not one per file.
511
+ */
512
+ interface OwnershipContext {
513
+ readonly file: string;
514
+ readonly originalFileName?: string;
515
+ readonly patch: StoredPatch;
516
+ readonly currentGenSha: string;
517
+ readonly fernignorePatterns: readonly string[];
518
+ readonly warnedGens: Set<string>;
519
+ readonly warnings: string[];
520
+ }
521
+ /**
522
+ * Single canonical home for "is this file user-owned?" resolution. Every
523
+ * service-level check (preGenerationRebase, rebasePatches, trimAbsorbedFiles)
524
+ * routes through `resolveCanonical`.
525
+ *
526
+ * The earlier `rebasePatches` inline variant (`resolveAtCurrentGen`) was
527
+ * collapsed under FER-10386 once pre-flight investigation showed its
528
+ * narrower fall-back was harmless on every reachable scenario but worse
529
+ * on the unreachable-base-tree case. See `docs/known-divergences.md`
530
+ * entry 5 for the closed-out divergence record.
531
+ */
532
+ declare class FileOwnership {
533
+ private readonly git;
534
+ constructor(git: GitClient);
535
+ /**
536
+ * Canonical resolution. Returns true if `file` is user-owned per:
537
+ *
538
+ * 1. `patch.user_owned === true` — authoritative, persisted flag.
539
+ * 2. `file === ".fernignore"` — always user-owned.
540
+ * 3. File matches a `.fernignore` pattern (via minimatch).
541
+ * 4. Patch content shows `--- /dev/null` for this file (legacy-safe
542
+ * signal for patches predating `user_owned` or whose flag was lost).
543
+ * 5. Tree-based check against `patch.base_generation` when reachable
544
+ * AND distinct from `currentGenSha`. Unreachable base → conservative
545
+ * user-owned (silent-absorption safety net), with a one-time warning
546
+ * per base SHA.
547
+ * 6. Final fallback: check `currentGenSha` when no usable base exists
548
+ * (legacy one-gen lockfile).
549
+ */
550
+ resolveCanonical(ctx: OwnershipContext): Promise<boolean>;
551
+ }
552
+
516
553
  interface ConflictDetail {
517
554
  patchId: string;
518
555
  patchMessage: string;
@@ -578,7 +615,7 @@ interface ApplyOptions {
578
615
  * Contract for consumers (e.g., generator-cli pipeline inserting AutoVersionStep):
579
616
  *
580
617
  * - Between `prepareReplay` and `applyPreparedReplay`, HEAD is at the new
581
- * `[fern-generated]` commit (unless `_prepared.terminal === true`, e.g. dry-run).
618
+ * `[fern-generated]` commit (unless `_prepared.kind === "terminal"`, e.g. dry-run).
582
619
  * - Consumers MAY land additional commits on HEAD between phases. `applyPatches`
583
620
  * runs against current HEAD at phase-2 call time, so mid-phase commits are
584
621
  * respected. `rebasePatches` uses `currentGenerationSha` (the pure `[fern-generated]`
@@ -595,7 +632,7 @@ interface ApplyOptions {
595
632
  * prep.previousGenerationSha != null && prep.flow !== "skip-application"
596
633
  */
597
634
  interface ReplayPreparation {
598
- flow: "first-generation" | "no-patches" | "normal-regeneration" | "skip-application";
635
+ flow: FlowKind;
599
636
  /** lockfile.current_generation from BEFORE this run; null iff no prior generation exists */
600
637
  previousGenerationSha: string | null;
601
638
  /** SHA of the new [fern-generated] commit created by prepareReplay (or HEAD for dry-run terminals) */
@@ -605,12 +642,26 @@ interface ReplayPreparation {
605
642
  /** @internal Opaque state consumed by applyPreparedReplay */
606
643
  readonly _prepared: InternalPreparationState;
607
644
  }
608
- /** @internal */
609
- interface InternalPreparationState {
610
- flow: "first-generation" | "no-patches" | "normal-regeneration" | "skip-application";
611
- /** true → applyPreparedReplay returns preparedReport without disk/git work (dry-run path) */
612
- terminal: boolean;
613
- preparedReport?: ReplayReport;
645
+ /**
646
+ * @internal
647
+ * Opaque state passed from `prepareReplay` to `applyPreparedReplay`.
648
+ *
649
+ * Discriminated by `kind`:
650
+ * - `"terminal"` — phase 2 returns `preparedReport` without disk/git work
651
+ * (dry-run, first-generation with no patch work, no-patches with no
652
+ * detection results). Apply is a no-op.
653
+ * - `"active"` — phase 2 must apply patches, rebase, save lockfile, commit.
654
+ * Carries the patch set + cached phase-1 state.
655
+ */
656
+ type InternalPreparationState = InternalPreparationTerminal | InternalPreparationActive;
657
+ interface InternalPreparationTerminal {
658
+ readonly kind: "terminal";
659
+ readonly flow: FlowKind;
660
+ readonly preparedReport: ReplayReport;
661
+ }
662
+ interface InternalPreparationActive {
663
+ readonly kind: "active";
664
+ readonly flow: FlowKind;
614
665
  /** post-preGenRebase, post-detection, post-absorption (references, not copies) */
615
666
  patchesToApply: StoredPatch[];
616
667
  /** subset of patchesToApply that were freshly detected this cycle (vs already in the lockfile) */
@@ -629,33 +680,43 @@ interface InternalPreparationState {
629
680
  optionsSnapshot?: ReplayOptions;
630
681
  }
631
682
  declare class ReplayService {
632
- private git;
633
- private detector;
634
- private applicator;
635
- private committer;
636
- private lockManager;
637
- private outputDir;
683
+ /** @internal Used by Flow implementations in `src/flows/`. */
684
+ git: GitClient;
685
+ /** @internal Used by Flow implementations in `src/flows/`. */
686
+ detector: ReplayDetector;
687
+ /** @internal Used by Flow implementations in `src/flows/`. */
688
+ applicator: ReplayApplicator;
689
+ /** @internal Used by Flow implementations in `src/flows/`. */
690
+ committer: ReplayCommitter;
691
+ /** @internal Used by Flow implementations in `src/flows/`. */
692
+ lockManager: LockfileManager;
693
+ /** @internal Used by Flow implementations in `src/flows/`. */
694
+ fileOwnership: FileOwnership;
695
+ /** @internal Used by Flow implementations in `src/flows/`. */
696
+ outputDir: string;
638
697
  /** @internal Test-only. Captures the last ReplayResult[] returned from applicator.applyPatches(). */
639
- private _lastApplyResults;
698
+ _lastApplyResults: ReplayResult[];
640
699
  /**
641
700
  * Service-level warnings accumulated during a single runReplay() call.
642
701
  * Merged with `detector.warnings` into `ReplayReport.warnings` by each flow handler.
643
- * Populated by `isFileUserOwned` when a patch's base generation tree is unreachable
644
- * (shallow clone / aggressive `git gc --prune`) and we fall back to a conservative
645
- * heuristic. Cleared at the top of `runReplay()`.
702
+ * Populated by `FileOwnership.resolveCanonical` when a patch's base generation tree
703
+ * is unreachable (shallow clone / aggressive `git gc --prune`) and we fall back to a
704
+ * conservative heuristic. Cleared at the top of `runReplay()`.
705
+ *
706
+ * @internal Used by Flow implementations in `src/flows/`.
646
707
  */
647
- private warnings;
708
+ warnings: string[];
648
709
  /**
649
710
  * @internal Test-only accessor.
650
711
  * Returns the ReplayApplicator instance used for the last run. Tests use this
651
- * to inspect the inter-patch accumulator after runReplay() completes (FER-9791, I2).
712
+ * to inspect the inter-patch accumulator after runReplay() completes (invariant I2).
652
713
  */
653
714
  get applicatorRef(): ReplayApplicator;
654
715
  /**
655
716
  * @internal Test-only accessor.
656
717
  * Returns the ReplayResult[] from the most recent applyPatches() call. Tests use
657
- * this to filter applied-vs-conflicted-vs-absorbed patches for invariant assertions
658
- * (FER-9791). Returns an empty array if no patches have been applied yet.
718
+ * this to filter applied-vs-conflicted-vs-absorbed patches for invariant
719
+ * assertions. Returns an empty array if no patches have been applied yet.
659
720
  */
660
721
  getLastResults(): ReadonlyArray<ReplayResult>;
661
722
  constructor(outputDir: string, _config: ReplayConfig);
@@ -682,6 +743,16 @@ declare class ReplayService {
682
743
  * its own post-commit logic), this returns the precomputed report.
683
744
  */
684
745
  applyPreparedReplay(prep: ReplayPreparation, options?: ApplyOptions): Promise<ReplayReport>;
746
+ /**
747
+ * Construct the flow for the current run. `selectFlow` is called from
748
+ * `prepareReplay` and decides based on options + lockfile state.
749
+ */
750
+ private selectFlow;
751
+ /**
752
+ * Construct a flow given its kind. Used by `applyPreparedReplay` to
753
+ * route to the same kind that was selected in phase 1.
754
+ */
755
+ private flowForKind;
685
756
  /**
686
757
  * Backwards-compatible atomic entry point. Composes `prepareReplay` + `applyPreparedReplay`.
687
758
  * Behavior is byte-identical to the pre-split implementation for all existing callers.
@@ -703,28 +774,20 @@ declare class ReplayService {
703
774
  baseBranchHead?: string;
704
775
  }): Promise<void>;
705
776
  private determineFlow;
706
- private firstGenerationReport;
707
- private _prepareFirstGeneration;
708
- private _applyFirstGeneration;
709
- /**
710
- * Skip-application mode phase 1: commit the generation and update the
711
- * in-memory lockfile, but don't detect or apply patches. Sets a marker
712
- * so the next normal run skips revert detection in preGenerationRebase().
713
- *
714
- * Disk save is deferred to `_applySkipApplication`.
715
- */
716
- private _prepareSkipApplication;
717
- private _applySkipApplication;
718
- private _prepareNoPatchesRegeneration;
719
- private _applyNoPatchesRegeneration;
720
- private _prepareNormalRegeneration;
721
- private _applyNormalRegeneration;
722
777
  /**
723
778
  * Rebase cleanly applied patches so they are relative to the current generation.
724
779
  * This prevents recurring conflicts on subsequent regenerations.
725
780
  * Returns the number of patches rebased.
781
+ *
782
+ * @internal Used by Flow implementations in `src/flows/`.
726
783
  */
727
- private rebasePatches;
784
+ rebasePatches(results: ReplayResult[], currentGenSha: string): Promise<{
785
+ absorbed: number;
786
+ repointed: number;
787
+ contentRebased: number;
788
+ keptAsUserOwned: number;
789
+ absorbedPatchIds: Set<string>;
790
+ }>;
728
791
  /**
729
792
  * Read THEIRS snapshot (post-customer-edit content) for a list of files
730
793
  * from the working tree on disk. Used by `rebasePatches` after a clean
@@ -735,17 +798,28 @@ declare class ReplayService {
735
798
  */
736
799
  private readSnapshotFromDisk;
737
800
  /**
738
- * FER-10203: returns true when a `[fern-autoversion]` commit between
739
- * `fromSha` and `toSha` touched any of the patch's `files`. Re-classifies
740
- * each grep match via `isGenerationCommit` so a customer commit that
741
- * merely quotes the marker in its body doesn't false-positive.
801
+ * Detects autoversion contamination — returns true when a
802
+ * `[fern-autoversion]` commit between `fromSha` and `toSha` touched any
803
+ * of the patch's `files`. Re-classifies each grep match via
804
+ * `isGenerationCommit` so a customer commit that merely quotes the
805
+ * marker in its body doesn't false-positive.
806
+ *
807
+ * Used by preGenerationRebase to skip the customer-content rebuild for
808
+ * patches whose files were last touched by an autoversion step — a
809
+ * naive `git diff currentGen HEAD` there would capture the
810
+ * placeholder→semver swap as customer customization.
742
811
  */
743
812
  private hasAutoversionContamination;
744
813
  /**
745
- * FER-10203: rebuild patch_content from `theirs_snapshot` (captured at
746
- * detection time, predates pipeline content) rather than a HEAD-relative
747
- * diff. Returns `null` when snapshot is missing or doesn't cover every
748
- * file in `patch.files` — caller leaves the patch unchanged.
814
+ * Rebuild patch_content from `theirs_snapshot` (captured at detection
815
+ * time, predates pipeline content) rather than a HEAD-relative diff.
816
+ * Returns `null` when snapshot is missing or doesn't cover every file
817
+ * in `patch.files` — caller leaves the patch unchanged.
818
+ *
819
+ * Used as the autoversion-contamination escape hatch: when the
820
+ * HEAD-relative diff would capture pipeline content (autoversion's
821
+ * placeholder swap) as customer customization, the snapshot path
822
+ * preserves the customer's actual intent.
749
823
  */
750
824
  private computeSnapshotBasedPatchDiff;
751
825
  /**
@@ -764,7 +838,7 @@ declare class ReplayService {
764
838
  * **Skips patches sharing files with other patches.** HEAD's content
765
839
  * for a file shared by N patches is cumulative across all of them, so
766
840
  * a HEAD-fallback would falsely encode other patches' contributions
767
- * into one patch's snapshot — breaking FER-9525 cross-patch isolation
841
+ * into one patch's snapshot — breaking cross-patch isolation
768
842
  * if the snapshot fallback later fires. Per-patch snapshots only
769
843
  * make sense when the patch owns its file. Multi-patch-same-file
770
844
  * legacy lockfiles keep using line-anchored reconstruction (the
@@ -778,37 +852,6 @@ declare class ReplayService {
778
852
  * Skips patches that already have a snapshot.
779
853
  */
780
854
  private migratePatchesToSnapshot;
781
- /**
782
- * Determine whether `file` is user-owned (never produced by the generator).
783
- *
784
- * Resolution order (first match wins):
785
- * 1. `patch.user_owned === true` — authoritative, persisted across cycles.
786
- * 2. `.fernignore` itself, or file matches a .fernignore pattern.
787
- * 3. Legacy-safe signal: `patch_content` shows this file as a `--- /dev/null`
788
- * creation. Works for patches that predate the `user_owned` field or
789
- * whose flag was lost. Immune to rename tricks — relies on raw patch text.
790
- * 4. Tree-based check against `patch.base_generation` when it is reachable
791
- * AND distinct from `currentGenSha`. If the base tree is unreachable
792
- * (shallow clone / aggressive `git gc --prune`), emit a one-time
793
- * customer-actionable warning (deduplicated via warnedGens) and
794
- * conservatively treat as user-owned — strictly safer than silent
795
- * absorption (FER-9809).
796
- * 5. Final fallback — check `currentGenSha` when there is no usable base
797
- * (legacy one-gen lockfile). Preserves pre-fix behavior for that edge.
798
- *
799
- * `originalFileName` is the pre-rename path when the generator moved the
800
- * file between the patch's base and the current generation. Tree lookups
801
- * at ancestor generations use the original path — that's where the
802
- * generator produced the content.
803
- */
804
- private isFileUserOwned;
805
- /**
806
- * Returns true if `file`'s diff section in `patchContent` starts with
807
- * `--- /dev/null`, meaning this file was introduced by the patch (user
808
- * creation). Used as a legacy-safe fallback when `patch.user_owned` is
809
- * absent or cleared.
810
- */
811
- private fileIsCreationInPatchContent;
812
855
  /**
813
856
  * For conflict patches with mixed results (some files merged, some conflicted),
814
857
  * check if the cleanly merged files were absorbed by the generator (empty diff).
@@ -823,7 +866,12 @@ declare class ReplayService {
823
866
  * Pre-generation rebase: update patches using the customer's current state.
824
867
  * Called BEFORE commitGeneration() while HEAD has customer code.
825
868
  */
826
- private preGenerationRebase;
869
+ /** @internal Used by Flow implementations in `src/flows/`. */
870
+ preGenerationRebase(patches: StoredPatch[]): Promise<{
871
+ conflictResolved: number;
872
+ conflictAbsorbed: number;
873
+ contentRefreshed: number;
874
+ }>;
827
875
  /**
828
876
  * After applyPatches(), surface a warning for each (patch × file) where
829
877
  * diff3 silently dropped lines that were unchanged context in the
@@ -832,21 +880,35 @@ declare class ReplayService {
832
880
  * relied on them. This is the "surface or preserve, never silently drop"
833
881
  * contract for legitimate-deletion cases.
834
882
  */
835
- private recordDroppedContextLineWarnings;
883
+ /** @internal Used by Flow implementations in `src/flows/`. */
884
+ recordDroppedContextLineWarnings(results: ReplayResult[]): void;
836
885
  /**
837
886
  * After applyPatches(), strip conflict markers from conflicting files
838
887
  * so only clean content is committed. Keeps the Generated (OURS) side.
839
888
  */
840
- private revertConflictingFiles;
889
+ /** @internal Used by Flow implementations in `src/flows/`. */
890
+ revertConflictingFiles(results: ReplayResult[]): void;
841
891
  /**
842
892
  * Clean up stale conflict markers left by a previous crashed run.
843
893
  * Called after commitGeneration() when HEAD is the [fern-generated] commit.
844
894
  * Restores files to their clean generated state from HEAD.
845
895
  * Skips .fernignore-protected files to prevent overwriting user content.
846
896
  */
847
- private cleanupStaleConflictMarkers;
897
+ /** @internal Used by Flow implementations in `src/flows/`. */
898
+ cleanupStaleConflictMarkers(): Promise<void>;
848
899
  private readFernignorePatterns;
849
- private buildReport;
900
+ /** @internal Used by Flow implementations in `src/flows/`. */
901
+ buildReport(flow: "first-generation" | "no-patches" | "normal-regeneration" | "skip-application", patches: StoredPatch[], results: ReplayResult[], options?: ReplayOptions, warnings?: string[], rebaseCounts?: {
902
+ absorbed: number;
903
+ repointed: number;
904
+ contentRebased: number;
905
+ keptAsUserOwned: number;
906
+ absorbedPatchIds: Set<string>;
907
+ }, preRebaseCounts?: {
908
+ conflictResolved: number;
909
+ conflictAbsorbed: number;
910
+ contentRefreshed: number;
911
+ }, patchesReverted?: number): ReplayReport;
850
912
  }
851
913
 
852
914
  interface MigrationAnalysis {
@@ -1053,4 +1115,4 @@ interface StatusGeneration {
1053
1115
  }
1054
1116
  declare function status(outputDir: string): StatusResult;
1055
1117
 
1056
- export { type ApplyOptions, type BootstrapOptions, type BootstrapResult, CREDENTIAL_PATTERNS, type CommitInfo, type CommitOptions, type ConflictDetail, type ConflictMetadata, type ConflictReason, type ConflictRegion, type CredentialPattern, type CustomizationsConfig, type DetectionResult, type DiffStat, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_BOT_NAME, FernignoreMigrator, type FileResult, type ForgetOptions, type ForgetResult, type GenerationLock, type GenerationRecord, GitClient, LockfileManager, LockfileNotFoundError, type MatchedPatch, type MergeResult, type MigrationAnalysis, type MigrationResult, type MoveDeclaration, ReplayApplicator, ReplayCommitter, type ReplayConfig, ReplayDetector, type ReplayOptions, type ReplayPreparation, type ReplayReport, type ReplayResult, ReplayService, type ResetOptions, type ResetResult, type ResolveOptions, type ResolveResult, SENSITIVE_FILE_PATTERNS, type StatusGeneration, type StatusPatch, type StatusResult, type StoredPatch, type UnresolvedPatchInfo, bootstrap, forget, isGenerationCommit, isReplayCommit, isRevertCommit, isSensitiveFile, parseRevertedMessage, parseRevertedSha, reset, resolve, scanForCredentials, status, threeWayMerge };
1118
+ export { type ApplyOptions, type BootstrapOptions, type BootstrapResult, CREDENTIAL_PATTERNS, type CommitInfo, type CommitOptions, type ConflictDetail, type ConflictMetadata, type ConflictReason, type ConflictRegion, type CredentialPattern, type CustomizationsConfig, type DetectionResult, type DiffStat, FERN_BOT_EMAIL, FERN_BOT_LOGIN, FERN_BOT_NAME, FernignoreMigrator, type FileResult, type FlowKind, type ForgetOptions, type ForgetResult, type GenerationLock, type GenerationRecord, GitClient, LockfileManager, LockfileNotFoundError, type MatchedPatch, type MergeResult, type MigrationAnalysis, type MigrationResult, type MoveDeclaration, ReplayApplicator, ReplayCommitter, type ReplayConfig, ReplayDetector, type ReplayOptions, type ReplayPreparation, type ReplayReport, type ReplayResult, ReplayService, type ResetOptions, type ResetResult, type ResolveOptions, type ResolveResult, SENSITIVE_FILE_PATTERNS, type StatusGeneration, type StatusPatch, type StatusResult, type StoredPatch, type UnresolvedPatchInfo, bootstrap, forget, isGenerationCommit, isReplayCommit, isRevertCommit, isSensitiveFile, parseRevertedMessage, parseRevertedSha, reset, resolve, scanForCredentials, status, threeWayMerge };