@fern-api/replay 0.15.1 → 0.16.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
@@ -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;
@@ -259,22 +257,24 @@ declare class ReplayDetector {
259
257
  private sdkOutputDir;
260
258
  readonly warnings: string[];
261
259
  constructor(git: GitClient, lockManager: LockfileManager, sdkOutputDir: string);
262
- detectNewPatches(): Promise<DetectionResult>;
263
260
  /**
264
- * Non-linear-history detection via `merge-base(prevGen, HEAD)`.
261
+ * Derive the previous-generation SHA from git history instead of trusting
262
+ * `lock.current_generation`.
263
+ *
264
+ * Walks `git log HEAD --first-parent` and returns the SHA of the most recent
265
+ * commit that `isGenerationBoundary` classifies as a customization-baseline
266
+ * boundary, or null if no such commit is reachable from HEAD on the mainline.
265
267
  *
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.
268
+ * `isGenerationBoundary` is intentionally narrower than `isGenerationCommit`:
269
+ * it excludes `[fern-autoversion]` (lightweight bumps that don't reset the
270
+ * baseline) and the GitHub merge-commit subject for fern-bot regen PRs
271
+ * (which sits on first-parent but where the customer fix-up commits live
272
+ * on second-parent stopping at the merge would hide them).
273
273
  *
274
- * Falls through to the legacy composite path when no common ancestor exists
275
- * (truly disjoint history — orphan branches with no shared root).
274
+ * See docs/adr/0001-derived-scan-boundary.md.
276
275
  */
277
- private detectPatchesViaMergeBase;
276
+ findPreviousGenerationFromHistory(): Promise<string | null>;
277
+ detectNewPatches(): Promise<DetectionResult>;
278
278
  /**
279
279
  * Walk `${rangeStart}..HEAD`, classify each commit, emit one
280
280
  * `StoredPatch` per customer commit. Body shared by the linear path
@@ -308,9 +308,7 @@ declare class ReplayDetector {
308
308
  * within the current linear detection window, and are absent from
309
309
  * HEAD at the end of the window.
310
310
  *
311
- * Rationale: on a merge commit, createMergeCompositePatch already diffs
312
- * firstParent..mergeCommit so net-zero files never enter the composite. On
313
- * 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
314
312
  * briefly added and later removed survives as a create+delete pair whose
315
313
  * cumulative diff is empty. We drop such patches before they reach the
316
314
  * lockfile.
@@ -341,13 +339,6 @@ declare class ReplayDetector {
341
339
  * more aggressively. On typical SDK repos this is a single sub-10ms call.
342
340
  */
343
341
  private listHeadFiles;
344
- /**
345
- * Create a single composite patch for a merge commit by diffing the merge
346
- * commit against its first parent. This captures the net change of the
347
- * entire merged branch as one patch, eliminating accumulator chaining and
348
- * zero-net-change ghost conflicts.
349
- */
350
- private createMergeCompositePatch;
351
342
  /**
352
343
  * Detect patches via tree diff for non-linear history. Returns a composite patch.
353
344
  * Revert reconciliation is skipped here because tree-diff produces a single composite
@@ -377,6 +368,13 @@ declare class ReplayDetector {
377
368
  private resolveDiffBase;
378
369
  private parseGitLog;
379
370
  private getLastGeneration;
371
+ /**
372
+ * Build a transient `GenerationRecord` from a SHA derived by walking git
373
+ * history. Only `commit_sha` (and `tree_hash` when needed downstream) is
374
+ * load-bearing here; the rest are filled with defensible placeholders.
375
+ * This record is consumed by `detectPatchesInRange` and is never persisted.
376
+ */
377
+ private synthesizeGenerationRecord;
380
378
  }
381
379
 
382
380
  /**
@@ -477,7 +475,6 @@ declare class ReplayApplicator {
477
475
  interface CommitOptions {
478
476
  cliVersion: string;
479
477
  generatorVersions: Record<string, string>;
480
- baseBranchHead?: string;
481
478
  }
482
479
  declare class ReplayCommitter {
483
480
  private git;
@@ -596,12 +593,10 @@ interface ReplayOptions {
596
593
  generatorVersions?: Record<string, string>;
597
594
  /** Commit generation + update lockfile, skip detection/application */
598
595
  skipApplication?: boolean;
599
- /** SHA of the base branch HEAD before replay runs. Stored in lockfile for squash merge recovery. */
600
- baseBranchHead?: string;
601
596
  }
602
597
  /**
603
598
  * Options honored by `applyPreparedReplay()`. `cliVersion`, `generatorVersions`,
604
- * `baseBranchHead`, `dryRun`, and `skipApplication` were all captured in phase 1
599
+ * `dryRun`, and `skipApplication` were all captured in phase 1
605
600
  * and re-supplying them here is meaningless.
606
601
  */
