@fern-api/replay 0.1.0 → 0.2.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.
@@ -19,6 +19,7 @@ export declare class LockfileManager {
19
19
  addPatch(patch: StoredPatch): void;
20
20
  updatePatch(patchId: string, updates: Partial<Pick<StoredPatch, "base_generation" | "patch_content" | "content_hash" | "files">>): void;
21
21
  removePatch(patchId: string): void;
22
+ clearPatches(): void;
22
23
  getPatches(): StoredPatch[];
23
24
  getGeneration(commitSha: string): GenerationRecord | undefined;
24
25
  getCustomizationsConfig(): CustomizationsConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"LockfileManager.d.ts","sourceRoot":"","sources":["../src/LockfileManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACR,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,oBAAoB,EACvB,MAAM,YAAY,CAAC;AAIpB,qBAAa,eAAe;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,IAAI,CAA+B;gBAE/B,SAAS,EAAE,MAAM;IAI7B,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,kBAAkB,IAAI,MAAM,CAE/B;IAED,MAAM,IAAI,OAAO;IAIjB,IAAI,IAAI,cAAc;IAYtB,UAAU,CAAC,eAAe,EAAE,gBAAgB,GAAG,IAAI;IAKnD;;;;OAIG;IACH,kBAAkB,CAAC,eAAe,EAAE,gBAAgB,GAAG,IAAI;IAS3D,IAAI,IAAI,IAAI;IAmBZ,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAM7C,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAKlC,WAAW,CACP,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,GAAG,eAAe,GAAG,cAAc,GAAG,OAAO,CAAC,CAAC,GACpG,IAAI;IASP,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKlC,UAAU,IAAI,WAAW,EAAE;IAK3B,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAK9D,uBAAuB,IAAI,oBAAoB;IAQ/C,OAAO,CAAC,YAAY;CAKvB"}
1
+ {"version":3,"file":"LockfileManager.d.ts","sourceRoot":"","sources":["../src/LockfileManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACR,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,oBAAoB,EACvB,MAAM,YAAY,CAAC;AAIpB,qBAAa,eAAe;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,IAAI,CAA+B;gBAE/B,SAAS,EAAE,MAAM;IAI7B,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,kBAAkB,IAAI,MAAM,CAE/B;IAED,MAAM,IAAI,OAAO;IAIjB,IAAI,IAAI,cAAc;IAYtB,UAAU,CAAC,eAAe,EAAE,gBAAgB,GAAG,IAAI;IAKnD;;;;OAIG;IACH,kBAAkB,CAAC,eAAe,EAAE,gBAAgB,GAAG,IAAI;IAS3D,IAAI,IAAI,IAAI;IAmBZ,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAM7C,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAKlC,WAAW,CACP,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,GAAG,eAAe,GAAG,cAAc,GAAG,OAAO,CAAC,CAAC,GACpG,IAAI;IASP,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKlC,YAAY,IAAI,IAAI;IAKpB,UAAU,IAAI,WAAW,EAAE;IAK3B,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAK9D,uBAAuB,IAAI,oBAAoB;IAQ/C,OAAO,CAAC,YAAY;CAKvB"}
@@ -84,6 +84,10 @@ export class LockfileManager {
84
84
  this.ensureLoaded();
85
85
  this.lock.patches = this.lock.patches.filter((p) => p.id !== patchId);
86
86
  }
87
+ clearPatches() {
88
+ this.ensureLoaded();
89
+ this.lock.patches = [];
90
+ }
87
91
  getPatches() {
88
92
  this.ensureLoaded();
89
93
  return this.lock.patches;
@@ -1 +1 @@
1
- {"version":3,"file":"LockfileManager.js","sourceRoot":"","sources":["../src/LockfileManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LockfileManager.js","sourceRoot":"","sources":["../src/LockfileManager.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,12 @@ export declare class ReplayDetector {
19
19
  * so rebased commits with same content produce the same hash.
20
20
  */
21
21
  computeContentHash(patchContent: string): string;
22
+ /**
23
+ * Detect customization patches via tree diff when the generation commit
24
+ * is not an ancestor of HEAD (non-linear history from squash merge).
25
+ * Returns a single composite patch covering all customizations.
26
+ */
27
+ private detectPatchesViaTreeDiff;
22
28
  private parseGitLog;
23
29
  private getLastGeneration;
24
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ReplayDetector.d.ts","sourceRoot":"","sources":["../src/ReplayDetector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAA8B,WAAW,EAAE,MAAM,YAAY,CAAC;AAM1E,qBAAa,cAAc;IACvB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,YAAY,CAAS;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAM;gBAErB,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM;IAM9E;;;;OAIG;IACG,gBAAgB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAiFhD;;;;OAIG;IACH,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAShD,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,iBAAiB;CAG5B"}
1
+ {"version":3,"file":"ReplayDetector.d.ts","sourceRoot":"","sources":["../src/ReplayDetector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAgD,WAAW,EAAE,MAAM,YAAY,CAAC;AAM5F,qBAAa,cAAc;IACvB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,YAAY,CAAS;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAM;gBAErB,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM;IAM9E;;;;OAIG;IACG,gBAAgB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAyFhD;;;;OAIG;IACH,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAShD;;;;OAIG;YACW,wBAAwB;IA+BtC,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,iBAAiB;CAG5B"}
@@ -30,6 +30,13 @@ export class ReplayDetector {
30
30
  `Skipping new patch detection. Existing lockfile patches will still be applied.`);
31
31
  return [];
32
32
  }
33
+ // If the generation commit is not an ancestor of HEAD (e.g., after a squash
34
+ // merge of a divergent PR), git log would return all of main's history.
35
+ // Fall back to tree-diff-based detection which handles non-linear history.
36
+ const isAncestor = await this.git.isAncestor(lastGen.commit_sha, "HEAD");
37
+ if (!isAncestor) {
38
+ return this.detectPatchesViaTreeDiff(lastGen);
39
+ }
33
40
  const log = await this.git.exec([
34
41
  "log",
35
42
  "--format=%H%x00%an%x00%ae%x00%s",
@@ -94,6 +101,39 @@ export class ReplayDetector {
94
101
  .join("\n");
95
102
  return `sha256:${createHash("sha256").update(normalized).digest("hex")}`;
96
103
  }
104
+ /**
105
+ * Detect customization patches via tree diff when the generation commit
106
+ * is not an ancestor of HEAD (non-linear history from squash merge).
107
+ * Returns a single composite patch covering all customizations.
108
+ */
109
+ async detectPatchesViaTreeDiff(lastGen) {
110
+ // Get changed files first, then filter before computing the diff
111
+ const filesOutput = await this.git.exec(["diff", "--name-only", lastGen.commit_sha, "HEAD"]);
112
+ const files = filesOutput
113
+ .trim()
114
+ .split("\n")
115
+ .filter(Boolean)
116
+ .filter((f) => !INFRASTRUCTURE_FILES.has(f))
117
+ .filter((f) => !f.startsWith(".fern/"));
118
+ if (files.length === 0)
119
+ return [];
120
+ // Compute diff only for the filtered files
121
+ const diff = await this.git.exec(["diff", lastGen.commit_sha, "HEAD", "--", ...files]);
122
+ if (!diff.trim())
123
+ return [];
124
+ const contentHash = this.computeContentHash(diff);
125
+ const headSha = (await this.git.exec(["rev-parse", "HEAD"])).trim();
126
+ return [{
127
+ id: `patch-composite-${headSha.slice(0, 8)}`,
128
+ content_hash: contentHash,
129
+ original_commit: headSha,
130
+ original_message: "Customer customizations (composite)",
131
+ original_author: "composite",
132
+ base_generation: lastGen.commit_sha,
133
+ files,
134
+ patch_content: diff,
135
+ }];
136
+ }
97
137
  parseGitLog(log) {
98
138
  return log
99
139
  .trim()
@@ -1 +1 @@
1
- {"version":3,"file":"ReplayDetector.js","sourceRoot":"","sources":["../src/ReplayDetector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ReplayDetector.js","sourceRoot":"","sources":["../src/ReplayDetector.ts"],"names":[],"mappings":"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"}
@@ -15,15 +15,12 @@ export interface ReplayReport {
15
15
  patchesRepointed?: number;
16
16
  patchesContentRebased?: number;
17
17
  patchesKeptAsUserOwned?: number;
18
+ patchesConflictResolved?: number;
18
19
  conflicts: FileResult[];
19
20
  conflictDetails?: ConflictDetail[];
20
21
  wouldApply?: StoredPatch[];
21
22
  warnings?: string[];
22
23
  }
23
- export declare class ReplayConflictError extends Error {
24
- readonly report: ReplayReport;
25
- constructor(report: ReplayReport);
26
- }
27
24
  export interface ReplayOptions {
28
25
  /** Log what would happen but don't modify anything */
29
26
  dryRun?: boolean;
@@ -36,17 +33,32 @@ export interface ReplayOptions {
36
33
  }
37
34
  export declare class ReplayService {
38
35
  private git;
39
- private config;
40
36
  private detector;
41
37
  private applicator;
42
38
  private committer;
43
39
  private lockManager;
44
40
  private outputDir;
45
- constructor(outputDir: string, config: ReplayConfig);
41
+ constructor(outputDir: string, _config: ReplayConfig);
46
42
  /**
47
43
  * Run the full replay flow.
48
44
  */
49
45
  runReplay(options?: ReplayOptions): Promise<ReplayReport>;
46
+ /**
47
+ * Sync the lockfile after a divergent PR was squash-merged.
48
+ *
49
+ * When a divergent PR is merged, the lockfile's current_generation is stale
50
+ * (points to gen N while the code is at gen N+1). This method:
51
+ * 1. Adds the real generation (from the tag) to the lockfile
52
+ * 2. Updates current_generation to point to it
53
+ * 3. Clears existing patches (superseded — they'll be re-detected via tree diff)
54
+ * 4. Saves the lockfile
55
+ *
56
+ * Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
57
+ */
58
+ syncFromDivergentMerge(generationCommitSha: string, options?: {
59
+ cliVersion?: string;
60
+ generatorVersions?: Record<string, string>;
61
+ }): Promise<void>;
50
62
  /**
51
63
  * Determine which flow to use based on lockfile state.
52
64
  */
@@ -63,33 +75,23 @@ export declare class ReplayService {
63
75
  * Normal flow - apply existing patches + detect new ones.
64
76
  */
65
77
  private handleNormalRegeneration;
66
- /**
67
- * Get the active conflict policy based on config and environment.
68
- * Returns undefined when on_conflict is not configured (backwards-compatible).
69
- */
70
- private getConflictPolicy;
71
- /**
72
- * Auto-resolve all conflict markers in conflicted files.
73
- * "ours" = keep generated code, "theirs" = keep user customization.
74
- */
75
- private autoResolveConflicts;
76
- /**
77
- * Phase 1: Resolve conflict markers on disk BEFORE rebasing.
78
- * Handles keep-mine, keep-generated, and prompt policies.
79
- * Must run before rebasePatches() so rebased content captures clean files.
80
- */
81
- private resolveConflictsOnDisk;
82
- /**
83
- * Phase 2: Throw for "fail" policy AFTER lockfile save.
84
- * This ensures the lockfile is preserved even when we reject the commit.
85
- */
86
- private enforceConflictFailPolicy;
87
78
  /**
88
79
  * Rebase cleanly applied patches so they are relative to the current generation.
89
80
  * This prevents recurring conflicts on subsequent regenerations.
90
81
  * Returns the number of patches rebased.
91
82
  */
92
83
  private rebasePatches;
84
+ /**
85
+ * Pre-generation rebase: update stale conflicted patches using the customer's
86
+ * resolved state. Called BEFORE commitGeneration() so that HEAD still reflects
87
+ * the customer's code (generator output is in the working tree, not committed).
88
+ *
89
+ * For each patch with a stale base_generation:
90
+ * - If conflict markers remain in HEAD → skip (not yet resolved)
91
+ * - If diff(currentGen, HEAD) is empty → customer reverted, remove patch
92
+ * - Otherwise → update patch_content, content_hash, base_generation
93
+ */
94
+ private preGenerationRebase;
93
95
  /**
94
96
  * Read .fernignore patterns from the output directory.
95
97
  * Returns an empty array if .fernignore doesn't exist.
@@ -1 +1 @@
1
- {"version":3,"file":"ReplayService.d.ts","sourceRoot":"","sources":["../src/ReplayService.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAgB,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtF,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,kBAAkB,GAAG,YAAY,GAAG,qBAAqB,CAAC;IAChE,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC1C,SAAgB,MAAM,EAAE,YAAY,CAAC;gBAEzB,MAAM,EAAE,YAAY;CAKnC;AAED,MAAM,WAAW,aAAa;IAC1B,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sDAAsD;IACtD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,SAAS,CAAS;gBAGtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY;IAYxB;;OAEG;IACG,SAAS,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAa/D;;OAEG;IACH,OAAO,CAAC,aAAa;IAarB;;OAEG;YACW,qBAAqB;IA+BnC;;OAEG;YACW,2BAA2B;IAoEzC;;OAEG;YACW,wBAAwB;IA6DtC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;;OAGG;YACW,oBAAoB;IAsBlC;;;;OAIG;YACW,sBAAsB;IA+DpC;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAkBjC;;;;OAIG;YACW,aAAa;IAqG3B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAS9B;;OAEG;IACH,OAAO,CAAC,WAAW;CAmCtB"}
1
+ {"version":3,"file":"ReplayService.d.ts","sourceRoot":"","sources":["../src/ReplayService.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAoB,YAAY,EAAgB,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExG,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,kBAAkB,GAAG,YAAY,GAAG,qBAAqB,CAAC;IAChE,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC1B,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sDAAsD;IACtD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,SAAS,CAAS;gBAGtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,YAAY;IAWzB;;OAEG;IACG,SAAS,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAa/D;;;;;;;;;;;OAWG;IACG,sBAAsB,CACxB,mBAAmB,EAAE,MAAM,EAC3B,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAC9E,OAAO,CAAC,IAAI,CAAC;IAwBhB;;OAEG;IACH,OAAO,CAAC,aAAa;IAarB;;OAEG;YACW,qBAAqB;IA+BnC;;OAEG;YACW,2BAA2B;IA2DzC;;OAEG;YACW,wBAAwB;IAiEtC;;;;OAIG;YACW,aAAa;IAoG3B;;;;;;;;;OASG;YACW,mBAAmB;IAkDjC;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAS9B;;OAEG;IACH,OAAO,CAAC,WAAW;CAsCtB"}
@@ -6,29 +6,16 @@ import { LockfileManager } from "./LockfileManager.js";
6
6
  import { ReplayDetector } from "./ReplayDetector.js";
7
7
  import { ReplayApplicator } from "./ReplayApplicator.js";
8
8
  import { ReplayCommitter } from "./ReplayCommitter.js";
9
- import { resolveConflictMarkers } from "./ConflictResolver.js";
10
- import { promptForConflictResolution } from "./ConflictPrompt.js";
11
- import { isCI } from "./environment.js";
12
- export class ReplayConflictError extends Error {
13
- report;
14
- constructor(report) {
15
- super(`Replay produced ${report.patchesWithConflicts} patch conflict(s)`);
16
- this.name = "ReplayConflictError";
17
- this.report = report;
18
- }
19
- }
20
9
  export class ReplayService {
21
10
  git;
22
- config;
23
11
  detector;
24
12
  applicator;
25
13
  committer;
26
14
  lockManager;
27
15
  outputDir;
28
- constructor(outputDir, config) {
16
+ constructor(outputDir, _config) {
29
17
  const git = new GitClient(outputDir);
30
18
  this.git = git;
31
- this.config = config;
32
19
  this.outputDir = outputDir;
33
20
  this.lockManager = new LockfileManager(outputDir);
34
21
  this.detector = new ReplayDetector(git, this.lockManager, outputDir);
@@ -49,6 +36,38 @@ export class ReplayService {
49
36
  return this.handleNormalRegeneration(options);
50
37
  }
51
38
  }
39
+ /**
40
+ * Sync the lockfile after a divergent PR was squash-merged.
41
+ *
42
+ * When a divergent PR is merged, the lockfile's current_generation is stale
43
+ * (points to gen N while the code is at gen N+1). This method:
44
+ * 1. Adds the real generation (from the tag) to the lockfile
45
+ * 2. Updates current_generation to point to it
46
+ * 3. Clears existing patches (superseded — they'll be re-detected via tree diff)
47
+ * 4. Saves the lockfile
48
+ *
49
+ * Call this BEFORE runReplay() when the CLI detects a merged divergent PR.
50
+ */
51
+ async syncFromDivergentMerge(generationCommitSha, options) {
52
+ const treeHash = await this.git.getTreeHash(generationCommitSha);
53
+ const timestamp = (await this.git.exec(["log", "-1", "--format=%aI", generationCommitSha])).trim();
54
+ const record = {
55
+ commit_sha: generationCommitSha,
56
+ tree_hash: treeHash,
57
+ timestamp,
58
+ cli_version: options?.cliVersion ?? "unknown",
59
+ generator_versions: options?.generatorVersions ?? {},
60
+ };
61
+ if (!this.lockManager.exists()) {
62
+ this.lockManager.initializeInMemory(record);
63
+ }
64
+ else {
65
+ this.lockManager.read();
66
+ this.lockManager.addGeneration(record);
67
+ this.lockManager.clearPatches();
68
+ }
69
+ this.lockManager.save();
70
+ }
52
71
  /**
53
72
  * Determine which flow to use based on lockfile state.
54
73
  */
@@ -120,10 +139,6 @@ export class ReplayService {
120
139
  if (newPatches.length > 0) {
121
140
  results = await this.applicator.applyPatches(newPatches);
122
141
  }
123
- // Phase 1: Resolve conflict markers on disk BEFORE rebasing.
124
- // This ensures rebasePatches() captures clean file content, not
125
- // content with conflict markers baked in.
126
- await this.resolveConflictsOnDisk(results, newPatches, "no-patches", options, warnings);
127
142
  // Rebase cleanly applied patches to the current generation.
128
143
  // This prevents recurring conflicts on subsequent regenerations.
129
144
  const rebaseCounts = await this.rebasePatches(results, genRecord.commit_sha);
@@ -135,9 +150,6 @@ export class ReplayService {
135
150
  }
136
151
  }
137
152
  this.lockManager.save();
138
- // Phase 2: For "fail" policy, throw AFTER lockfile save so patches
139
- // aren't lost, but BEFORE replay commit so broken code isn't committed.
140
- this.enforceConflictFailPolicy(results, newPatches, "no-patches", options, warnings, rebaseCounts);
141
153
  if (newPatches.length > 0) {
142
154
  if (!options?.stageOnly) {
143
155
  const appliedCount = results.filter((r) => r.status === "applied" || r.status === "conflict").length;
@@ -155,11 +167,11 @@ export class ReplayService {
155
167
  * Normal flow - apply existing patches + detect new ones.
156
168
  */
157
169
  async handleNormalRegeneration(options) {
158
- const existingPatches = this.lockManager.getPatches();
159
- const newPatches = await this.detector.detectNewPatches();
160
- const warnings = [...this.detector.warnings];
161
- const allPatches = [...existingPatches, ...newPatches];
162
170
  if (options?.dryRun) {
171
+ const existingPatches = this.lockManager.getPatches();
172
+ const newPatches = await this.detector.detectNewPatches();
173
+ const warnings = [...this.detector.warnings];
174
+ const allPatches = [...existingPatches, ...newPatches];
163
175
  return {
164
176
  flow: "normal-regeneration",
165
177
  patchesDetected: allPatches.length,
@@ -171,6 +183,15 @@ export class ReplayService {
171
183
  warnings: warnings.length > 0 ? warnings : undefined,
172
184
  };
173
185
  }
186
+ // Pre-generation rebase: update stale conflicted patches using customer's
187
+ // resolved state. At this point HEAD still has the customer's code — the
188
+ // generator output is in the working tree but not yet committed.
189
+ let existingPatches = this.lockManager.getPatches();
190
+ const preRebaseCounts = await this.preGenerationRebase(existingPatches);
191
+ existingPatches = this.lockManager.getPatches();
192
+ const newPatches = await this.detector.detectNewPatches();
193
+ const warnings = [...this.detector.warnings];
194
+ const allPatches = [...existingPatches, ...newPatches];
174
195
  const commitOpts = options ? {
175
196
  cliVersion: options.cliVersion ?? "unknown",
176
197
  generatorVersions: options.generatorVersions ?? {},
@@ -178,8 +199,6 @@ export class ReplayService {
178
199
  await this.committer.commitGeneration("Update SDK", commitOpts);
179
200
  const genRecord = await this.committer.createGenerationRecord(commitOpts);
180
201
  const results = await this.applicator.applyPatches(allPatches);
181
- // Phase 1: Resolve conflict markers on disk BEFORE rebasing.
182
- await this.resolveConflictsOnDisk(results, allPatches, "normal-regeneration", options, warnings);
183
202
  // Rebase cleanly applied patches to the current generation.
184
203
  const rebaseCounts = await this.rebasePatches(results, genRecord.commit_sha);
185
204
  // Save lockfile BEFORE commit — lockfile is source of truth.
@@ -190,8 +209,6 @@ export class ReplayService {
190
209
  }
191
210
  }
192
211
  this.lockManager.save();
193
- // Phase 2: For "fail" policy, throw AFTER lockfile save.
194
- this.enforceConflictFailPolicy(results, allPatches, "normal-regeneration", options, warnings, rebaseCounts);
195
212
  if (options?.stageOnly) {
196
213
  await this.committer.stageAll();
197
214
  }
@@ -201,119 +218,7 @@ export class ReplayService {
201
218
  await this.committer.commitReplay(appliedCount, allPatches);
202
219
  }
203
220
  }
204
- return this.buildReport("normal-regeneration", allPatches, results, options, warnings, rebaseCounts);
205
- }
206
- /**
207
- * Get the active conflict policy based on config and environment.
208
- * Returns undefined when on_conflict is not configured (backwards-compatible).
209
- */
210
- getConflictPolicy() {
211
- const onConflict = this.config.on_conflict;
212
- if (!onConflict)
213
- return undefined;
214
- return isCI() ? onConflict.ci : onConflict.local;
215
- }
216
- /**
217
- * Auto-resolve all conflict markers in conflicted files.
218
- * "ours" = keep generated code, "theirs" = keep user customization.
219
- */
220
- async autoResolveConflicts(results, strategy) {
221
- const { join } = await import("node:path");
222
- for (const result of results) {
223
- if (result.status !== "conflict" || !result.fileResults)
224
- continue;
225
- for (const fileResult of result.fileResults) {
226
- if (fileResult.status !== "conflict")
227
- continue;
228
- const filePath = join(this.outputDir, fileResult.file);
229
- await resolveConflictMarkers(filePath, strategy);
230
- fileResult.status = "merged";
231
- fileResult.reason = `auto-resolved (${strategy === "ours" ? "keep-generated" : "keep-mine"})`;
232
- }
233
- // If all files resolved, mark the patch as applied
234
- const allResolved = result.fileResults.every((f) => f.status !== "conflict");
235
- if (allResolved) {
236
- result.status = "applied";
237
- }
238
- }
239
- }
240
- /**
241
- * Phase 1: Resolve conflict markers on disk BEFORE rebasing.
242
- * Handles keep-mine, keep-generated, and prompt policies.
243
- * Must run before rebasePatches() so rebased content captures clean files.
244
- */
245
- async resolveConflictsOnDisk(results, allPatches, flow, options, warnings) {
246
- const hasConflicts = results.some((r) => r.status === "conflict");
247
- if (!hasConflicts)
248
- return;
249
- const policy = this.getConflictPolicy();
250
- if (policy === undefined)
251
- return; // backwards-compatible: commit with markers
252
- switch (policy) {
253
- case "fail":
254
- // Don't resolve — enforceConflictFailPolicy() handles this after lockfile save
255
- break;
256
- case "keep-mine":
257
- await this.autoResolveConflicts(results, "theirs");
258
- break;
259
- case "keep-generated":
260
- await this.autoResolveConflicts(results, "ours");
261
- break;
262
- case "prompt": {
263
- if (!process.stdin.isTTY)
264
- break; // No TTY → fall through to default
265
- const { join } = await import("node:path");
266
- for (const result of results) {
267
- if (result.status !== "conflict" || !result.fileResults)
268
- continue;
269
- for (const fileResult of result.fileResults) {
270
- if (fileResult.status !== "conflict")
271
- continue;
272
- const answer = await promptForConflictResolution({
273
- patchId: result.patch.id,
274
- patchMessage: result.patch.original_message,
275
- file: fileResult,
276
- });
277
- if (answer === "abort") {
278
- const report = this.buildReport(flow, allPatches, results, options, warnings);
279
- throw new ReplayConflictError(report);
280
- }
281
- if (answer === "ours" || answer === "theirs") {
282
- const filePath = join(this.outputDir, fileResult.file);
283
- await resolveConflictMarkers(filePath, answer);
284
- fileResult.status = "merged";
285
- fileResult.reason = `resolved (${answer === "ours" ? "keep-generated" : "keep-mine"})`;
286
- }
287
- // "manual" → leave conflict markers
288
- }
289
- const allResolved = result.fileResults.every((f) => f.status !== "conflict");
290
- if (allResolved) {
291
- result.status = "applied";
292
- }
293
- }
294
- break;
295
- }
296
- case "create-pr":
297
- // Future feature. Fall through with a warning.
298
- if (warnings) {
299
- warnings.push("on_conflict: create-pr is not yet implemented; conflict markers left in place");
300
- }
301
- break;
302
- }
303
- }
304
- /**
305
- * Phase 2: Throw for "fail" policy AFTER lockfile save.
306
- * This ensures the lockfile is preserved even when we reject the commit.
307
- */
308
- enforceConflictFailPolicy(results, allPatches, flow, options, warnings, rebaseCounts) {
309
- const hasConflicts = results.some((r) => r.status === "conflict");
310
- if (!hasConflicts)
311
- return;
312
- const policy = this.getConflictPolicy();
313
- if (policy !== "fail")
314
- return;
315
- const report = this.buildReport(flow, allPatches, results, options, warnings, rebaseCounts);
316
- throw new ReplayConflictError(report);
221
+ return this.buildReport("normal-regeneration", allPatches, results, options, warnings, rebaseCounts, preRebaseCounts);
317
222
  }
318
223
  /**
319
224
  * Rebase cleanly applied patches so they are relative to the current generation.
@@ -380,9 +285,8 @@ export class ReplayService {
380
285
  ]).catch(() => null);
381
286
  if (!diff || !diff.trim()) {
382
287
  // Patch is now empty — customization was absorbed by the generator.
383
- // Only removePatch (not absorbedPatchIds) — new patches that haven't
384
- // been added yet are correctly ignored by the no-op removePatch.
385
288
  this.lockManager.removePatch(patch.id);
289
+ absorbedPatchIds.add(patch.id);
386
290
  absorbed++;
387
291
  continue;
388
292
  }
@@ -410,6 +314,55 @@ export class ReplayService {
410
314
  }
411
315
  return { absorbed, repointed, contentRebased, keptAsUserOwned, absorbedPatchIds };
412
316
  }
317
+ /**
318
+ * Pre-generation rebase: update stale conflicted patches using the customer's
319
+ * resolved state. Called BEFORE commitGeneration() so that HEAD still reflects
320
+ * the customer's code (generator output is in the working tree, not committed).
321
+ *
322
+ * For each patch with a stale base_generation:
323
+ * - If conflict markers remain in HEAD → skip (not yet resolved)
324
+ * - If diff(currentGen, HEAD) is empty → customer reverted, remove patch
325
+ * - Otherwise → update patch_content, content_hash, base_generation
326
+ */
327
+ async preGenerationRebase(patches) {
328
+ const lock = this.lockManager.read();
329
+ const currentGen = lock.current_generation;
330
+ let conflictResolved = 0;
331
+ let conflictAbsorbed = 0;
332
+ for (const patch of patches) {
333
+ if (patch.base_generation === currentGen)
334
+ continue;
335
+ try {
336
+ // Check for unresolved conflict markers in HEAD
337
+ const markerFiles = await this.git.exec(["grep", "-l", "<<<<<<<", "HEAD", "--", ...patch.files]).catch(() => "");
338
+ if (markerFiles.trim())
339
+ continue;
340
+ // Compute customer's resolved state relative to current generation
341
+ const diff = await this.git.exec(["diff", currentGen, "HEAD", "--", ...patch.files]).catch(() => null);
342
+ // Diff command failed (e.g., invalid SHA) — skip this patch
343
+ if (diff === null)
344
+ continue;
345
+ if (!diff.trim()) {
346
+ // Empty diff — customer reverted their customization
347
+ this.lockManager.removePatch(patch.id);
348
+ conflictAbsorbed++;
349
+ continue;
350
+ }
351
+ // Update the patch with the resolved content
352
+ const newContentHash = this.detector.computeContentHash(diff);
353
+ this.lockManager.updatePatch(patch.id, {
354
+ base_generation: currentGen,
355
+ patch_content: diff,
356
+ content_hash: newContentHash,
357
+ });
358
+ conflictResolved++;
359
+ }
360
+ catch {
361
+ // If anything fails, leave the patch unchanged
362
+ }
363
+ }
364
+ return { conflictResolved, conflictAbsorbed };
365
+ }
413
366
  /**
414
367
  * Read .fernignore patterns from the output directory.
415
368
  * Returns an empty array if .fernignore doesn't exist.
@@ -426,7 +379,7 @@ export class ReplayService {
426
379
  /**
427
380
  * Build the final report from results.
428
381
  */
429
- buildReport(flow, patches, results, options, warnings, rebaseCounts) {
382
+ buildReport(flow, patches, results, options, warnings, rebaseCounts, preRebaseCounts) {
430
383
  const conflictResults = results.filter((r) => r.status === "conflict");
431
384
  const conflictDetails = conflictResults
432
385
  .map((r) => ({
@@ -446,6 +399,8 @@ export class ReplayService {
446
399
  patchesRepointed: rebaseCounts && rebaseCounts.repointed > 0 ? rebaseCounts.repointed : undefined,
447
400
  patchesContentRebased: rebaseCounts && rebaseCounts.contentRebased > 0 ? rebaseCounts.contentRebased : undefined,
448
401
  patchesKeptAsUserOwned: rebaseCounts && rebaseCounts.keptAsUserOwned > 0 ? rebaseCounts.keptAsUserOwned : undefined,
402
+ patchesConflictResolved: preRebaseCounts && (preRebaseCounts.conflictResolved + preRebaseCounts.conflictAbsorbed) > 0
403
+ ? preRebaseCounts.conflictResolved + preRebaseCounts.conflictAbsorbed : undefined,
449
404
  conflicts: conflictResults
450
405
  .flatMap((r) => r.fileResults?.filter((f) => f.status === "conflict") ?? []),
451
406
  conflictDetails: conflictDetails.length > 0 ? conflictDetails : undefined,