@fern-api/replay 0.15.2 → 0.16.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
@@ -12,8 +12,6 @@ interface GenerationRecord {
12
12
  timestamp: string;
13
13
  cli_version: string;
14
14
  generator_versions: Record<string, string>;
15
- /** SHA of the base branch HEAD before replay ran. Always on main's lineage. */
16
- base_branch_head?: string;
17
15
  }
18
16
  interface StoredPatch {
19
17
  id: string;
@@ -216,8 +214,19 @@ declare class LockfileNotFoundError extends Error {
216
214
  declare class LockfileManager {
217
215
  private outputDir;
218
216
  private lock;
217
+ private fernignorePatterns;
219
218
  readonly warnings: string[];
220
219
  constructor(outputDir: string);
220
+ /**
221
+ * Inform the lockfile of the customer's `.fernignore` patterns so that
222
+ * `save()` can suppress its "Patch X removed from lockfile" warning when
223
+ * every file in the stripped patch is already `.fernignore`-protected.
224
+ * The strip itself still happens (credentials never persist); only the
225
+ * customer-visible warning is suppressed in that case, because the
226
+ * protected files survive the generator wipe regardless of whether the
227
+ * lockfile tracks them, so there's nothing for the customer to action.
228
+ */
229
+ setFernignorePatterns(patterns: string[]): void;
221
230
  get lockfilePath(): string;
222
231
  get customizationsPath(): string;
223
232
  exists(): boolean;
@@ -259,22 +268,24 @@ declare class ReplayDetector {
259
268
  private sdkOutputDir;
260
269
  readonly warnings: string[];
261
270
  constructor(git: GitClient, lockManager: LockfileManager, sdkOutputDir: string);
262
- detectNewPatches(): Promise<DetectionResult>;
263
271
  /**
264
- * Non-linear-history detection via `merge-base(prevGen, HEAD)`.
272
+ * Derive the previous-generation SHA from git history instead of trusting
273
+ * `lock.current_generation`.
265
274
  *
266
- * After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
267
- * `[fern-generated]`) is orphaned but still in git's object database. The
268
- * merge-base is the commit on main from which the bot's branch was created
269
- * a reachable ancestor of HEAD. Walking that range and classifying each
270
- * commit via `isGenerationCommit` mirrors the linear path and eliminates
271
- * the bug class where pipeline-driven changes (autoversion, replay,
272
- * generation) get bundled as customer customizations.
275
+ * Walks `git log HEAD --first-parent` and returns the SHA of the most recent
276
+ * commit that `isGenerationBoundary` classifies as a customization-baseline
277
+ * boundary, or null if no such commit is reachable from HEAD on the mainline.
273
278
  *
274
- * Falls through to the legacy composite path when no common ancestor exists
275
- * (truly disjoint history orphan branches with no shared root).
279
+ * `isGenerationBoundary` is intentionally narrower than `isGenerationCommit`:
280
+ * it excludes `[fern-autoversion]` (lightweight bumps that don't reset the
281
+ * baseline) and the GitHub merge-commit subject for fern-bot regen PRs
282
+ * (which sits on first-parent but where the customer fix-up commits live
283
+ * on second-parent — stopping at the merge would hide them).
284
+ *
285
+ * See docs/adr/0001-derived-scan-boundary.md.
276
286
  */
277
- private detectPatchesViaMergeBase;
287
+ findPreviousGenerationFromHistory(): Promise<string | null>;
288
+ detectNewPatches(): Promise<DetectionResult>;
278
289
  /**
279
290
  * Walk `${rangeStart}..HEAD`, classify each commit, emit one
280
291
  * `StoredPatch` per customer commit. Body shared by the linear path
@@ -368,6 +379,13 @@ declare class ReplayDetector {
368
379
  private resolveDiffBase;
369
380
  private parseGitLog;
370
381
  private getLastGeneration;
382
+ /**
383
+ * Build a transient `GenerationRecord` from a SHA derived by walking git
384
+ * history. Only `commit_sha` (and `tree_hash` when needed downstream) is
385
+ * load-bearing here; the rest are filled with defensible placeholders.
386
+ * This record is consumed by `detectPatchesInRange` and is never persisted.
387
+ */
388
+ private synthesizeGenerationRecord;
371
389
  }
372
390
 
373
391
  /**
@@ -468,7 +486,6 @@ declare class ReplayApplicator {
468
486
  interface CommitOptions {
469
487
  cliVersion: string;
470
488
  generatorVersions: Record<string, string>;
471
- baseBranchHead?: string;
472
489
  }
473
490
  declare class ReplayCommitter {
474
491
  private git;
@@ -587,12 +604,10 @@ interface ReplayOptions {
587
604
  generatorVersions?: Record<string, string>;
588
605
  /** Commit generation + update lockfile, skip detection/application */
589
606
  skipApplication?: boolean;
590
- /** SHA of the base branch HEAD before replay runs. Stored in lockfile for squash merge recovery. */
591
- baseBranchHead?: string;
592
607
  }
593
608
  /**
594
609
  * Options honored by `applyPreparedReplay()`. `cliVersion`, `generatorVersions`,
595
- * `baseBranchHead`, `dryRun`, and `skipApplication` were all captured in phase 1
610
+ * `dryRun`, and `skipApplication` were all captured in phase 1
596
611
  * and re-supplying them here is meaningless.
597
612
  */
598
613
  interface ApplyOptions {
@@ -612,8 +627,8 @@ interface ApplyOptions {
612
627
  * respected. `rebasePatches` uses `currentGenerationSha` (the pure `[fern-generated]`
613
628
  * SHA), NOT HEAD — mid-phase commits do not retarget patches' `base_generation`.
614
629
  * - The service instance is single-use across the phase boundary: do not call
615
- * other service methods (`syncFromDivergentMerge`, another `prepareReplay`, etc.)
616
- * between phases. In-memory lockfile and warning state would be corrupted.
630
+ * another `prepareReplay()` between phases. In-memory lockfile and warning
631
+ * state would be corrupted.
617
632
  * - If the caller's mid-phase work throws or `applyPreparedReplay` is never called,
618
633
  * the repo is left with the new `[fern-generated]` commit but a stale lockfile.
619
634
  * Consumers MUST either (a) still call `applyPreparedReplay` so the lockfile
@@ -628,8 +643,6 @@ interface ReplayPreparation {
628
643
  previousGenerationSha: string | null;
629
644
  /** SHA of the new [fern-generated] commit created by prepareReplay (or HEAD for dry-run terminals) */
630
645
  currentGenerationSha: string;
631
- /** options.baseBranchHead passed through */
632
- baseBranchHead: string | null;
633
646
  /** @internal Opaque state consumed by applyPreparedReplay */
634
647
  readonly _prepared: InternalPreparationState;
635
648
  }
@@ -749,21 +762,6 @@ declare class ReplayService {
749
762
  * Behavior is byte-identical to the pre-split implementation for all existing callers.
750
763
  */
751
764
  runReplay(options?: ReplayOptions): Promise<ReplayReport>;
752
- /**
753
- * Sync the lockfile after a divergent PR was squash-merged.
754
- * Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
755
- *
756
- * After updating the generation record, re-detects customer patches via
757
- * tree diff between the generation tag (pure generation tree) and HEAD
758
- * (which includes customer customizations after squash merge). This ensures
759
- * patches survive the squash merge → regenerate cycle even when the lockfile
760
- * was restored from the base branch during conflict PR creation.
761
- */
762
- syncFromDivergentMerge(generationCommitSha: string, options?: {
763
- cliVersion?: string;
764
- generatorVersions?: Record<string, string>;
765
- baseBranchHead?: string;
766
- }): Promise<void>;
767
765
  private determineFlow;
768
766
  /**
769
767
  * Rebase cleanly applied patches so they are relative to the current generation.
@@ -887,7 +885,8 @@ declare class ReplayService {
887
885
  */
888
886
  /** @internal Used by Flow implementations in `src/flows/`. */
889
887
  cleanupStaleConflictMarkers(): Promise<void>;
890
- private readFernignorePatterns;
888
+ /** @internal Used by Flow implementations in `src/flows/`. */
889
+ readFernignorePatterns(): string[];
891
890
  /** @internal Used by Flow implementations in `src/flows/`. */
892
891
  buildReport(flow: "first-generation" | "no-patches" | "normal-regeneration" | "skip-application", patches: StoredPatch[], results: ReplayResult[], options?: ReplayOptions, warnings?: string[], rebaseCounts?: {
893
892
  absorbed: number;
package/dist/index.d.ts CHANGED
@@ -12,8 +12,6 @@ interface GenerationRecord {
12
12
  timestamp: string;
13
13
  cli_version: string;
14
14
  generator_versions: Record<string, string>;
15
- /** SHA of the base branch HEAD before replay ran. Always on main's lineage. */
16
- base_branch_head?: string;
17
15
  }
18
16
  interface StoredPatch {
19
17
  id: string;
@@ -216,8 +214,19 @@ declare class LockfileNotFoundError extends Error {
216
214
  declare class LockfileManager {
217
215
  private outputDir;
218
216
  private lock;
217
+ private fernignorePatterns;
219
218
  readonly warnings: string[];
220
219
  constructor(outputDir: string);
220
+ /**
221
+ * Inform the lockfile of the customer's `.fernignore` patterns so that
222
+ * `save()` can suppress its "Patch X removed from lockfile" warning when
223
+ * every file in the stripped patch is already `.fernignore`-protected.
224
+ * The strip itself still happens (credentials never persist); only the
225
+ * customer-visible warning is suppressed in that case, because the
226
+ * protected files survive the generator wipe regardless of whether the
227
+ * lockfile tracks them, so there's nothing for the customer to action.
228
+ */
229
+ setFernignorePatterns(patterns: string[]): void;
221
230
  get lockfilePath(): string;
222
231
  get customizationsPath(): string;
223
232
  exists(): boolean;
@@ -259,22 +268,24 @@ declare class ReplayDetector {
259
268
  private sdkOutputDir;
260
269
  readonly warnings: string[];
261
270
  constructor(git: GitClient, lockManager: LockfileManager, sdkOutputDir: string);
262
- detectNewPatches(): Promise<DetectionResult>;
263
271
  /**
264
- * Non-linear-history detection via `merge-base(prevGen, HEAD)`.
272
+ * Derive the previous-generation SHA from git history instead of trusting
273
+ * `lock.current_generation`.
265
274
  *
266
- * After a squash-merge of a Fern-bot PR, `lastGen.commit_sha` (the previous
267
- * `[fern-generated]`) is orphaned but still in git's object database. The
268
- * merge-base is the commit on main from which the bot's branch was created
269
- * a reachable ancestor of HEAD. Walking that range and classifying each
270
- * commit via `isGenerationCommit` mirrors the linear path and eliminates
271
- * the bug class where pipeline-driven changes (autoversion, replay,
272
- * generation) get bundled as customer customizations.
275
+ * Walks `git log HEAD --first-parent` and returns the SHA of the most recent
276
+ * commit that `isGenerationBoundary` classifies as a customization-baseline
277
+ * boundary, or null if no such commit is reachable from HEAD on the mainline.
273
278
  *
274
- * Falls through to the legacy composite path when no common ancestor exists
275
- * (truly disjoint history orphan branches with no shared root).
279
+ * `isGenerationBoundary` is intentionally narrower than `isGenerationCommit`:
280
+ * it excludes `[fern-autoversion]` (lightweight bumps that don't reset the
281
+ * baseline) and the GitHub merge-commit subject for fern-bot regen PRs
282
+ * (which sits on first-parent but where the customer fix-up commits live
283
+ * on second-parent — stopping at the merge would hide them).
284
+ *
285
+ * See docs/adr/0001-derived-scan-boundary.md.
276
286
  */
277
- private detectPatchesViaMergeBase;
287
+ findPreviousGenerationFromHistory(): Promise<string | null>;
288
+ detectNewPatches(): Promise<DetectionResult>;
278
289
  /**
279
290
  * Walk `${rangeStart}..HEAD`, classify each commit, emit one
280
291
  * `StoredPatch` per customer commit. Body shared by the linear path
@@ -368,6 +379,13 @@ declare class ReplayDetector {
368
379
  private resolveDiffBase;
369
380
  private parseGitLog;
370
381
  private getLastGeneration;
382
+ /**
383
+ * Build a transient `GenerationRecord` from a SHA derived by walking git
384
+ * history. Only `commit_sha` (and `tree_hash` when needed downstream) is
385
+ * load-bearing here; the rest are filled with defensible placeholders.
386
+ * This record is consumed by `detectPatchesInRange` and is never persisted.
387
+ */
388
+ private synthesizeGenerationRecord;
371
389
  }
372
390
 
373
391
  /**
@@ -468,7 +486,6 @@ declare class ReplayApplicator {
468
486
  interface CommitOptions {
469
487
  cliVersion: string;
470
488
  generatorVersions: Record<string, string>;
471
- baseBranchHead?: string;
472
489
  }
473
490
  declare class ReplayCommitter {
474
491
  private git;
@@ -587,12 +604,10 @@ interface ReplayOptions {
587
604
  generatorVersions?: Record<string, string>;
588
605
  /** Commit generation + update lockfile, skip detection/application */
589
606
  skipApplication?: boolean;
590
- /** SHA of the base branch HEAD before replay runs. Stored in lockfile for squash merge recovery. */
591
- baseBranchHead?: string;
592
607
  }
593
608
  /**
594
609
  * Options honored by `applyPreparedReplay()`. `cliVersion`, `generatorVersions`,
595
- * `baseBranchHead`, `dryRun`, and `skipApplication` were all captured in phase 1
610
+ * `dryRun`, and `skipApplication` were all captured in phase 1
596
611
  * and re-supplying them here is meaningless.
597
612
  */
598
613
  interface ApplyOptions {
@@ -612,8 +627,8 @@ interface ApplyOptions {
612
627
  * respected. `rebasePatches` uses `currentGenerationSha` (the pure `[fern-generated]`
613
628
  * SHA), NOT HEAD — mid-phase commits do not retarget patches' `base_generation`.
614
629
  * - The service instance is single-use across the phase boundary: do not call
615
- * other service methods (`syncFromDivergentMerge`, another `prepareReplay`, etc.)
616
- * between phases. In-memory lockfile and warning state would be corrupted.
630
+ * another `prepareReplay()` between phases. In-memory lockfile and warning
631
+ * state would be corrupted.
617
632
  * - If the caller's mid-phase work throws or `applyPreparedReplay` is never called,
618
633
  * the repo is left with the new `[fern-generated]` commit but a stale lockfile.
619
634
  * Consumers MUST either (a) still call `applyPreparedReplay` so the lockfile
@@ -628,8 +643,6 @@ interface ReplayPreparation {
628
643
  previousGenerationSha: string | null;
629
644
  /** SHA of the new [fern-generated] commit created by prepareReplay (or HEAD for dry-run terminals) */
630
645
  currentGenerationSha: string;
631
- /** options.baseBranchHead passed through */
632
- baseBranchHead: string | null;
633
646
  /** @internal Opaque state consumed by applyPreparedReplay */
634
647
  readonly _prepared: InternalPreparationState;
635
648
  }
@@ -749,21 +762,6 @@ declare class ReplayService {
749
762
  * Behavior is byte-identical to the pre-split implementation for all existing callers.
750
763
  */
751
764
  runReplay(options?: ReplayOptions): Promise<ReplayReport>;
752
- /**
753
- * Sync the lockfile after a divergent PR was squash-merged.
754
- * Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
755
- *
756
- * After updating the generation record, re-detects customer patches via
757
- * tree diff between the generation tag (pure generation tree) and HEAD
758
- * (which includes customer customizations after squash merge). This ensures
759
- * patches survive the squash merge → regenerate cycle even when the lockfile
760
- * was restored from the base branch during conflict PR creation.
761
- */
762
- syncFromDivergentMerge(generationCommitSha: string, options?: {
763
- cliVersion?: string;
764
- generatorVersions?: Record<string, string>;
765
- baseBranchHead?: string;
766
- }): Promise<void>;
767
765
  private determineFlow;
768
766
  /**
769
767
  * Rebase cleanly applied patches so they are relative to the current generation.
@@ -887,7 +885,8 @@ declare class ReplayService {
887
885
  */
888
886
  /** @internal Used by Flow implementations in `src/flows/`. */
889
887
  cleanupStaleConflictMarkers(): Promise<void>;
890
- private readFernignorePatterns;
888
+ /** @internal Used by Flow implementations in `src/flows/`. */
889
+ readFernignorePatterns(): string[];
891
890
  /** @internal Used by Flow implementations in `src/flows/`. */
892
891
  buildReport(flow: "first-generation" | "no-patches" | "normal-regeneration" | "skip-application", patches: StoredPatch[], results: ReplayResult[], options?: ReplayOptions, warnings?: string[], rebaseCounts?: {
893
892
  absorbed: number;