607
602
  interface ApplyOptions {
@@ -621,8 +616,8 @@ interface ApplyOptions {
621
616
  * respected. `rebasePatches` uses `currentGenerationSha` (the pure `[fern-generated]`
622
617
  * SHA), NOT HEAD — mid-phase commits do not retarget patches' `base_generation`.
623
618
  * - The service instance is single-use across the phase boundary: do not call
624
- * other service methods (`syncFromDivergentMerge`, another `prepareReplay`, etc.)
625
- * between phases. In-memory lockfile and warning state would be corrupted.
619
+ * another `prepareReplay()` between phases. In-memory lockfile and warning
620
+ * state would be corrupted.
626
621
  * - If the caller's mid-phase work throws or `applyPreparedReplay` is never called,
627
622
  * the repo is left with the new `[fern-generated]` commit but a stale lockfile.
628
623
  * Consumers MUST either (a) still call `applyPreparedReplay` so the lockfile
@@ -637,8 +632,6 @@ interface ReplayPreparation {
637
632
  previousGenerationSha: string | null;
638
633
  /** SHA of the new [fern-generated] commit created by prepareReplay (or HEAD for dry-run terminals) */
639
634
  currentGenerationSha: string;
640
- /** options.baseBranchHead passed through */
641
- baseBranchHead: string | null;
642
635
  /** @internal Opaque state consumed by applyPreparedReplay */
643
636
  readonly _prepared: InternalPreparationState;
644
637
  }
@@ -758,21 +751,6 @@ declare class ReplayService {
758
751
  * Behavior is byte-identical to the pre-split implementation for all existing callers.
759
752
  */
760
753
  runReplay(options?: ReplayOptions): Promise<ReplayReport>;
761
- /**
762
- * Sync the lockfile after a divergent PR was squash-merged.
763
- * Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
764
- *
765
- * After updating the generation record, re-detects customer patches via
766
- * tree diff between the generation tag (pure generation tree) and HEAD
767
- * (which includes customer customizations after squash merge). This ensures
768
- * patches survive the squash merge → regenerate cycle even when the lockfile
769
- * was restored from the base branch during conflict PR creation.
770
- */
771
- syncFromDivergentMerge(generationCommitSha: string, options?: {
772
- cliVersion?: string;
773
- generatorVersions?: Record<string, string>;
774
- baseBranchHead?: string;
775
- }): Promise<void>;
776
754
  private determineFlow;
777
755
  /**
778
756
  * Rebase cleanly applied patches so they are relative to the current generation.
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;
@@ -259,22 +257,24 @@ declare class ReplayDetector {
259
257
  private sdkOutputDir;
260
258
  readonly warnings: string[];
261
259
  constructor(git: GitClient, lockManager: LockfileManager, sdkOutputDir: string);
262
- detectNewPatches(): Promise<DetectionResult>;
263
260
  /**
264
- * Non-linear-history detection via `merge-base(prevGen, HEAD)`.
261
+ * Derive the previous-generation SHA from git history instead of trusting
262
+ * `lock.current_generation`.
263
+ *
264
+ * Walks `git log HEAD --first-parent` and returns the SHA of the most recent
265
+ * commit that `isGenerationBoundary` classifies as a customization-baseline
266
+ * boundary, or null if no such commit is reachable from HEAD on the mainline.
265
267
  *
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.
268
+ * `isGenerationBoundary` is intentionally narrower than `isGenerationCommit`:
269
+ * it excludes `[fern-autoversion]` (lightweight bumps that don't reset the
270
+ * baseline) and the GitHub merge-commit subject for fern-bot regen PRs
271
+ * (which sits on first-parent but where the customer fix-up commits live
272
+ * on second-parent stopping at the merge would hide them).
273
273
  *
274
- * Falls through to the legacy composite path when no common ancestor exists
275
- * (truly disjoint history — orphan branches with no shared root).
274
+ * See docs/adr/0001-derived-scan-boundary.md.
276
275
  */
277
- private detectPatchesViaMergeBase;
276
+ findPreviousGenerationFromHistory(): Promise<string | null>;
277
+ detectNewPatches(): Promise<DetectionResult>;
278
278
  /**
279
279
  * Walk `${rangeStart}..HEAD`, classify each commit, emit one
280
280
  * `StoredPatch` per customer commit. Body shared by the linear path
@@ -308,9 +308,7 @@ declare class ReplayDetector {
308
308
  * within the current linear detection window, and are absent from
309
309
  * HEAD at the end of the window.
310
310
  *
311
- * Rationale: on a merge commit, createMergeCompositePatch already diffs
312
- * firstParent..mergeCommit so net-zero files never enter the composite. On
313
- * 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
314
312
  * briefly added and later removed survives as a create+delete pair whose
315
313
  * cumulative diff is empty. We drop such patches before they reach the
316
314
  * lockfile.
@@ -341,13 +339,6 @@ declare class ReplayDetector {
341
339
  * more aggressively. On typical SDK repos this is a single sub-10ms call.
342
340
  */
343
341
  private listHeadFiles;
344
- /**
345
- * Create a single composite patch for a merge commit by diffing the merge
346
- * commit against its first parent. This captures the net change of the
347
- * entire merged branch as one patch, eliminating accumulator chaining and
348
- * zero-net-change ghost conflicts.
349
- */
350
- private createMergeCompositePatch;
351
342
  /**
352
343
  * Detect patches via tree diff for non-linear history. Returns a composite patch.
353
344
  * Revert reconciliation is skipped here because tree-diff produces a single composite
@@ -377,6 +368,13 @@ declare class ReplayDetector {
377
368
  private resolveDiffBase;
378
369
  private parseGitLog;
379
370
  private getLastGeneration;
371
+ /**
372
+ * Build a transient `GenerationRecord` from a SHA derived by walking git
373
+ * history. Only `commit_sha` (and `tree_hash` when needed downstream) is
374
+ * load-bearing here; the rest are filled with defensible placeholders.
375
+ * This record is consumed by `detectPatchesInRange` and is never persisted.
376
+ */
377
+ private synthesizeGenerationRecord;
380
378
  }
381
379
 
382
380
  /**
@@ -477,7 +475,6 @@ declare class ReplayApplicator {
477
475
  interface CommitOptions {
478
476
  cliVersion: string;
479
477
  generatorVersions: Record<string, string>;
480
- baseBranchHead?: string;
481
478
  }
482
479
  declare class ReplayCommitter {
483
480
  private git;
@@ -596,12 +593,10 @@ interface ReplayOptions {
596
593
  generatorVersions?: Record<string, string>;
597
594
  /** Commit generation + update lockfile, skip detection/application */
598
595
  skipApplication?: boolean;
599
- /** SHA of the base branch HEAD before replay runs. Stored in lockfile for squash merge recovery. */
600
- baseBranchHead?: string;
601
596
  }
602
597
  /**
603
598
  * Options honored by `applyPreparedReplay()`. `cliVersion`, `generatorVersions`,
604
- * `baseBranchHead`, `dryRun`, and `skipApplication` were all captured in phase 1
599
+ * `dryRun`, and `skipApplication` were all captured in phase 1
605
600
  * and re-supplying them here is meaningless.
606
601
  */
607
602
  interface ApplyOptions {
@@ -621,8 +616,8 @@ interface ApplyOptions {
621
616
  * respected. `rebasePatches` uses `currentGenerationSha` (the pure `[fern-generated]`
622
617
  * SHA), NOT HEAD — mid-phase commits do not retarget patches' `base_generation`.
623
618
  * - The service instance is single-use across the phase boundary: do not call
624
- * other service methods (`syncFromDivergentMerge`, another `prepareReplay`, etc.)
625
- * between phases. In-memory lockfile and warning state would be corrupted.
619
+ * another `prepareReplay()` between phases. In-memory lockfile and warning
620
+ * state would be corrupted.
626
621
  * - If the caller's mid-phase work throws or `applyPreparedReplay` is never called,
627
622
  * the repo is left with the new `[fern-generated]` commit but a stale lockfile.
628
623
  * Consumers MUST either (a) still call `applyPreparedReplay` so the lockfile
@@ -637,8 +632,6 @@ interface ReplayPreparation {
637
632
  previousGenerationSha: string | null;
638
633
  /** SHA of the new [fern-generated] commit created by prepareReplay (or HEAD for dry-run terminals) */
639
634
  currentGenerationSha: string;
640
- /** options.baseBranchHead passed through */
641
- baseBranchHead: string | null;
642
635
  /** @internal Opaque state consumed by applyPreparedReplay */
643
636
  readonly _prepared: InternalPreparationState;
644
637
  }
@@ -758,21 +751,6 @@ declare class ReplayService {
758
751
  * Behavior is byte-identical to the pre-split implementation for all existing callers.
759
752
  */
760
753
  runReplay(options?: ReplayOptions): Promise<ReplayReport>;
761
- /**
762
- * Sync the lockfile after a divergent PR was squash-merged.
763
- * Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
764
- *
765
- * After updating the generation record, re-detects customer patches via
766
- * tree diff between the generation tag (pure generation tree) and HEAD
767
- * (which includes customer customizations after squash merge). This ensures
768
- * patches survive the squash merge → regenerate cycle even when the lockfile
769
- * was restored from the base branch during conflict PR creation.
770
- */
771
- syncFromDivergentMerge(generationCommitSha: string, options?: {
772
- cliVersion?: string;
773
- generatorVersions?: Record<string, string>;
774
- baseBranchHead?: string;
775
- }): Promise<void>;
776
754
  private determineFlow;
777
755
  /**
778
756
  * Rebase cleanly applied patches so they are relative to the current generation.