@fern-api/replay 0.15.0 → 0.15.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.
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,17 +300,15 @@ 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
- * Rationale: on a merge commit, createMergeCompositePatch already diffs
304
- * firstParent..mergeCommit so net-zero files never enter the composite. On
305
- * linear history each commit becomes its own patch, so a file that was
311
+ * Rationale: each commit becomes its own patch, so a file that was
306
312
  * briefly added and later removed survives as a create+delete pair whose
307
313
  * cumulative diff is empty. We drop such patches before they reach the
308
314
  * lockfile.
@@ -322,8 +328,8 @@ declare class ReplayDetector {
322
328
  * single files from a multi-file patch would require a follow-up that
323
329
  * regenerates the patch content via `git format-patch -- <files>`. No
324
330
  * 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.
331
+ * preserves today's behaviour. Revisit if a future adversarial test
332
+ * demands per-file stripping.
327
333
  */
328
334
  private collapseNetZeroFiles;
329
335
  /**
@@ -333,13 +339,6 @@ declare class ReplayDetector {
333
339
  * more aggressively. On typical SDK repos this is a single sub-10ms call.
334
340
  */
335
341
  private listHeadFiles;
336
- /**
337
- * Create a single composite patch for a merge commit by diffing the merge
338
- * commit against its first parent. This captures the net change of the
339
- * entire merged branch as one patch, eliminating accumulator chaining and
340
- * zero-net-change ghost conflicts.
341
- */
342
- private createMergeCompositePatch;
343
342
  /**
344
343
  * Detect patches via tree diff for non-linear history. Returns a composite patch.
345
344
  * Revert reconciliation is skipped here because tree-diff produces a single composite
@@ -381,46 +380,27 @@ declare class ReplayDetector {
381
380
  */
382
381
  declare function threeWayMerge(base: string, ours: string, theirs: string): MergeResult;
383
382
 
383
+ interface AccumulatorEntry {
384
+ readonly content: string;
385
+ readonly baseGeneration: string;
386
+ }
387
+
384
388
  declare class ReplayApplicator {
385
389
  private git;
386
390
  private lockManager;
387
391
  private outputDir;
388
392
  private renameCache;
389
393
  private treeExistsCache;
390
- private fileTheirsAccumulator;
391
394
  /**
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).
395
+ * Inter-patch THEIRS coordination. Created fresh in each `applyPatches`
396
+ * invocation with the run's apply mode + precomputed shared-file set.
397
+ * See `src/accumulator/MergeAccumulator.ts` for the full contract.
410
398
  *
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.
399
+ * Initialized to an empty default in the constructor so type narrowing
400
+ * works for callers that read it before any `applyPatches` invocation
401
+ * (e.g., the `getAccumulatorSnapshot` test accessor).
422
402
  */
423
- private sharedFiles;
403
+ private accumulator;
424
404
  constructor(git: GitClient, lockManager: LockfileManager, outputDir: string);
425
405
  /**
426
406
  * Resolve the GenerationRecord for a patch's base_generation.
@@ -428,19 +408,14 @@ declare class ReplayApplicator {
428
408
  * when base_generation isn't a tracked generation (commit-scan patches).
429
409
  */
430
410
  private resolveBaseGeneration;
431
- /** Reset inter-patch accumulator for a new cycle. */
432
- private resetAccumulator;
433
411
  /**
434
412
  * @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.
413
+ * Returns a defensive copy of the inter-patch accumulator. Used by
414
+ * invariant I2 to assert that after every applied patch, the
415
+ * accumulator has an entry for each file the patch touched, and the
416
+ * entry's content matches on-disk.
439
417
  */
440
- getAccumulatorSnapshot(): ReadonlyMap<string, {
441
- content: string;
442
- baseGeneration: string;
443
- }>;
418
+ getAccumulatorSnapshot(): ReadonlyMap<string, AccumulatorEntry>;
444
419
  /**
445
420
  * Apply all patches, returning results for each.
446
421
  * Skips patches that match exclude patterns in replay.yml
@@ -513,6 +488,59 @@ declare class ReplayCommitter {
513
488
  getTreeHash(commitSha: string): Promise<string>;
514
489
  }
515
490
 
491
+ /**
492
+ * Inputs for one ownership resolution call.
493
+ *
494
+ * `originalFileName` is the pre-rename path used when the generator moved
495
+ * the file between generations — tree lookups at ancestor generations need
496
+ * the original path because that's where the generator produced the content.
497
+ *
498
+ * `warnedGens` and `warnings` are mutable accumulators threaded across calls
499
+ * within a single replay run. The canonical path appends a one-time warning
500
+ * when a base generation tree is unreachable (shallow clone / aggressive GC)
501
+ * so the customer sees a single message per affected base, not one per file.
502
+ */
503
+ interface OwnershipContext {
504
+ readonly file: string;
505
+ readonly originalFileName?: string;
506
+ readonly patch: StoredPatch;
507
+ readonly currentGenSha: string;
508
+ readonly fernignorePatterns: readonly string[];
509
+ readonly warnedGens: Set<string>;
510
+ readonly warnings: string[];
511
+ }
512
+ /**
513
+ * Single canonical home for "is this file user-owned?" resolution. Every
514
+ * service-level check (preGenerationRebase, rebasePatches, trimAbsorbedFiles)
515
+ * routes through `resolveCanonical`.
516
+ *
517
+ * The earlier `rebasePatches` inline variant (`resolveAtCurrentGen`) was
518
+ * collapsed under FER-10386 once pre-flight investigation showed its
519
+ * narrower fall-back was harmless on every reachable scenario but worse
520
+ * on the unreachable-base-tree case. See `docs/known-divergences.md`
521
+ * entry 5 for the closed-out divergence record.
522
+ */
523
+ declare class FileOwnership {
524
+ private readonly git;
525
+ constructor(git: GitClient);
526
+ /**
527
+ * Canonical resolution. Returns true if `file` is user-owned per:
528
+ *
529
+ * 1. `patch.user_owned === true` — authoritative, persisted flag.
530
+ * 2. `file === ".fernignore"` — always user-owned.
531
+ * 3. File matches a `.fernignore` pattern (via minimatch).
532
+ * 4. Patch content shows `--- /dev/null` for this file (legacy-safe
533
+ * signal for patches predating `user_owned` or whose flag was lost).
534
+ * 5. Tree-based check against `patch.base_generation` when reachable
535
+ * AND distinct from `currentGenSha`. Unreachable base → conservative
536
+ * user-owned (silent-absorption safety net), with a one-time warning
537
+ * per base SHA.
538
+ * 6. Final fallback: check `currentGenSha` when no usable base exists
539
+ * (legacy one-gen lockfile).
540
+ */
541
+ resolveCanonical(ctx: OwnershipContext): Promise<boolean>;
542
+ }
543
+
516
544
  interface ConflictDetail {
517
545
  patchId: string;
518
546
  patchMessage: string;
@@ -578,7 +606,7 @@ interface ApplyOptions {
578
606
  * Contract for consumers (e.g., generator-cli pipeline inserting AutoVersionStep):
579
607
  *
580
608
  * - Between `prepareReplay` and `applyPreparedReplay`, HEAD is at the new
581
- * `[fern-generated]` commit (unless `_prepared.terminal === true`, e.g. dry-run).
609
+ * `[fern-generated]` commit (unless `_prepared.kind === "terminal"`, e.g. dry-run).
582
610
  * - Consumers MAY land additional commits on HEAD between phases. `applyPatches`
583
611
  * runs against current HEAD at phase-2 call time, so mid-phase commits are
584
612
  * respected. `rebasePatches` uses `currentGenerationSha` (the pure `[fern-generated]`
@@ -595,7 +623,7 @@ interface ApplyOptions {
595
623
  * prep.previousGenerationSha != null && prep.flow !== "skip-application"
596
624
  */
597
625
  interface ReplayPreparation {
598
- flow: "first-generation" | "no-patches" | "normal-regeneration" | "skip-application";
626
+ flow: FlowKind;
599
627
  /** lockfile.current_generation from BEFORE this run; null iff no prior generation exists */
600
628
  previousGenerationSha: string | null;
601
629
  /** SHA of the new [fern-generated] commit created by prepareReplay (or HEAD for dry-run terminals) */
@@ -605,12 +633,26 @@ interface ReplayPreparation {
605
633
  /** @internal Opaque state consumed by applyPreparedReplay */
606
634
  readonly _prepared: InternalPreparationState;
607
635
  }
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;
636
+ /**
637
+ * @internal
638
+ * Opaque state passed from `prepareReplay` to `applyPreparedReplay`.
639
+ *
640
+ * Discriminated by `kind`:
641
+ * - `"terminal"` — phase 2 returns `preparedReport` without disk/git work
642
+ * (dry-run, first-generation with no patch work, no-patches with no
643
+ * detection results). Apply is a no-op.
644
+ * - `"active"` — phase 2 must apply patches, rebase, save lockfile, commit.
645
+ * Carries the patch set + cached phase-1 state.
646
+ */
647
+ type InternalPreparationState = InternalPreparationTerminal | InternalPreparationActive;
648
+ interface InternalPreparationTerminal {
649
+ readonly kind: "terminal";
650
+ readonly flow: FlowKind;
651
+ readonly preparedReport: ReplayReport;
652
+ }
653
+ interface InternalPreparationActive {
654
+ readonly kind: "active";
655
+ readonly flow: FlowKind;
614
656
  /** post-preGenRebase, post-detection, post-absorption (references, not copies) */
615
657
  patchesToApply: StoredPatch[];
616
658
  /** subset of patchesToApply that were freshly detected this cycle (vs already in the lockfile) */
@@ -629,33 +671,43 @@ interface InternalPreparationState {
629
671
  optionsSnapshot?: ReplayOptions;
630
672
  }
631
673
  declare class ReplayService {
632
- private git;
633
- private detector;
634
- private applicator;
635
- private committer;
636
- private lockManager;
637
- private outputDir;
674
+ /** @internal Used by Flow implementations in `src/flows/`. */
675
+ git: GitClient;
676
+ /** @internal Used by Flow implementations in `src/flows/`. */
677
+ detector: ReplayDetector;
678
+ /** @internal Used by Flow implementations in `src/flows/`. */
679
+ applicator: ReplayApplicator;
680
+ /** @internal Used by Flow implementations in `src/flows/`. */
681
+ committer: ReplayCommitter;
682
+ /** @internal Used by Flow implementations in `src/flows/`. */
683
+ lockManager: LockfileManager;
684
+ /** @internal Used by Flow implementations in `src/flows/`. */
685
+ fileOwnership: FileOwnership;
686
+ /** @internal Used by Flow implementations in `src/flows/`. */
687
+ outputDir: string;
638
688
  /** @internal Test-only. Captures the last ReplayResult[] returned from applicator.applyPatches(). */
639
- private _lastApplyResults;
689
+ _lastApplyResults: ReplayResult[];
640
690
  /**
641
691
  * Service-level warnings accumulated during a single runReplay() call.
642
692
  * 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()`.
693
+ * Populated by `FileOwnership.resolveCanonical` when a patch's base generation tree
694
+ * is unreachable (shallow clone / aggressive `git gc --prune`) and we fall back to a
695
+ * conservative heuristic. Cleared at the top of `runReplay()`.
696
+ *
697
+ * @internal Used by Flow implementations in `src/flows/`.
646
698
  */
647
- private warnings;
699
+ warnings: string[];
648
700
  /**
649
701
  * @internal Test-only accessor.
650
702
  * 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).
703
+ * to inspect the inter-patch accumulator after runReplay() completes (invariant I2).
652
704
  */
653
705
  get applicatorRef(): ReplayApplicator;
654
706
  /**
655
707
  * @internal Test-only accessor.
656
708
  * 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.
709
+ * this to filter applied-vs-conflicted-vs-absorbed patches for invariant
710
+ * assertions. Returns an empty array if no patches have been applied yet.
659
711
  */
660
712
  getLastResults(): ReadonlyArray<ReplayResult>;
661
713
  constructor(outputDir: string, _config: ReplayConfig);
@@ -682,6 +734,16 @@ declare class ReplayService {
682
734
  * its own post-commit logic), this returns the precomputed report.
683
735
  */
684
736
  applyPreparedReplay(prep: ReplayPreparation, options?: ApplyOptions): Promise<ReplayReport>;
737
+ /**
738
+ * Construct the flow for the current run. `selectFlow` is called from
739
+ * `prepareReplay` and decides based on options + lockfile state.
740
+ */
741
+ private selectFlow;
742
+ /**
743
+ * Construct a flow given its kind. Used by `applyPreparedReplay` to
744
+ * route to the same kind that was selected in phase 1.
745
+ */
746
+ private flowForKind;
685
747
  /**
686
748
  * Backwards-compatible atomic entry point. Composes `prepareReplay` + `applyPreparedReplay`.
687
749
  * Behavior is byte-identical to the pre-split implementation for all existing callers.
@@ -703,28 +765,20 @@ declare class ReplayService {
703
765
  baseBranchHead?: string;
704
766
  }): Promise<void>;
705
767
  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
768
  /**
723
769
  * Rebase cleanly applied patches so they are relative to the current generation.
724
770
  * This prevents recurring conflicts on subsequent regenerations.
725
771
  * Returns the number of patches rebased.
772
+ *
773
+ * @internal Used by Flow implementations in `src/flows/`.
726
774
  */
727
- private rebasePatches;
775
+ rebasePatches(results: ReplayResult[], currentGenSha: string): Promise<{
776
+ absorbed: number;
777
+ repointed: number;
778
+ contentRebased: number;
779
+ keptAsUserOwned: number;
780
+ absorbedPatchIds: Set<string>;
781
+ }>;
728
782
  /**
729
783
  * Read THEIRS snapshot (post-customer-edit content) for a list of files
730
784
  * from the working tree on disk. Used by `rebasePatches` after a clean
@@ -735,17 +789,28 @@ declare class ReplayService {
735
789
  */
736
790
  private readSnapshotFromDisk;
737
791
  /**
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.
792
+ * Detects autoversion contamination — returns true when a
793
+ * `[fern-autoversion]` commit between `fromSha` and `toSha` touched any
794
+ * of the patch's `files`. Re-classifies each grep match via
795
+ * `isGenerationCommit` so a customer commit that merely quotes the
796
+ * marker in its body doesn't false-positive.
797
+ *
798
+ * Used by preGenerationRebase to skip the customer-content rebuild for
799
+ * patches whose files were last touched by an autoversion step — a
800
+ * naive `git diff currentGen HEAD` there would capture the
801
+ * placeholder→semver swap as customer customization.
742
802
  */
743
803
  private hasAutoversionContamination;
744
804
  /**
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.
805
+ * Rebuild patch_content from `theirs_snapshot` (captured at detection
806
+ * time, predates pipeline content) rather than a HEAD-relative diff.
807
+ * Returns `null` when snapshot is missing or doesn't cover every file
808
+ * in `patch.files` — caller leaves the patch unchanged.
809
+ *
810
+ * Used as the autoversion-contamination escape hatch: when the
811
+ * HEAD-relative diff would capture pipeline content (autoversion's
812
+ * placeholder swap) as customer customization, the snapshot path
813
+ * preserves the customer's actual intent.
749
814
  */
750
815
  private computeSnapshotBasedPatchDiff;
751
816
  /**
@@ -764,7 +829,7 @@ declare class ReplayService {
764
829
  * **Skips patches sharing files with other patches.** HEAD's content
765
830
  * for a file shared by N patches is cumulative across all of them, so
766
831
  * a HEAD-fallback would falsely encode other patches' contributions
767
- * into one patch's snapshot — breaking FER-9525 cross-patch isolation
832
+ * into one patch's snapshot — breaking cross-patch isolation
768
833
  * if the snapshot fallback later fires. Per-patch snapshots only
769
834
  * make sense when the patch owns its file. Multi-patch-same-file
770
835
  * legacy lockfiles keep using line-anchored reconstruction (the
@@ -778,37 +843,6 @@ declare class ReplayService {
778
843
  * Skips patches that already have a snapshot.
779
844
  */
780
845
  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
846
  /**
813
847
  * For conflict patches with mixed results (some files merged, some conflicted),
814
848
  * check if the cleanly merged files were absorbed by the generator (empty diff).
@@ -823,7 +857,12 @@ declare class ReplayService {
823
857
  * Pre-generation rebase: update patches using the customer's current state.
824
858
  * Called BEFORE commitGeneration() while HEAD has customer code.
825
859
  */
826
- private preGenerationRebase;
860
+ /** @internal Used by Flow implementations in `src/flows/`. */
861
+ preGenerationRebase(patches: StoredPatch[]): Promise<{
862
+ conflictResolved: number;
863
+ conflictAbsorbed: number;
864
+ contentRefreshed: number;
865
+ }>;
827
866
  /**
828
867
  * After applyPatches(), surface a warning for each (patch × file) where
829
868
  * diff3 silently dropped lines that were unchanged context in the
@@ -832,21 +871,35 @@ declare class ReplayService {
832
871
  * relied on them. This is the "surface or preserve, never silently drop"
833
872
  * contract for legitimate-deletion cases.
834
873
  */
835
- private recordDroppedContextLineWarnings;
874
+ /** @internal Used by Flow implementations in `src/flows/`. */
875
+ recordDroppedContextLineWarnings(results: ReplayResult[]): void;
836
876
  /**
837
877
  * After applyPatches(), strip conflict markers from conflicting files
838
878
  * so only clean content is committed. Keeps the Generated (OURS) side.
839
879
  */
840
- private revertConflictingFiles;
880
+ /** @internal Used by Flow implementations in `src/flows/`. */
881
+ revertConflictingFiles(results: ReplayResult[]): void;
841
882
  /**
842
883
  * Clean up stale conflict markers left by a previous crashed run.
843
884
  * Called after commitGeneration() when HEAD is the [fern-generated] commit.
844
885
  * Restores files to their clean generated state from HEAD.
845
886
  * Skips .fernignore-protected files to prevent overwriting user content.
846
887
  */
847
- private cleanupStaleConflictMarkers;
888
+ /** @internal Used by Flow implementations in `src/flows/`. */
889
+ cleanupStaleConflictMarkers(): Promise<void>;
848
890
  private readFernignorePatterns;
849
- private buildReport;
891
+ /** @internal Used by Flow implementations in `src/flows/`. */
892
+ buildReport(flow: "first-generation" | "no-patches" | "normal-regeneration" | "skip-application", patches: StoredPatch[], results: ReplayResult[], options?: ReplayOptions, warnings?: string[], rebaseCounts?: {
893
+ absorbed: number;
894
+ repointed: number;
895
+ contentRebased: number;
896
+ keptAsUserOwned: number;
897
+ absorbedPatchIds: Set<string>;
898
+ }, preRebaseCounts?: {
899
+ conflictResolved: number;
900
+ conflictAbsorbed: number;
901
+ contentRefreshed: number;
902
+ }, patchesReverted?: number): ReplayReport;
850
903
  }
851
904
 
852
905
  interface MigrationAnalysis {
@@ -1053,4 +1106,4 @@ interface StatusGeneration {
1053
1106
  }
1054
1107
  declare function status(outputDir: string): StatusResult;
1055
1108
 
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 };
1109
+ 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 };