@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/README.md +19 -7
- package/dist/cli.cjs +3437 -3311
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +1932 -1807
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +198 -145
- package/dist/index.d.ts +198 -145
- package/dist/index.js +1937 -1812
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
303
|
+
* respectively.
|
|
296
304
|
*/
|
|
297
305
|
computeContentHash(patchContent: string): string;
|
|
298
306
|
/**
|
|
299
|
-
*
|
|
300
|
-
*
|
|
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:
|
|
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.
|
|
326
|
-
*
|
|
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
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
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
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
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
|
|
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
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
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.
|
|
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:
|
|
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
|
-
/**
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
-
|
|
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 `
|
|
644
|
-
* (shallow clone / aggressive `git gc --prune`) and we fall back to a
|
|
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
|
-
|
|
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 (
|
|
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
|
|
658
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
739
|
-
* `fromSha` and `toSha` touched any
|
|
740
|
-
*
|
|
741
|
-
*
|
|
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
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
888
|
+
/** @internal Used by Flow implementations in `src/flows/`. */
|
|
889
|
+
cleanupStaleConflictMarkers(): Promise<void>;
|
|
848
890
|
private readFernignorePatterns;
|
|
849
|
-
|
|
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 };
|