@fern-api/fern-api-dev 5.51.2 → 5.52.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.
Files changed (2) hide show
  1. package/cli.cjs +499 -133
  2. package/package.json +1 -1
package/cli.cjs CHANGED
@@ -116508,6 +116508,7 @@ __export(DocsYmlSchemas_exports, {
116508
116508
  BodyThemeConfig: () => BodyThemeConfig,
116509
116509
  ChangelogConfiguration: () => ChangelogConfiguration,
116510
116510
  ChangelogFolderRelativePath: () => ChangelogFolderRelativePath,
116511
+ ChangelogLayout: () => ChangelogLayout,
116511
116512
  CheckConfig: () => CheckConfig,
116512
116513
  CheckRuleSeverity: () => CheckRuleSeverity,
116513
116514
  CheckRulesConfig: () => CheckRulesConfig,
@@ -116634,7 +116635,7 @@ function normalizeTranslationConfig(config5) {
116634
116635
  }
116635
116636
  return config5;
116636
116637
  }
116637
- var TabId, ChangelogFolderRelativePath, AudienceId, RoleId, LibraryName, ProgrammingLanguage, Language, PageActionOption, AIChatLocation, AIChatModel, EditThisPageLaunch, Availability2, VersionAvailability, TitleSource, LibraryLanguage, FontStyle, FontDisplay, SearchbarPlacement, TabsPlacement, SwitcherPlacement, ContentAlignment, HeaderPosition, ProductSwitcherThemeConfig, LanguageSwitcherThemeConfig, FooterNavThemeConfig, TabsThemeConfig, BodyThemeConfig, SidebarThemeConfig, PageActionsThemeConfig, HttpSnippetLanguage, Target, TwitterCardSetting, JsScriptStrategy, FontWeight, ColorThemedConfig, ColorConfig, Audience, Role, CustomDomain, HttpSnippetsConfig, BackgroundImageThemedConfig, BackgroundImageConfiguration, CssConfig, CollapsedStringValue, CollapsedValue, WithPermissions, FeatureFlag, FeatureFlagConfiguration, WithFeatureFlags, WithViewers, SegmentConfig, FullStoryAnalyticsConfig, IntercomConfig, PostHogConfig, GTMConfig, GoogleAnalytics4Config, AnalyticsConfig, AiExamplesConfig, AIChatWebsiteDatasource, AIChatDatasource, PageDescriptionSource, AgentsConfig, AIChatConfig, FontConfigVariant, FontConfigPath, FontConfig, DocsTypographyConfig, ThemeConfig, LayoutConfig, SearchSettingsConfig, DocsSettingsConfig, ColorsConfiguration, CustomPageAction, PageActionOptions, PageActionsConfig, GithubEditThisPageConfig, EditThisPageConfig, DocsInstance, LogoConfiguration, LinkConfiguration, VersionedSnippetLanguageConfiguration, SnippetLanguageConfiguration, SnippetsConfiguration, PlaygroundButtonSettings, PlaygroundSettings, AnnouncementConfig, NavbarLinkConfig, NavbarGithubConfigWithOptions, NavbarGithubConfig, NavbarDropdownConfig, NavbarLink, FooterLinksConfig, JsFileConfigSettings, JsFileConfig, JsRemoteConfig, JsConfigOptions, JsConfig, MetadataConfig, RedirectConfig, CheckRuleSeverity, CheckRulesConfig, CheckConfig, IntegrationsConfig, ExperimentalConfig, GitLibraryInputSchema, PathLibraryInputSchema, LibraryInputConfiguration, LibraryOutputConfiguration, LibraryConfiguration, PageConfiguration, ChangelogConfiguration, LibraryReferenceConfiguration, FolderConfiguration, ApiReferenceEndpointConfiguration, ApiReferenceOperationConfiguration, ApiReferenceLayoutItem, ApiReferenceSectionConfiguration, ApiReferencePackageConfigurationWithOptions, ApiReferencePackageConfiguration, ApiReferenceConfiguration, NavigationItem, SectionConfiguration, UntabbedNavigationConfig, TabVariant, TabbedNavigationItemWithLayout, TabbedNavigationItemWithVariants, TabbedNavigationItem, TabbedNavigationConfig, NavigationConfig, TabConfig, VersionConfig, VersionFileConfig, ProductConfigBase, InternalProduct, ExternalProduct, ProductConfig, RelativeProductPath, AbsoluteProductPath, ProductPath, ProductFileConfig, TranslationConfigObject, TranslationConfig, DocsConfiguration;
116638
+ var TabId, ChangelogFolderRelativePath, AudienceId, RoleId, LibraryName, ProgrammingLanguage, Language, PageActionOption, AIChatLocation, AIChatModel, EditThisPageLaunch, Availability2, VersionAvailability, TitleSource, LibraryLanguage, FontStyle, FontDisplay, SearchbarPlacement, TabsPlacement, SwitcherPlacement, ContentAlignment, HeaderPosition, ChangelogLayout, ProductSwitcherThemeConfig, LanguageSwitcherThemeConfig, FooterNavThemeConfig, TabsThemeConfig, BodyThemeConfig, SidebarThemeConfig, PageActionsThemeConfig, HttpSnippetLanguage, Target, TwitterCardSetting, JsScriptStrategy, FontWeight, ColorThemedConfig, ColorConfig, Audience, Role, CustomDomain, HttpSnippetsConfig, BackgroundImageThemedConfig, BackgroundImageConfiguration, CssConfig, CollapsedStringValue, CollapsedValue, WithPermissions, FeatureFlag, FeatureFlagConfiguration, WithFeatureFlags, WithViewers, SegmentConfig, FullStoryAnalyticsConfig, IntercomConfig, PostHogConfig, GTMConfig, GoogleAnalytics4Config, AnalyticsConfig, AiExamplesConfig, AIChatWebsiteDatasource, AIChatDatasource, PageDescriptionSource, AgentsConfig, AIChatConfig, FontConfigVariant, FontConfigPath, FontConfig, DocsTypographyConfig, ThemeConfig, LayoutConfig, SearchSettingsConfig, DocsSettingsConfig, ColorsConfiguration, CustomPageAction, PageActionOptions, PageActionsConfig, GithubEditThisPageConfig, EditThisPageConfig, DocsInstance, LogoConfiguration, LinkConfiguration, VersionedSnippetLanguageConfiguration, SnippetLanguageConfiguration, SnippetsConfiguration, PlaygroundButtonSettings, PlaygroundSettings, AnnouncementConfig, NavbarLinkConfig, NavbarGithubConfigWithOptions, NavbarGithubConfig, NavbarDropdownConfig, NavbarLink, FooterLinksConfig, JsFileConfigSettings, JsFileConfig, JsRemoteConfig, JsConfigOptions, JsConfig, MetadataConfig, RedirectConfig, CheckRuleSeverity, CheckRulesConfig, CheckConfig, IntegrationsConfig, ExperimentalConfig, GitLibraryInputSchema, PathLibraryInputSchema, LibraryInputConfiguration, LibraryOutputConfiguration, LibraryConfiguration, PageConfiguration, ChangelogConfiguration, LibraryReferenceConfiguration, FolderConfiguration, ApiReferenceEndpointConfiguration, ApiReferenceOperationConfiguration, ApiReferenceLayoutItem, ApiReferenceSectionConfiguration, ApiReferencePackageConfigurationWithOptions, ApiReferencePackageConfiguration, ApiReferenceConfiguration, NavigationItem, SectionConfiguration, UntabbedNavigationConfig, TabVariant, TabbedNavigationItemWithLayout, TabbedNavigationItemWithVariants, TabbedNavigationItem, TabbedNavigationConfig, NavigationConfig, TabConfig, VersionConfig, VersionFileConfig, ProductConfigBase, InternalProduct, ExternalProduct, ProductConfig, RelativeProductPath, AbsoluteProductPath, ProductPath, ProductFileConfig, TranslationConfigObject, TranslationConfig, DocsConfiguration;
116638
116639
  var init_DocsYmlSchemas = __esm({
116639
116640
  "../configuration/lib/docs-yml/DocsYmlSchemas.js"() {
116640
116641
  "use strict";
@@ -116694,6 +116695,7 @@ var init_DocsYmlSchemas = __esm({
116694
116695
  SwitcherPlacement = external_exports.enum(["header", "sidebar"]);
116695
116696
  ContentAlignment = external_exports.enum(["center", "left"]);
116696
116697
  HeaderPosition = external_exports.enum(["fixed", "static"]);
116698
+ ChangelogLayout = external_exports.enum(["timeline", "classic"]);
116697
116699
  ProductSwitcherThemeConfig = external_exports.enum(["default", "toggle", "tabs"]);
116698
116700
  LanguageSwitcherThemeConfig = external_exports.enum(["default", "minimal"]);
116699
116701
  FooterNavThemeConfig = external_exports.enum(["default", "minimal"]);
@@ -116843,7 +116845,8 @@ var init_DocsYmlSchemas = __esm({
116843
116845
  "disable-header": external_exports.boolean().optional(),
116844
116846
  "hide-nav-links": external_exports.boolean().optional(),
116845
116847
  "hide-feedback": external_exports.boolean().optional(),
116846
- "mobile-toc": external_exports.boolean().optional()
116848
+ "mobile-toc": external_exports.boolean().optional(),
116849
+ "changelog-layout": ChangelogLayout.optional()
116847
116850
  });
116848
116851
  SearchSettingsConfig = external_exports.object({
116849
116852
  "prioritize-current-product": external_exports.boolean().optional(),
@@ -117580,6 +117583,18 @@ var init_ChangelogFolderRelativePath = __esm({
117580
117583
  }
117581
117584
  });
117582
117585
 
117586
+ // ../configuration/lib/docs-yml/schemas/sdk/api/resources/docs/types/ChangelogLayout.js
117587
+ var ChangelogLayout2;
117588
+ var init_ChangelogLayout = __esm({
117589
+ "../configuration/lib/docs-yml/schemas/sdk/api/resources/docs/types/ChangelogLayout.js"() {
117590
+ "use strict";
117591
+ ChangelogLayout2 = {
117592
+ Timeline: "timeline",
117593
+ Classic: "classic"
117594
+ };
117595
+ }
117596
+ });
117597
+
117583
117598
  // ../configuration/lib/docs-yml/schemas/sdk/api/resources/docs/types/CheckConfig.js
117584
117599
  var init_CheckConfig = __esm({
117585
117600
  "../configuration/lib/docs-yml/schemas/sdk/api/resources/docs/types/CheckConfig.js"() {
@@ -118689,6 +118704,7 @@ var init_types96 = __esm({
118689
118704
  init_BodyThemeConfig();
118690
118705
  init_ChangelogConfiguration();
118691
118706
  init_ChangelogFolderRelativePath();
118707
+ init_ChangelogLayout();
118692
118708
  init_CheckConfig();
118693
118709
  init_CheckRuleSeverity();
118694
118710
  init_CheckRulesConfig();
@@ -118826,6 +118842,7 @@ __export(docs_exports, {
118826
118842
  AiChatModel: () => AiChatModel,
118827
118843
  Availability: () => Availability3,
118828
118844
  BodyThemeConfig: () => BodyThemeConfig2,
118845
+ ChangelogLayout: () => ChangelogLayout2,
118829
118846
  CheckRuleSeverity: () => CheckRuleSeverity2,
118830
118847
  CollapsedStringValue: () => CollapsedStringValue2,
118831
118848
  ContentAlignment: () => ContentAlignment2,
@@ -118879,6 +118896,7 @@ __export(api_exports2, {
118879
118896
  AiChatModel: () => AiChatModel,
118880
118897
  Availability: () => Availability3,
118881
118898
  BodyThemeConfig: () => BodyThemeConfig2,
118899
+ ChangelogLayout: () => ChangelogLayout2,
118882
118900
  CheckRuleSeverity: () => CheckRuleSeverity2,
118883
118901
  CollapsedStringValue: () => CollapsedStringValue2,
118884
118902
  ContentAlignment: () => ContentAlignment2,
@@ -121801,6 +121819,16 @@ var init_ChangelogConfiguration2 = __esm({
121801
121819
  }
121802
121820
  });
121803
121821
 
121822
+ // ../configuration/lib/docs-yml/schemas/sdk/serialization/resources/docs/types/ChangelogLayout.js
121823
+ var ChangelogLayout3;
121824
+ var init_ChangelogLayout2 = __esm({
121825
+ "../configuration/lib/docs-yml/schemas/sdk/serialization/resources/docs/types/ChangelogLayout.js"() {
121826
+ "use strict";
121827
+ init_core5();
121828
+ ChangelogLayout3 = schemas_exports6.enum_(["timeline", "classic"]);
121829
+ }
121830
+ });
121831
+
121804
121832
  // ../configuration/lib/docs-yml/schemas/sdk/serialization/resources/docs/types/CheckRuleSeverity.js
121805
121833
  var CheckRuleSeverity3;
121806
121834
  var init_CheckRuleSeverity2 = __esm({
@@ -122388,6 +122416,7 @@ var init_LayoutConfig2 = __esm({
122388
122416
  "../configuration/lib/docs-yml/schemas/sdk/serialization/resources/docs/types/LayoutConfig.js"() {
122389
122417
  "use strict";
122390
122418
  init_core5();
122419
+ init_ChangelogLayout2();
122391
122420
  init_ContentAlignment2();
122392
122421
  init_HeaderPosition2();
122393
122422
  init_SearchbarPlacement2();
@@ -122406,7 +122435,8 @@ var init_LayoutConfig2 = __esm({
122406
122435
  disableHeader: schemas_exports6.property("disable-header", schemas_exports6.boolean().optional()),
122407
122436
  hideNavLinks: schemas_exports6.property("hide-nav-links", schemas_exports6.boolean().optional()),
122408
122437
  hideFeedback: schemas_exports6.property("hide-feedback", schemas_exports6.boolean().optional()),
122409
- mobileToc: schemas_exports6.property("mobile-toc", schemas_exports6.boolean().optional())
122438
+ mobileToc: schemas_exports6.property("mobile-toc", schemas_exports6.boolean().optional()),
122439
+ changelogLayout: schemas_exports6.property("changelog-layout", ChangelogLayout3.optional())
122410
122440
  });
122411
122441
  }
122412
122442
  });
@@ -123486,6 +123516,7 @@ var init_types97 = __esm({
123486
123516
  init_BodyThemeConfig2();
123487
123517
  init_ChangelogConfiguration2();
123488
123518
  init_ChangelogFolderRelativePath2();
123519
+ init_ChangelogLayout2();
123489
123520
  init_CheckConfig2();
123490
123521
  init_CheckRuleSeverity2();
123491
123522
  init_CheckRulesConfig2();
@@ -123644,6 +123675,7 @@ __export(docs_exports2, {
123644
123675
  BodyThemeConfig: () => BodyThemeConfig3,
123645
123676
  ChangelogConfiguration: () => ChangelogConfiguration2,
123646
123677
  ChangelogFolderRelativePath: () => ChangelogFolderRelativePath2,
123678
+ ChangelogLayout: () => ChangelogLayout3,
123647
123679
  CheckConfig: () => CheckConfig2,
123648
123680
  CheckRuleSeverity: () => CheckRuleSeverity3,
123649
123681
  CheckRulesConfig: () => CheckRulesConfig2,
@@ -123816,6 +123848,7 @@ __export(serialization_exports3, {
123816
123848
  BodyThemeConfig: () => BodyThemeConfig3,
123817
123849
  ChangelogConfiguration: () => ChangelogConfiguration2,
123818
123850
  ChangelogFolderRelativePath: () => ChangelogFolderRelativePath2,
123851
+ ChangelogLayout: () => ChangelogLayout3,
123819
123852
  CheckConfig: () => CheckConfig2,
123820
123853
  CheckRuleSeverity: () => CheckRuleSeverity3,
123821
123854
  CheckRulesConfig: () => CheckRulesConfig2,
@@ -123970,6 +124003,7 @@ __export(schemas_exports7, {
123970
124003
  AiChatModel: () => AiChatModel,
123971
124004
  Availability: () => Availability3,
123972
124005
  BodyThemeConfig: () => BodyThemeConfig2,
124006
+ ChangelogLayout: () => ChangelogLayout2,
123973
124007
  CheckRuleSeverity: () => CheckRuleSeverity2,
123974
124008
  CollapsedStringValue: () => CollapsedStringValue2,
123975
124009
  ContentAlignment: () => ContentAlignment2,
@@ -232197,6 +232231,11 @@ function convertLayoutConfig(layout, themeTabsAlignment, themeTabsPlacement) {
232197
232231
  hideNavLinks: layout.hideNavLinks ?? false,
232198
232232
  hideFeedback: layout.hideFeedback ?? false,
232199
232233
  mobileToc: layout.mobileToc ?? false,
232234
+ // Passed through as-is (no default): omitted renders the searchable
232235
+ // timeline, "classic" renders the legacy stacked layout. Resolved by the
232236
+ // fern-platform companion PR. Part of the `as unknown as` cast below
232237
+ // until the published FDR SDK adds `changelogLayout`.
232238
+ changelogLayout: layout.changelogLayout,
232200
232239
  tabsAlignment: resolvedTabsAlignment
232201
232240
  };
232202
232241
  }
@@ -569724,9 +569763,9 @@ var require_diff3 = __commonJS({
569724
569763
  }
569725
569764
  });
569726
569765
 
569727
- // ../../../node_modules/.pnpm/@fern-api+replay@0.18.0/node_modules/@fern-api/replay/dist/index.cjs
569766
+ // ../../../node_modules/.pnpm/@fern-api+replay@0.19.0/node_modules/@fern-api/replay/dist/index.cjs
569728
569767
  var require_dist24 = __commonJS({
569729
- "../../../node_modules/.pnpm/@fern-api+replay@0.18.0/node_modules/@fern-api/replay/dist/index.cjs"(exports2, module4) {
569768
+ "../../../node_modules/.pnpm/@fern-api+replay@0.19.0/node_modules/@fern-api/replay/dist/index.cjs"(exports2, module4) {
569730
569769
  "use strict";
569731
569770
  var __defProp5 = Object.defineProperty;
569732
569771
  var __getOwnPropDesc4 = Object.getOwnPropertyDescriptor;
@@ -570144,15 +570183,15 @@ var require_dist24 = __commonJS({
570144
570183
  async function applyPatchToContent(base4, patchContent, filePath) {
570145
570184
  const fileDiff = extractFileDiff(patchContent, filePath);
570146
570185
  if (!fileDiff) return null;
570147
- const tempDir2 = await (0, import_promises510.mkdtemp)((0, import_node_path72.join)((0, import_node_os32.tmpdir)(), "replay-migrate-"));
570186
+ const tempDir2 = await (0, import_promises510.mkdtemp)((0, import_node_path82.join)((0, import_node_os32.tmpdir)(), "replay-migrate-"));
570148
570187
  const tempGit = new GitClient(tempDir2);
570149
570188
  try {
570150
570189
  await tempGit.exec(["init"]);
570151
570190
  await tempGit.exec(["config", "user.email", "migrate@fern.com"]);
570152
570191
  await tempGit.exec(["config", "user.name", "Fern Replay Migration"]);
570153
570192
  await tempGit.exec(["config", "commit.gpgSign", "false"]);
570154
- const tempFilePath = (0, import_node_path72.join)(tempDir2, filePath);
570155
- await (0, import_promises510.mkdir)((0, import_node_path72.dirname)(tempFilePath), { recursive: true });
570193
+ const tempFilePath = (0, import_node_path82.join)(tempDir2, filePath);
570194
+ await (0, import_promises510.mkdir)((0, import_node_path82.dirname)(tempFilePath), { recursive: true });
570156
570195
  await (0, import_promises510.writeFile)(tempFilePath, base4);
570157
570196
  await tempGit.exec(["add", filePath]);
570158
570197
  await tempGit.exec(["commit", "-m", `base for ${filePath}`, "--allow-empty"]);
@@ -570185,13 +570224,13 @@ var require_dist24 = __commonJS({
570185
570224
  }
570186
570225
  var import_promises510;
570187
570226
  var import_node_os32;
570188
- var import_node_path72;
570227
+ var import_node_path82;
570189
570228
  var init_PatchApplyTheirs = __esm3({
570190
570229
  "src/PatchApplyTheirs.ts"() {
570191
570230
  "use strict";
570192
570231
  import_promises510 = require("fs/promises");
570193
570232
  import_node_os32 = require("os");
570194
- import_node_path72 = require("path");
570233
+ import_node_path82 = require("path");
570195
570234
  init_GitClient();
570196
570235
  }
570197
570236
  });
@@ -570456,6 +570495,22 @@ var require_dist24 = __commonJS({
570456
570495
  this.lock.forgotten_hashes.push(hash5);
570457
570496
  }
570458
570497
  }
570498
+ /**
570499
+ * Record a dismissed customization: visibility metadata in `dismissals`
570500
+ * plus a tombstone in `forgotten_hashes` for every content hash.
570501
+ * `forgotten_hashes` stays the only list the detector consults; the
570502
+ * metadata exists so `status` can show what was dismissed.
570503
+ */
570504
+ addDismissal(record23) {
570505
+ this.ensureLoaded();
570506
+ if (!this.lock.dismissals) {
570507
+ this.lock.dismissals = [];
570508
+ }
570509
+ this.lock.dismissals.push(record23);
570510
+ for (const hash5 of record23.content_hashes) {
570511
+ this.addForgottenHash(hash5);
570512
+ }
570513
+ }
570459
570514
  getUnresolvedPatches() {
570460
570515
  this.ensureLoaded();
570461
570516
  return this.lock.patches.filter((p6) => p6.status === "unresolved");
@@ -570617,12 +570672,14 @@ var require_dist24 = __commonJS({
570617
570672
  return snapshot;
570618
570673
  }
570619
570674
  function filterInfrastructureAndSensitiveFiles(rawFilesOutput, fernignorePatterns) {
570620
- const allFiles = rawFilesOutput.trim().split("\n").filter(Boolean).filter((f3) => !INFRASTRUCTURE_FILES.has(f3)).filter((f3) => !f3.startsWith(".fern/"));
570675
+ const rawFiles = rawFilesOutput.trim().split("\n").filter(Boolean);
570676
+ const infrastructureFiles = rawFiles.filter((f3) => INFRASTRUCTURE_FILES.has(f3) || f3.startsWith(".fern/"));
570677
+ const allFiles = rawFiles.filter((f3) => !INFRASTRUCTURE_FILES.has(f3) && !f3.startsWith(".fern/"));
570621
570678
  const sensitiveFiles = allFiles.filter((f3) => isSensitiveFile(f3));
570622
570679
  const nonSensitive = allFiles.filter((f3) => !isSensitiveFile(f3));
570623
570680
  const fernignoredFiles = nonSensitive.filter((f3) => matchesFernignorePattern(f3, fernignorePatterns));
570624
570681
  const files = nonSensitive.filter((f3) => !matchesFernignorePattern(f3, fernignorePatterns));
570625
- return { files, sensitiveFiles, fernignoredFiles };
570682
+ return { files, sensitiveFiles, fernignoredFiles, infrastructureFiles };
570626
570683
  }
570627
570684
  var ReplayDetector = class {
570628
570685
  git;
@@ -570630,6 +570687,15 @@ var require_dist24 = __commonJS({
570630
570687
  sdkOutputDir;
570631
570688
  fernignorePatterns;
570632
570689
  warnings = [];
570690
+ /**
570691
+ * Structured degraded-state channel, mirroring `warnings`. Populated when
570692
+ * detection cannot guarantee customizations were carried forward (baseline
570693
+ * unreachable in this clone). Cleared per-run by `ReplayService.prepareReplay`
570694
+ * alongside `warnings`. Merged into `ReplayReport.degradedReasons` by each
570695
+ * flow handler. Degraded is observability only: detection results are
570696
+ * unchanged.
570697
+ */
570698
+ degradedReasons = [];
570633
570699
  constructor(git, lockManager, sdkOutputDir, fernignorePatterns = []) {
570634
570700
  this.git = git;
570635
570701
  this.lockManager = lockManager;
@@ -570700,6 +570766,10 @@ var require_dist24 = __commonJS({
570700
570766
  this.warnings.push(
570701
570767
  `No generation boundary found in git history and the lockfile's recorded generation (${lock.current_generation.slice(0, 7)}) matches no entry in its generation records. Customer customizations may differ from the last known generation and could be lost on the next regeneration. Run \`fern-replay bootstrap --force\` to re-initialize tracking.`
570702
570768
  );
570769
+ this.degradedReasons.push({
570770
+ code: "baseline-unresolvable",
570771
+ message: `The previous generation recorded in the lockfile (${lock.current_generation.slice(0, 7)}) could not be resolved to any reachable baseline during this run. Customizations may not have been carried forward \u2014 review the result before merging.`
570772
+ });
570703
570773
  }
570704
570774
  return { patches: [], revertedPatchIds: [] };
570705
570775
  }
@@ -570708,6 +570778,12 @@ var require_dist24 = __commonJS({
570708
570778
  this.warnings.push(
570709
570779
  `Generation commit ${lastGen.commit_sha.slice(0, 7)} not found in git history. Falling back to tree-diff detection (likely a shallow clone).`
570710
570780
  );
570781
+ if (lock.patches.length > 0) {
570782
+ this.degradedReasons.push({
570783
+ code: "generation-anchor-unreachable",
570784
+ message: `The previous generation (${lastGen.commit_sha.slice(0, 7)}) was not reachable during this run, and ${lock.patches.length} tracked customization(s) could not be verified against it. Customizations may not have been carried forward \u2014 review the result before merging.`
570785
+ });
570786
+ }
570711
570787
  return this.detectPatchesViaTreeDiff(
570712
570788
  lastGen,
570713
570789
  /* commitKnownMissing */
@@ -570747,54 +570823,27 @@ var require_dist24 = __commonJS({
570747
570823
  if (lock.patches.find((p6) => p6.original_commit === commit.sha)) {
570748
570824
  continue;
570749
570825
  }
570750
- const parents = await this.git.getCommitParents(commit.sha);
570751
- let filesOutput;
570752
- try {
570753
- filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
570754
- } catch {
570826
+ const materialized = await this.materializeCommitPatch(commit.sha);
570827
+ if (materialized.sensitiveFiles.length > 0) {
570755
570828
  this.warnings.push(
570756
- `Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
570829
+ `Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${materialized.sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
570757
570830
  );
570758
- continue;
570759
570831
  }
570760
- const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
570761
- filesOutput,
570762
- this.fernignorePatterns
570763
- );
570764
- if (sensitiveFiles.length > 0) {
570832
+ if (materialized.fernignoredFiles.length > 0) {
570765
570833
  this.warnings.push(
570766
- `Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
570834
+ `.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${materialized.fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
570767
570835
  );
570768
570836
  }
570769
- if (fernignoredFiles.length > 0) {
570837
+ if (materialized.status === "unreachable") {
570770
570838
  this.warnings.push(
570771
- `.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
570839
+ `Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
570772
570840
  );
570773
- }
570774
- if (files.length === 0) {
570775
570841
  continue;
570776
570842
  }
570777
- const wasFiltered = sensitiveFiles.length > 0 || fernignoredFiles.length > 0;
570778
- const parentSha = parents[0];
570779
- let patchContent;
570780
- if (wasFiltered && parentSha) {
570781
- try {
570782
- patchContent = await this.git.exec(["diff", parentSha, commit.sha, "--", ...files]);
570783
- } catch {
570784
- continue;
570785
- }
570786
- if (!patchContent.trim()) continue;
570787
- } else {
570788
- try {
570789
- patchContent = await this.git.formatPatch(commit.sha);
570790
- } catch {
570791
- this.warnings.push(
570792
- `Could not generate patch for commit ${commit.sha.slice(0, 7)} \u2014 it may be unreachable in a shallow clone. Skipping.`
570793
- );
570794
- continue;
570795
- }
570843
+ if (materialized.status === "empty") {
570844
+ continue;
570796
570845
  }
570797
- const contentHash = this.computeContentHash(patchContent);
570846
+ const { files, patchContent, contentHash } = materialized;
570798
570847
  if (lock.patches.find((p6) => p6.content_hash === contentHash) || forgottenHashes.has(contentHash)) {
570799
570848
  continue;
570800
570849
  }
@@ -570871,6 +570920,63 @@ var require_dist24 = __commonJS({
570871
570920
  const filteredPatches = survivingPatches.filter((_5, i5) => !revertIndicesToRemove.has(i5));
570872
570921
  return { patches: filteredPatches, revertedPatchIds: [...revertedPatchIdSet] };
570873
570922
  }
570923
+ /**
570924
+ * Materialize a commit into patch content exactly as detection does.
570925
+ *
570926
+ * Pipeline: `diff-tree --name-only` → filter infrastructure / sensitive /
570927
+ * `.fernignore`d files → scoped `git diff` when files were stripped,
570928
+ * otherwise full `git format-patch` → content hash.
570929
+ *
570930
+ * This is THE definition of a commit's re-emission identity: the content
570931
+ * hash this method returns is what `detectPatchesInRange` will compute if
570932
+ * the commit re-enters the scan range (e.g. after a regen PR is merged
570933
+ * with a merge commit). Callers that tombstone a dismissed patch must use
570934
+ * this hash — the stored `content_hash` drifts every time the patch is
570935
+ * carried forward, so it does not identify the commit on re-detection.
570936
+ *
570937
+ * Never throws; failures are reported through the `status` discriminant.
570938
+ */
570939
+ async materializeCommitPatch(sha) {
570940
+ let parents;
570941
+ let filesOutput;
570942
+ try {
570943
+ parents = await this.git.getCommitParents(sha);
570944
+ filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", sha]);
570945
+ } catch {
570946
+ return { status: "unreachable", sensitiveFiles: [], fernignoredFiles: [] };
570947
+ }
570948
+ const { files, sensitiveFiles, fernignoredFiles, infrastructureFiles } = filterInfrastructureAndSensitiveFiles(filesOutput, this.fernignorePatterns);
570949
+ if (files.length === 0) {
570950
+ return { status: "empty", sensitiveFiles, fernignoredFiles };
570951
+ }
570952
+ const wasFiltered = sensitiveFiles.length > 0 || fernignoredFiles.length > 0 || infrastructureFiles.length > 0;
570953
+ const parentSha = parents[0];
570954
+ let patchContent;
570955
+ if (wasFiltered && parentSha) {
570956
+ try {
570957
+ patchContent = await this.git.exec(["diff", parentSha, sha, "--", ...files]);
570958
+ } catch {
570959
+ return { status: "empty", sensitiveFiles, fernignoredFiles };
570960
+ }
570961
+ if (!patchContent.trim()) {
570962
+ return { status: "empty", sensitiveFiles, fernignoredFiles };
570963
+ }
570964
+ } else {
570965
+ try {
570966
+ patchContent = await this.git.formatPatch(sha);
570967
+ } catch {
570968
+ return { status: "unreachable", sensitiveFiles, fernignoredFiles };
570969
+ }
570970
+ }
570971
+ return {
570972
+ status: "ok",
570973
+ files,
570974
+ sensitiveFiles,
570975
+ fernignoredFiles,
570976
+ patchContent,
570977
+ contentHash: this.computeContentHash(patchContent)
570978
+ };
570979
+ }
570874
570980
  /**
570875
570981
  * Compute content hash for deduplication.
570876
570982
  *
@@ -570975,6 +571081,13 @@ var require_dist24 = __commonJS({
570975
571081
  async detectPatchesViaTreeDiff(lastGen, commitKnownMissing) {
570976
571082
  const diffBase = await this.resolveDiffBase(lastGen, commitKnownMissing);
570977
571083
  if (!diffBase) {
571084
+ this.warnings.push(
571085
+ `Neither the previous generation's commit (${lastGen.commit_sha.slice(0, 7)}) nor its recorded tree was reachable during this run. Falling back to a bounded commit scan, which cannot see customizations older than the clone window. Widen the clone window (e.g. \`git fetch --unshallow\`) and re-run replay to verify customizations were carried forward.`
571086
+ );
571087
+ this.degradedReasons.push({
571088
+ code: "generation-tree-unreachable",
571089
+ message: `The previous generation (${lastGen.commit_sha.slice(0, 7)}) was not reachable during this run \u2014 neither its commit nor its recorded tree. Customizations may not have been carried forward \u2014 review the result before merging, or widen the clone window and re-run replay.`
571090
+ });
570978
571091
  return this.detectPatchesViaCommitScan();
570979
571092
  }
570980
571093
  const lockForGuard = this.lockManager.read();
@@ -571087,10 +571200,7 @@ var require_dist24 = __commonJS({
571087
571200
  continue;
571088
571201
  }
571089
571202
  const filesOutput = await this.git.exec(["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha]);
571090
- const { files, sensitiveFiles, fernignoredFiles } = filterInfrastructureAndSensitiveFiles(
571091
- filesOutput,
571092
- this.fernignorePatterns
571093
- );
571203
+ const { files, sensitiveFiles, fernignoredFiles, infrastructureFiles } = filterInfrastructureAndSensitiveFiles(filesOutput, this.fernignorePatterns);
571094
571204
  if (sensitiveFiles.length > 0) {
571095
571205
  this.warnings.push(
571096
571206
  `Sensitive file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${sensitiveFiles.join(", ")}. These files will not be tracked by replay.`
@@ -571101,7 +571211,8 @@ var require_dist24 = __commonJS({
571101
571211
  `.fernignore-protected file(s) excluded from patch for commit ${commit.sha.slice(0, 7)}: ${fernignoredFiles.join(", ")}. These files are managed by .fernignore, not Replay.`
571102
571212
  );
571103
571213
  }
571104
- if ((sensitiveFiles.length > 0 || fernignoredFiles.length > 0) && files.length > 0) {
571214
+ const wasFiltered = sensitiveFiles.length > 0 || fernignoredFiles.length > 0 || infrastructureFiles.length > 0;
571215
+ if (wasFiltered && files.length > 0) {
571105
571216
  if (parents.length === 0) {
571106
571217
  continue;
571107
571218
  }
@@ -572351,14 +572462,21 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572351
572462
  return this.git.getTreeHash(commitSha);
572352
572463
  }
572353
572464
  };
572354
- var import_node_fs210 = require("fs");
572465
+ var import_node_fs36 = require("fs");
572355
572466
  var import_promises610 = require("fs/promises");
572356
- var import_node_path82 = require("path");
572467
+ var import_node_path92 = require("path");
572357
572468
  var import_minimatch52 = require_commonjs3();
572358
572469
  init_GitClient();
572470
+ var import_node_fs210 = require("fs");
572471
+ var import_node_path62 = require("path");
572472
+ function readFernignorePatterns(outputDir) {
572473
+ const fernignorePath = (0, import_node_path62.join)(outputDir, ".fernignore");
572474
+ if (!(0, import_node_fs210.existsSync)(fernignorePath)) return [];
572475
+ return (0, import_node_fs210.readFileSync)(fernignorePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
572476
+ }
572359
572477
  var import_promises410 = require("fs/promises");
572360
572478
  var import_node_os22 = require("os");
572361
- var import_node_path62 = require("path");
572479
+ var import_node_path72 = require("path");
572362
572480
  var import_node_child_process15 = require("child_process");
572363
572481
  init_HybridReconstruction();
572364
572482
  function normalizeHunkBody(hunk) {
@@ -572576,10 +572694,10 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572576
572694
  }
572577
572695
  async function unifiedDiff(beforeContent, afterContent, filePath) {
572578
572696
  if (beforeContent === afterContent) return "";
572579
- const tmp20 = await (0, import_promises410.mkdtemp)((0, import_node_path62.join)((0, import_node_os22.tmpdir)(), "fern-replay-pp-"));
572697
+ const tmp20 = await (0, import_promises410.mkdtemp)((0, import_node_path72.join)((0, import_node_os22.tmpdir)(), "fern-replay-pp-"));
572580
572698
  try {
572581
- const beforePath = (0, import_node_path62.join)(tmp20, "before");
572582
- const afterPath = (0, import_node_path62.join)(tmp20, "after");
572699
+ const beforePath = (0, import_node_path72.join)(tmp20, "before");
572700
+ const afterPath = (0, import_node_path72.join)(tmp20, "after");
572583
572701
  await (0, import_promises410.writeFile)(beforePath, beforeContent, "utf-8");
572584
572702
  await (0, import_promises410.writeFile)(afterPath, afterContent, "utf-8");
572585
572703
  const raw = await runGitDiffNoIndex(beforePath, afterPath);
@@ -572751,6 +572869,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572751
572869
  patchesToApply: [],
572752
572870
  newPatchIds: /* @__PURE__ */ new Set(),
572753
572871
  detectorWarnings: [],
572872
+ detectorDegraded: [],
572754
572873
  revertedCount: 0,
572755
572874
  genSha: genRecord.commit_sha,
572756
572875
  optionsSnapshot: options2
@@ -572808,6 +572927,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572808
572927
  patchesToApply: [],
572809
572928
  newPatchIds: /* @__PURE__ */ new Set(),
572810
572929
  detectorWarnings: [],
572930
+ detectorDegraded: [],
572811
572931
  revertedCount: 0,
572812
572932
  genSha: genRecord.commit_sha,
572813
572933
  optionsSnapshot: options2
@@ -572843,8 +572963,10 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572843
572963
  const previousGenerationSha = preLock.current_generation ?? null;
572844
572964
  let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
572845
572965
  const detectorWarnings = [...this.service.detector.warnings];
572966
+ const detectorDegraded = [...this.service.detector.degradedReasons];
572846
572967
  if (options2?.dryRun) {
572847
572968
  const dryRunWarnings = [...detectorWarnings, ...this.service.warnings];
572969
+ const dryRunDegraded = dedupeDegradedReasons([...detectorDegraded, ...this.service.degradedReasons]);
572848
572970
  const preparedReport = {
572849
572971
  flow: "no-patches",
572850
572972
  patchesDetected: newPatches.length,
@@ -572854,7 +572976,9 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572854
572976
  patchesReverted: revertedPatchIds.length,
572855
572977
  conflicts: [],
572856
572978
  wouldApply: newPatches,
572857
- warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0
572979
+ warnings: dryRunWarnings.length > 0 ? dryRunWarnings : void 0,
572980
+ degraded: dryRunDegraded.length > 0 ? true : void 0,
572981
+ degradedReasons: dryRunDegraded.length > 0 ? dryRunDegraded : void 0
572858
572982
  };
572859
572983
  const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s5) => s5.trim()).catch(() => "");
572860
572984
  return {
@@ -572908,6 +573032,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572908
573032
  patchesToApply: newPatches,
572909
573033
  newPatchIds: new Set(newPatches.map((p6) => p6.id)),
572910
573034
  detectorWarnings,
573035
+ detectorDegraded,
572911
573036
  revertedCount: revertedPatchIds.length,
572912
573037
  genSha: genRecord.commit_sha,
572913
573038
  optionsSnapshot: options2
@@ -572951,6 +573076,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572951
573076
  }
572952
573077
  }
572953
573078
  const warnings = [...detectorWarnings, ...this.service.warnings];
573079
+ const degradedReasons = [...prep._prepared.detectorDegraded, ...this.service.degradedReasons];
572954
573080
  return this.service.buildReport(
572955
573081
  "no-patches",
572956
573082
  newPatches,
@@ -572959,7 +573085,8 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572959
573085
  warnings,
572960
573086
  rebaseCounts,
572961
573087
  void 0,
572962
- prep._prepared.revertedCount
573088
+ prep._prepared.revertedCount,
573089
+ degradedReasons
572963
573090
  );
572964
573091
  }
572965
573092
  };
@@ -572974,6 +573101,10 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572974
573101
  const existingPatches2 = this.service.lockManager.getPatches();
572975
573102
  const { patches: newPatches2, revertedPatchIds: dryRunReverted } = await this.service.detector.detectNewPatches();
572976
573103
  const warnings = [...this.service.detector.warnings, ...this.service.warnings];
573104
+ const dryRunDegraded = dedupeDegradedReasons([
573105
+ ...this.service.detector.degradedReasons,
573106
+ ...this.service.degradedReasons
573107
+ ]);
572977
573108
  const allPatches2 = [...existingPatches2, ...newPatches2];
572978
573109
  const preparedReport = {
572979
573110
  flow: "normal-regeneration",
@@ -572984,7 +573115,9 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
572984
573115
  patchesReverted: dryRunReverted.length,
572985
573116
  conflicts: [],
572986
573117
  wouldApply: allPatches2,
572987
- warnings: warnings.length > 0 ? warnings : void 0
573118
+ warnings: warnings.length > 0 ? warnings : void 0,
573119
+ degraded: dryRunDegraded.length > 0 ? true : void 0,
573120
+ degradedReasons: dryRunDegraded.length > 0 ? dryRunDegraded : void 0
572988
573121
  };
572989
573122
  const headSha = await this.service.git.exec(["rev-parse", "HEAD"]).then((s5) => s5.trim()).catch(() => "");
572990
573123
  return {
@@ -573023,6 +573156,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
573023
573156
  existingPatches = this.service.lockManager.getPatches();
573024
573157
  let { patches: newPatches, revertedPatchIds } = await this.service.detector.detectNewPatches();
573025
573158
  const detectorWarnings = [...this.service.detector.warnings];
573159
+ const detectorDegraded = [...this.service.detector.degradedReasons];
573026
573160
  if (removedByPreRebase.length > 0) {
573027
573161
  const removedOriginalCommits = new Set(removedByPreRebase.map((p6) => p6.original_commit));
573028
573162
  const removedOriginalMessages = new Set(removedByPreRebase.map((p6) => p6.original_message));
@@ -573063,6 +573197,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
573063
573197
  newPatchIds: new Set(newPatches.map((p6) => p6.id)),
573064
573198
  preRebaseCounts,
573065
573199
  detectorWarnings,
573200
+ detectorDegraded,
573066
573201
  revertedCount: revertedPatchIds.length,
573067
573202
  genSha: genRecord.commit_sha,
573068
573203
  optionsSnapshot: options2
@@ -573111,6 +573246,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
573111
573246
  await this.service.committer.commitReplay(appliedCount, allPatches, void 0, { buckets });
573112
573247
  }
573113
573248
  const warnings = [...detectorWarnings, ...this.service.warnings];
573249
+ const degradedReasons = [...prep._prepared.detectorDegraded, ...this.service.degradedReasons];
573114
573250
  return this.service.buildReport(
573115
573251
  "normal-regeneration",
573116
573252
  allPatches,
@@ -573119,7 +573255,8 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
573119
573255
  warnings,
573120
573256
  rebaseCounts,
573121
573257
  prep._prepared.preRebaseCounts,
573122
- prep._prepared.revertedCount
573258
+ prep._prepared.revertedCount,
573259
+ degradedReasons
573123
573260
  );
573124
573261
  }
573125
573262
  };
@@ -573157,6 +573294,16 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
573157
573294
  * @internal Used by Flow implementations in `src/flows/`.
573158
573295
  */
573159
573296
  warnings = [];
573297
+ /**
573298
+ * Service-level structured degraded reasons, mirroring `warnings`.
573299
+ * Populated at apply time (e.g. `recordUnreachableBaseWarnings` when a
573300
+ * patch's base-generation tree is unreachable). Merged with
573301
+ * `detector.degradedReasons` into `ReplayReport.degradedReasons` by each
573302
+ * flow handler. Cleared at the top of `prepareReplay()`.
573303
+ *
573304
+ * @internal Used by Flow implementations in `src/flows/`.
573305
+ */
573306
+ degradedReasons = [];
573160
573307
  /**
573161
573308
  * @internal Test-only accessor.
573162
573309
  * Returns the ReplayApplicator instance used for the last run. Tests use this
@@ -573200,7 +573347,10 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
573200
573347
  async prepareReplay(options2) {
573201
573348
  this.warnings = [];
573202
573349
  this.detector.warnings.length = 0;
573350
+ this.degradedReasons = [];
573351
+ this.detector.degradedReasons.length = 0;
573203
573352
  this.cleanseLegacyFernignoreEntries();
573353
+ this.cleanseInfrastructureFileEntries();
573204
573354
  return this.selectFlow(options2).prepare(options2);
573205
573355
  }
573206
573356
  /**
@@ -573254,6 +573404,50 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
573254
573404
  this.lockManager.save();
573255
573405
  }
573256
573406
  }
573407
+ /**
573408
+ * Migration step: strips infrastructure file diffs (`.fernignore`, `.fern/*`)
573409
+ * from existing patches' `patch_content`. These diffs were bundled by older
573410
+ * detection logic that did not scope `git diff` when only infrastructure files
573411
+ * were stripped. Patches that end up with no surviving diff sections are
573412
+ * removed entirely.
573413
+ *
573414
+ * Idempotent: a second run is a no-op because the first run already removed
573415
+ * all infrastructure sections. Per-patch try/catch ensures isolation.
573416
+ */
573417
+ cleanseInfrastructureFileEntries() {
573418
+ if (!this.lockManager.exists()) return;
573419
+ this.lockManager.read();
573420
+ let lockfileMutated = false;
573421
+ const patches = this.lockManager.getPatches();
573422
+ for (const patch5 of patches) {
573423
+ try {
573424
+ const result = computeInfrastructureFileCleanse(
573425
+ patch5,
573426
+ (content5) => this.detector.computeContentHash(content5)
573427
+ );
573428
+ if (result.unchanged) continue;
573429
+ if (result.remove) {
573430
+ this.lockManager.removePatch(patch5.id);
573431
+ this.warnings.push(
573432
+ `Stripped infrastructure file diffs from patch ${patch5.id}: patch contained only infrastructure file changes (${result.strippedPaths.join(", ")}) \u2014 removed.`
573433
+ );
573434
+ } else {
573435
+ this.lockManager.updatePatch(patch5.id, {
573436
+ patch_content: result.patch_content,
573437
+ content_hash: result.content_hash
573438
+ });
573439
+ this.warnings.push(
573440
+ `Stripped infrastructure file diffs from patch ${patch5.id}: removed bundled diffs for ${result.strippedPaths.join(", ")}.`
573441
+ );
573442
+ }
573443
+ lockfileMutated = true;
573444
+ } catch {
573445
+ }
573446
+ }
573447
+ if (lockfileMutated) {
573448
+ this.lockManager.save();
573449
+ }
573450
+ }
573257
573451
  /**
573258
573452
  * Phase 2 of the two-phase replay flow. Applies patches, rebases them against
573259
573453
  * the generation, saves the lockfile, and commits `[fern-replay]` (or stages
@@ -573509,7 +573703,7 @@ Patches absorbed by generator (${buckets.absorbed.length}):`;
573509
573703
  for (const file4 of files) {
573510
573704
  if (isBinaryFile(file4)) continue;
573511
573705
  try {
573512
- const content5 = await (0, import_promises610.readFile)((0, import_node_path82.join)(this.outputDir, file4), "utf-8");
573706
+ const content5 = await (0, import_promises610.readFile)((0, import_node_path92.join)(this.outputDir, file4), "utf-8");
573513
573707
  snapshot[file4] = content5;
573514
573708
  } catch {
573515
573709
  }
@@ -574026,6 +574220,12 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574026
574220
  );
574027
574221
  }
574028
574222
  }
574223
+ if (warned2.size > 0) {
574224
+ this.degradedReasons.push({
574225
+ code: "patch-base-unreachable",
574226
+ message: `${warned2.size} customization file(s) could not be checked against the generation they were made on because that generation's tree was not reachable during this run. Customizations may not have been carried forward \u2014 review the result before merging, or widen the clone window and re-run replay.`
574227
+ });
574228
+ }
574029
574229
  }
574030
574230
  /**
574031
574231
  * After applyPatches(), strip conflict markers from conflicting files
@@ -574037,11 +574237,11 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574037
574237
  if (result.status !== "conflict" || !result.fileResults) continue;
574038
574238
  for (const fileResult of result.fileResults) {
574039
574239
  if (fileResult.status !== "conflict") continue;
574040
- const filePath = (0, import_node_path82.join)(this.outputDir, fileResult.file);
574240
+ const filePath = (0, import_node_path92.join)(this.outputDir, fileResult.file);
574041
574241
  try {
574042
- const content5 = (0, import_node_fs210.readFileSync)(filePath, "utf-8");
574242
+ const content5 = (0, import_node_fs36.readFileSync)(filePath, "utf-8");
574043
574243
  const stripped = stripConflictMarkers(content5);
574044
- (0, import_node_fs210.writeFileSync)(filePath, stripped);
574244
+ (0, import_node_fs36.writeFileSync)(filePath, stripped);
574045
574245
  } catch {
574046
574246
  }
574047
574247
  }
@@ -574069,12 +574269,11 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574069
574269
  }
574070
574270
  /** @internal Used by Flow implementations in `src/flows/`. */
574071
574271
  readFernignorePatterns() {
574072
- const fernignorePath = (0, import_node_path82.join)(this.outputDir, ".fernignore");
574073
- if (!(0, import_node_fs210.existsSync)(fernignorePath)) return [];
574074
- return (0, import_node_fs210.readFileSync)(fernignorePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
574272
+ return readFernignorePatterns(this.outputDir);
574075
574273
  }
574076
574274
  /** @internal Used by Flow implementations in `src/flows/`. */
574077
- buildReport(flow4, patches, results, options2, warnings, rebaseCounts, preRebaseCounts, patchesReverted) {
574275
+ buildReport(flow4, patches, results, options2, warnings, rebaseCounts, preRebaseCounts, patchesReverted, degradedReasons) {
574276
+ const degraded = dedupeDegradedReasons(degradedReasons ?? []);
574078
574277
  const conflictResults = results.filter((r4) => r4.status === "conflict");
574079
574278
  const conflictDetails = conflictResults.map((r4) => {
574080
574279
  const conflictFiles = r4.fileResults?.filter((f3) => f3.status === "conflict") ?? [];
@@ -574114,10 +574313,23 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574114
574313
  conflictDetails: r4.fileResults?.filter((f3) => f3.status === "conflict") ?? []
574115
574314
  })) : void 0,
574116
574315
  wouldApply: options2?.dryRun ? patches : void 0,
574117
- warnings: warnings && warnings.length > 0 ? warnings : void 0
574316
+ warnings: warnings && warnings.length > 0 ? warnings : void 0,
574317
+ degraded: degraded.length > 0 ? true : void 0,
574318
+ degradedReasons: degraded.length > 0 ? degraded : void 0
574118
574319
  };
574119
574320
  }
574120
574321
  };
574322
+ function dedupeDegradedReasons(reasons) {
574323
+ const seen = /* @__PURE__ */ new Set();
574324
+ const out = [];
574325
+ for (const reason of reasons) {
574326
+ const key2 = `${reason.code}\0${reason.message}`;
574327
+ if (seen.has(key2)) continue;
574328
+ seen.add(key2);
574329
+ out.push(reason);
574330
+ }
574331
+ return out;
574332
+ }
574121
574333
  function computeCommitBuckets(results, absorbedPatchIds, patchesPassedToCommit) {
574122
574334
  const resultByPatchId = /* @__PURE__ */ new Map();
574123
574335
  for (const r4 of results) resultByPatchId.set(r4.patch.id, r4);
@@ -574166,7 +574378,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574166
574378
  }
574167
574379
  }
574168
574380
  }
574169
- const { stripped: patchContentStripped, content: newPatchContent, strippedSectionPaths } = stripFernignoreDiffSections(patch5.patch_content, matchesFernignore);
574381
+ const { stripped: patchContentStripped, content: newPatchContent, strippedSectionPaths } = stripDiffSectionsByPredicate(patch5.patch_content, matchesFernignore);
574170
574382
  const unchanged = strippedFromFiles.length === 0 && !snapshotChanged && !patchContentStripped;
574171
574383
  if (unchanged) {
574172
574384
  return {
@@ -574201,7 +574413,38 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574201
574413
  strippedPaths
574202
574414
  };
574203
574415
  }
574204
- function stripFernignoreDiffSections(patchContent, matchesFernignore) {
574416
+ function isInfrastructureFile(filePath) {
574417
+ return filePath === ".fernignore" || filePath.startsWith(".fern/");
574418
+ }
574419
+ function computeInfrastructureFileCleanse(patch5, computeContentHash2) {
574420
+ const { stripped, content: newPatchContent, strippedSectionPaths } = stripDiffSectionsByPredicate(patch5.patch_content, isInfrastructureFile);
574421
+ if (!stripped) {
574422
+ return {
574423
+ unchanged: true,
574424
+ remove: false,
574425
+ patch_content: patch5.patch_content,
574426
+ content_hash: patch5.content_hash,
574427
+ strippedPaths: []
574428
+ };
574429
+ }
574430
+ if (newPatchContent.trim() === "" || patch5.files.length === 0) {
574431
+ return {
574432
+ unchanged: false,
574433
+ remove: true,
574434
+ patch_content: "",
574435
+ content_hash: "",
574436
+ strippedPaths: strippedSectionPaths
574437
+ };
574438
+ }
574439
+ return {
574440
+ unchanged: false,
574441
+ remove: false,
574442
+ patch_content: newPatchContent,
574443
+ content_hash: computeContentHash2(newPatchContent),
574444
+ strippedPaths: strippedSectionPaths
574445
+ };
574446
+ }
574447
+ function stripDiffSectionsByPredicate(patchContent, shouldStrip) {
574205
574448
  const lines = patchContent.split("\n");
574206
574449
  const sectionStarts = [];
574207
574450
  for (let i5 = 0; i5 < lines.length; i5++) {
@@ -574223,7 +574466,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574223
574466
  const section = sectionStarts[i5];
574224
574467
  const next2 = sectionStarts[i5 + 1];
574225
574468
  const endIdx = next2 ? next2.idx : lines.length;
574226
- if (section.path !== null && matchesFernignore(section.path)) {
574469
+ if (section.path !== null && shouldStrip(section.path)) {
574227
574470
  stripped = true;
574228
574471
  strippedSectionPaths.push(section.path);
574229
574472
  continue;
@@ -574233,8 +574476,8 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574233
574476
  return { stripped, content: newLines.join("\n"), strippedSectionPaths };
574234
574477
  }
574235
574478
  var import_node_crypto22 = require("crypto");
574236
- var import_node_fs36 = require("fs");
574237
- var import_node_path92 = require("path");
574479
+ var import_node_fs42 = require("fs");
574480
+ var import_node_path102 = require("path");
574238
574481
  var import_minimatch62 = require_commonjs3();
574239
574482
  var import_yaml22 = require_dist11();
574240
574483
  var FernignoreMigrator = class {
@@ -574247,14 +574490,14 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574247
574490
  this.outputDir = outputDir;
574248
574491
  }
574249
574492
  fernignoreExists() {
574250
- return (0, import_node_fs36.existsSync)((0, import_node_path92.join)(this.outputDir, ".fernignore"));
574493
+ return (0, import_node_fs42.existsSync)((0, import_node_path102.join)(this.outputDir, ".fernignore"));
574251
574494
  }
574252
574495
  readFernignorePatterns() {
574253
- const fernignorePath = (0, import_node_path92.join)(this.outputDir, ".fernignore");
574254
- if (!(0, import_node_fs36.existsSync)(fernignorePath)) {
574496
+ const fernignorePath = (0, import_node_path102.join)(this.outputDir, ".fernignore");
574497
+ if (!(0, import_node_fs42.existsSync)(fernignorePath)) {
574255
574498
  return [];
574256
574499
  }
574257
- const content5 = (0, import_node_fs36.readFileSync)(fernignorePath, "utf-8");
574500
+ const content5 = (0, import_node_fs42.readFileSync)(fernignorePath, "utf-8");
574258
574501
  return content5.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
574259
574502
  }
574260
574503
  /** Analyze .fernignore patterns vs git history. Creates synthetic patches for files differing from pristine generation. */
@@ -574359,16 +574602,16 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574359
574602
  return results;
574360
574603
  }
574361
574604
  readFileContent(filePath) {
574362
- const fullPath = (0, import_node_path92.join)(this.outputDir, filePath);
574363
- if (!(0, import_node_fs36.existsSync)(fullPath)) {
574605
+ const fullPath = (0, import_node_path102.join)(this.outputDir, filePath);
574606
+ if (!(0, import_node_fs42.existsSync)(fullPath)) {
574364
574607
  return null;
574365
574608
  }
574366
574609
  try {
574367
- const stat7 = (0, import_node_fs36.statSync)(fullPath);
574610
+ const stat7 = (0, import_node_fs42.statSync)(fullPath);
574368
574611
  if (stat7.isDirectory()) {
574369
574612
  return null;
574370
574613
  }
574371
- return (0, import_node_fs36.readFileSync)(fullPath, "utf-8");
574614
+ return (0, import_node_fs42.readFileSync)(fullPath, "utf-8");
574372
574615
  } catch {
574373
574616
  return null;
574374
574617
  }
@@ -574424,25 +574667,25 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574424
574667
  };
574425
574668
  }
574426
574669
  movePatternsToReplayYml(patterns) {
574427
- const replayYmlPath = (0, import_node_path92.join)(this.outputDir, ".fern", "replay.yml");
574670
+ const replayYmlPath = (0, import_node_path102.join)(this.outputDir, ".fern", "replay.yml");
574428
574671
  let config5 = {};
574429
- if ((0, import_node_fs36.existsSync)(replayYmlPath)) {
574430
- const content5 = (0, import_node_fs36.readFileSync)(replayYmlPath, "utf-8");
574672
+ if ((0, import_node_fs42.existsSync)(replayYmlPath)) {
574673
+ const content5 = (0, import_node_fs42.readFileSync)(replayYmlPath, "utf-8");
574431
574674
  config5 = (0, import_yaml22.parse)(content5) ?? {};
574432
574675
  }
574433
574676
  const existing = config5.exclude ?? [];
574434
574677
  const merged = [.../* @__PURE__ */ new Set([...existing, ...patterns])];
574435
574678
  config5.exclude = merged;
574436
- const dir = (0, import_node_path92.dirname)(replayYmlPath);
574437
- if (!(0, import_node_fs36.existsSync)(dir)) {
574438
- (0, import_node_fs36.mkdirSync)(dir, { recursive: true });
574679
+ const dir = (0, import_node_path102.dirname)(replayYmlPath);
574680
+ if (!(0, import_node_fs42.existsSync)(dir)) {
574681
+ (0, import_node_fs42.mkdirSync)(dir, { recursive: true });
574439
574682
  }
574440
- (0, import_node_fs36.writeFileSync)(replayYmlPath, (0, import_yaml22.stringify)(config5, { lineWidth: 0 }), "utf-8");
574683
+ (0, import_node_fs42.writeFileSync)(replayYmlPath, (0, import_yaml22.stringify)(config5, { lineWidth: 0 }), "utf-8");
574441
574684
  }
574442
574685
  };
574443
574686
  var import_node_crypto32 = require("crypto");
574444
- var import_node_fs42 = require("fs");
574445
- var import_node_path102 = require("path");
574687
+ var import_node_fs52 = require("fs");
574688
+ var import_node_path112 = require("path");
574446
574689
  init_GitClient();
574447
574690
  async function bootstrap(outputDir, options2) {
574448
574691
  const git = new GitClient(outputDir);
@@ -574609,10 +574852,10 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574609
574852
  }
574610
574853
  var REPLAY_FERNIGNORE_ENTRIES = [".fern/replay.lock", ".fern/replay.yml", ".gitattributes"];
574611
574854
  function ensureFernignoreEntries(outputDir) {
574612
- const fernignorePath = (0, import_node_path102.join)(outputDir, ".fernignore");
574855
+ const fernignorePath = (0, import_node_path112.join)(outputDir, ".fernignore");
574613
574856
  let content5 = "";
574614
- if ((0, import_node_fs42.existsSync)(fernignorePath)) {
574615
- content5 = (0, import_node_fs42.readFileSync)(fernignorePath, "utf-8");
574857
+ if ((0, import_node_fs52.existsSync)(fernignorePath)) {
574858
+ content5 = (0, import_node_fs52.readFileSync)(fernignorePath, "utf-8");
574616
574859
  }
574617
574860
  const lines = content5.split("\n");
574618
574861
  const toAdd = [];
@@ -574628,15 +574871,15 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574628
574871
  content5 += "\n";
574629
574872
  }
574630
574873
  content5 += toAdd.join("\n") + "\n";
574631
- (0, import_node_fs42.writeFileSync)(fernignorePath, content5, "utf-8");
574874
+ (0, import_node_fs52.writeFileSync)(fernignorePath, content5, "utf-8");
574632
574875
  return true;
574633
574876
  }
574634
574877
  var GITATTRIBUTES_ENTRIES = [".fern/replay.lock linguist-generated=true"];
574635
574878
  function ensureGitattributesEntries(outputDir) {
574636
- const gitattributesPath = (0, import_node_path102.join)(outputDir, ".gitattributes");
574879
+ const gitattributesPath = (0, import_node_path112.join)(outputDir, ".gitattributes");
574637
574880
  let content5 = "";
574638
- if ((0, import_node_fs42.existsSync)(gitattributesPath)) {
574639
- content5 = (0, import_node_fs42.readFileSync)(gitattributesPath, "utf-8");
574881
+ if ((0, import_node_fs52.existsSync)(gitattributesPath)) {
574882
+ content5 = (0, import_node_fs52.readFileSync)(gitattributesPath, "utf-8");
574640
574883
  }
574641
574884
  const lines = content5.split("\n");
574642
574885
  const toAdd = [];
@@ -574652,7 +574895,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574652
574895
  content5 += "\n";
574653
574896
  }
574654
574897
  content5 += toAdd.join("\n") + "\n";
574655
- (0, import_node_fs42.writeFileSync)(gitattributesPath, content5, "utf-8");
574898
+ (0, import_node_fs52.writeFileSync)(gitattributesPath, content5, "utf-8");
574656
574899
  }
574657
574900
  function computeContentHash(patchContent) {
574658
574901
  const lines = patchContent.split("\n");
@@ -574662,6 +574905,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574662
574905
  return `sha256:${(0, import_node_crypto32.createHash)("sha256").update(normalized).digest("hex")}`;
574663
574906
  }
574664
574907
  var import_minimatch72 = require_commonjs3();
574908
+ init_GitClient();
574665
574909
  function parseDiffStat(patchContent) {
574666
574910
  let additions = 0;
574667
574911
  let deletions = 0;
@@ -574720,7 +574964,28 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574720
574964
  totalPatches: 0,
574721
574965
  warnings: []
574722
574966
  };
574723
- function forget(outputDir, options2) {
574967
+ async function dismissPatches(outputDir, lockManager, patches) {
574968
+ const git = new GitClient(outputDir);
574969
+ const detector = new ReplayDetector(git, lockManager, outputDir, readFernignorePatterns(outputDir));
574970
+ for (const patch5 of patches) {
574971
+ const hashes = [patch5.content_hash];
574972
+ if (patch5.original_commit) {
574973
+ const materialized = await detector.materializeCommitPatch(patch5.original_commit);
574974
+ if (materialized.status === "ok" && !hashes.includes(materialized.contentHash)) {
574975
+ hashes.push(materialized.contentHash);
574976
+ }
574977
+ }
574978
+ lockManager.addDismissal({
574979
+ patch_id: patch5.id,
574980
+ original_commit: patch5.original_commit,
574981
+ original_message: patch5.original_message,
574982
+ content_hashes: hashes,
574983
+ dismissed_at: (/* @__PURE__ */ new Date()).toISOString(),
574984
+ via: "forget"
574985
+ });
574986
+ }
574987
+ }
574988
+ async function forget(outputDir, options2) {
574724
574989
  const lockManager = new LockfileManager(outputDir);
574725
574990
  if (!lockManager.exists()) {
574726
574991
  return { ...EMPTY_RESULT };
@@ -574731,9 +574996,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574731
574996
  const removed = lock.patches.map(toMatchedPatch);
574732
574997
  const warnings = buildWarnings(lock.patches);
574733
574998
  if (!options2.dryRun) {
574734
- for (const patch5 of lock.patches) {
574735
- lockManager.addForgottenHash(patch5.content_hash);
574736
- }
574999
+ await dismissPatches(outputDir, lockManager, lock.patches);
574737
575000
  lockManager.clearPatches();
574738
575001
  lockManager.save();
574739
575002
  }
@@ -574762,8 +575025,8 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574762
575025
  }
574763
575026
  const warnings = buildWarnings(patchesToRemove);
574764
575027
  if (!options2.dryRun) {
575028
+ await dismissPatches(outputDir, lockManager, patchesToRemove);
574765
575029
  for (const patch5 of patchesToRemove) {
574766
- lockManager.addForgottenHash(patch5.content_hash);
574767
575030
  lockManager.removePatch(patch5.id);
574768
575031
  }
574769
575032
  lockManager.save();
@@ -574802,7 +575065,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574802
575065
  matched: lock.patches.map(toMatchedPatch)
574803
575066
  };
574804
575067
  }
574805
- var import_node_fs52 = require("fs");
575068
+ var import_node_fs62 = require("fs");
574806
575069
  function reset2(outputDir, options2) {
574807
575070
  const lockManager = new LockfileManager(outputDir);
574808
575071
  if (!lockManager.exists()) {
@@ -574816,7 +575079,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574816
575079
  const lock = lockManager.read();
574817
575080
  const patchCount = lock.patches.length;
574818
575081
  if (!options2?.dryRun) {
574819
- (0, import_node_fs52.unlinkSync)(lockManager.lockfilePath);
575082
+ (0, import_node_fs62.unlinkSync)(lockManager.lockfilePath);
574820
575083
  }
574821
575084
  return {
574822
575085
  success: true,
@@ -574826,7 +575089,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574826
575089
  };
574827
575090
  }
574828
575091
  var import_promises710 = require("fs/promises");
574829
- var import_node_path112 = require("path");
575092
+ var import_node_path122 = require("path");
574830
575093
  init_GitClient();
574831
575094
  async function resolve15(outputDir, options2) {
574832
575095
  const lockManager = new LockfileManager(outputDir);
@@ -574867,15 +575130,16 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574867
575130
  const patchesToCommit = [...resolvingPatches, ...unresolvedPatches];
574868
575131
  if (patchesToCommit.length > 0) {
574869
575132
  const currentGen = lock.current_generation;
574870
- const detector = new ReplayDetector(git, lockManager, outputDir);
575133
+ const detector = new ReplayDetector(git, lockManager, outputDir, readFernignorePatterns(outputDir));
574871
575134
  const warnings = [];
575135
+ const patchesDismissed = [];
574872
575136
  let patchesResolved = 0;
574873
575137
  const pass1 = [];
574874
575138
  for (const patch5 of patchesToCommit) {
574875
575139
  const result = await computePerPatchDiffWithFallback(
574876
575140
  patch5,
574877
575141
  (f3) => git.showFile(currentGen, f3),
574878
- (f3) => (0, import_promises710.readFile)((0, import_node_path112.join)(outputDir, f3), "utf-8").catch(() => null),
575142
+ (f3) => (0, import_promises710.readFile)((0, import_node_path122.join)(outputDir, f3), "utf-8").catch(() => null),
574879
575143
  (files) => git.exec(["diff", currentGen, "--", ...files]).catch(() => null)
574880
575144
  );
574881
575145
  if (result === null) {
@@ -574909,7 +575173,27 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574909
575173
  const r4 = pass1[i5];
574910
575174
  if (r4.kind === "skip") continue;
574911
575175
  if (r4.kind === "revert") {
575176
+ const hashes = [patch5.content_hash];
575177
+ if (patch5.original_commit) {
575178
+ const materialized = await detector.materializeCommitPatch(patch5.original_commit);
575179
+ if (materialized.status === "ok" && !hashes.includes(materialized.contentHash)) {
575180
+ hashes.push(materialized.contentHash);
575181
+ } else if (materialized.status === "unreachable") {
575182
+ warnings.push(
575183
+ `Patch ${patch5.id}: original commit ${patch5.original_commit.slice(0, 7)} is unreachable; dismissed using the stored content hash only. If this commit reappears in history, the customization may be re-detected once.`
575184
+ );
575185
+ }
575186
+ }
575187
+ lockManager.addDismissal({
575188
+ patch_id: patch5.id,
575189
+ original_commit: patch5.original_commit,
575190
+ original_message: patch5.original_message,
575191
+ content_hashes: hashes,
575192
+ dismissed_at: (/* @__PURE__ */ new Date()).toISOString(),
575193
+ via: "resolve"
575194
+ });
574912
575195
  lockManager.removePatch(patch5.id);
575196
+ patchesDismissed.push({ id: patch5.id, message: patch5.original_message });
574913
575197
  continue;
574914
575198
  }
574915
575199
  if (lastIndexByHash.get(r4.hash) !== i5) {
@@ -574931,7 +575215,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574931
575215
  const theirsSnapshot = {};
574932
575216
  for (const file4 of filesForSnapshot) {
574933
575217
  if (isBinaryFile(file4)) continue;
574934
- const content5 = await (0, import_promises710.readFile)((0, import_node_path112.join)(outputDir, file4), "utf-8").catch(() => null);
575218
+ const content5 = await (0, import_promises710.readFile)((0, import_node_path122.join)(outputDir, file4), "utf-8").catch(() => null);
574935
575219
  if (content5 != null) {
574936
575220
  theirsSnapshot[file4] = content5;
574937
575221
  }
@@ -574958,6 +575242,7 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
574958
575242
  commitSha: commitSha2,
574959
575243
  phase: "committed",
574960
575244
  patchesResolved,
575245
+ patchesDismissed: patchesDismissed.length > 0 ? patchesDismissed : void 0,
574961
575246
  warnings: warnings.length > 0 ? warnings : void 0
574962
575247
  };
574963
575248
  }
@@ -575003,7 +575288,9 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
575003
575288
  lastGeneration: void 0,
575004
575289
  patches: [],
575005
575290
  unresolvedCount: 0,
575006
- excludePatterns: []
575291
+ excludePatterns: [],
575292
+ dismissed: [],
575293
+ legacyDismissedCount: 0
575007
575294
  };
575008
575295
  }
575009
575296
  const lock = lockManager.read();
@@ -575032,13 +575319,24 @@ If you want to keep these lines, restore them in a follow-up commit and re-run r
575032
575319
  }
575033
575320
  const config5 = lockManager.getCustomizationsConfig();
575034
575321
  const excludePatterns = config5.exclude ?? [];
575322
+ const dismissals = lock.dismissals ?? [];
575323
+ const dismissed = dismissals.map((d2) => ({
575324
+ message: d2.original_message,
575325
+ sha: d2.original_commit.slice(0, 7),
575326
+ via: d2.via,
575327
+ dismissedAt: d2.dismissed_at
575328
+ }));
575329
+ const coveredHashes = new Set(dismissals.flatMap((d2) => d2.content_hashes));
575330
+ const legacyDismissedCount = (lock.forgotten_hashes ?? []).filter((h4) => !coveredHashes.has(h4)).length;
575035
575331
  return {
575036
575332
  initialized: true,
575037
575333
  generationCount: lock.generations.length,
575038
575334
  lastGeneration,
575039
575335
  patches,
575040
575336
  unresolvedCount,
575041
- excludePatterns
575337
+ excludePatterns,
575338
+ dismissed,
575339
+ legacyDismissedCount
575042
575340
  };
575043
575341
  }
575044
575342
  }
@@ -608315,7 +608613,7 @@ var require_replay_forget = __commonJS({
608315
608613
  Object.defineProperty(exports2, "__esModule", { value: true });
608316
608614
  exports2.replayForget = replayForget3;
608317
608615
  var replay_1 = require_dist24();
608318
- function replayForget3(params2) {
608616
+ async function replayForget3(params2) {
608319
608617
  return (0, replay_1.forget)(params2.outputDir, params2.options);
608320
608618
  }
608321
608619
  }
@@ -686384,7 +686682,7 @@ var AccessTokenPosthogManager = class {
686384
686682
  properties: {
686385
686683
  ...event,
686386
686684
  ...event.properties,
686387
- version: "5.51.2",
686685
+ version: "5.52.0",
686388
686686
  usingAccessToken: true,
686389
686687
  ...getRunIdProperties()
686390
686688
  }
@@ -686450,7 +686748,7 @@ var UserPosthogManager = class {
686450
686748
  distinctId: this.userId ?? await this.getPersistedDistinctId(),
686451
686749
  event: "CLI",
686452
686750
  properties: {
686453
- version: "5.51.2",
686751
+ version: "5.52.0",
686454
686752
  ...event,
686455
686753
  ...event.properties,
686456
686754
  usingAccessToken: false,
@@ -867512,7 +867810,7 @@ var LOCAL_STORAGE_FOLDER4 = ".fern-dev";
867512
867810
  var LOGS_FOLDER_NAME = "logs";
867513
867811
  var MAX_LOGS_DIR_SIZE_BYTES = 100 * 1024 * 1024;
867514
867812
  function getCliSource() {
867515
- const version7 = "5.51.2";
867813
+ const version7 = "5.52.0";
867516
867814
  return `cli@${version7}`;
867517
867815
  }
867518
867816
  var DebugLogger = class {
@@ -895241,6 +895539,13 @@ og:description, headline, excerpt).`
895241
895539
  "static"
895242
895540
  ]
895243
895541
  },
895542
+ "docs.ChangelogLayout": {
895543
+ type: "string",
895544
+ enum: [
895545
+ "timeline",
895546
+ "classic"
895547
+ ]
895548
+ },
895244
895549
  "docs.LayoutConfig": {
895245
895550
  type: "object",
895246
895551
  properties: {
@@ -895386,6 +895691,17 @@ og:description, headline, excerpt).`
895386
895691
  }
895387
895692
  ],
895388
895693
  description: "If `mobile-toc` is set to true, a sticky collapsible table of contents bar will be shown on mobile viewports for guide and overview layout pages."
895694
+ },
895695
+ "changelog-layout": {
895696
+ oneOf: [
895697
+ {
895698
+ $ref: "#/definitions/docs.ChangelogLayout"
895699
+ },
895700
+ {
895701
+ type: "null"
895702
+ }
895703
+ ],
895704
+ description: "Sets which layout to use for changelog pages.\n\n@default: `timeline`\n\n- `timeline` renders the searchable timeline-of-cards layout.\n- `classic` renders the legacy stacked-entries layout with a per-page table of contents."
895389
895705
  }
895390
895706
  },
895391
895707
  additionalProperties: false
@@ -899700,7 +900016,7 @@ var LegacyDocsPublisher = class {
899700
900016
  previewId,
899701
900017
  disableTemplates: void 0,
899702
900018
  skipUpload,
899703
- cliVersion: "5.51.2",
900019
+ cliVersion: "5.52.0",
899704
900020
  loginCommand: "fern auth login"
899705
900021
  });
899706
900022
  if (taskContext.getResult() === TaskResult.Failure) {
@@ -902297,7 +902613,7 @@ var ForgetCommand = class {
902297
902613
  const dryRun = args["dry-run"];
902298
902614
  try {
902299
902615
  if (args.all) {
902300
- const result2 = (0, import_generator_cli.replayForget)({ outputDir, options: { all: true, dryRun } });
902616
+ const result2 = await (0, import_generator_cli.replayForget)({ outputDir, options: { all: true, dryRun } });
902301
902617
  if (!result2.initialized) {
902302
902618
  context3.stderr.info("Replay is not initialized. Nothing to forget.");
902303
902619
  return;
@@ -902320,7 +902636,7 @@ var ForgetCommand = class {
902320
902636
  return;
902321
902637
  }
902322
902638
  if (positionalArgs.length > 0 && positionalArgs.every((a3) => a3.startsWith("patch-"))) {
902323
- const result2 = (0, import_generator_cli.replayForget)({ outputDir, options: { patchIds: positionalArgs, dryRun } });
902639
+ const result2 = await (0, import_generator_cli.replayForget)({ outputDir, options: { patchIds: positionalArgs, dryRun } });
902324
902640
  if (!result2.initialized) {
902325
902641
  context3.stderr.info("Replay is not initialized. Nothing to forget.");
902326
902642
  return;
@@ -902346,7 +902662,7 @@ var ForgetCommand = class {
902346
902662
  });
902347
902663
  }
902348
902664
  const pattern = positionalArgs.length === 1 ? positionalArgs[0] : void 0;
902349
- const result = (0, import_generator_cli.replayForget)({ outputDir, options: { pattern } });
902665
+ const result = await (0, import_generator_cli.replayForget)({ outputDir, options: { pattern } });
902350
902666
  if (!result.initialized) {
902351
902667
  context3.stderr.info("Replay is not initialized. Nothing to forget.");
902352
902668
  return;
@@ -902389,7 +902705,7 @@ Remove ${matched.length} patch(es)? [y/N] `, resolvePrompt);
902389
902705
  }
902390
902706
  }
902391
902707
  const patchIds = matched.map((p6) => p6.id);
902392
- const removeResult = (0, import_generator_cli.replayForget)({ outputDir, options: { patchIds, dryRun: false } });
902708
+ const removeResult = await (0, import_generator_cli.replayForget)({ outputDir, options: { patchIds, dryRun: false } });
902393
902709
  context3.stderr.info(`Removed ${removeResult.removed.length} patch(es). ${removeResult.remaining} remaining.`);
902394
902710
  for (const warning of removeResult.warnings) {
902395
902711
  context3.stderr.warn(warning);
@@ -924464,9 +924780,9 @@ var DynamicTypeMapper2 = class {
924464
924780
  return this.convertNamed({ named });
924465
924781
  }
924466
924782
  case "optional":
924467
- return go_exports.Type.optional(this.convert({ typeReference: args.typeReference.value }));
924783
+ return this.convertOptionalOrNullable(args.typeReference.value);
924468
924784
  case "nullable":
924469
- return go_exports.Type.optional(this.convert({ typeReference: args.typeReference.value }));
924785
+ return this.convertOptionalOrNullable(args.typeReference.value);
924470
924786
  case "primitive":
924471
924787
  return this.convertPrimitive({ primitive: args.typeReference.value });
924472
924788
  case "set":
@@ -924477,6 +924793,56 @@ var DynamicTypeMapper2 = class {
924477
924793
  assertNever(args.typeReference);
924478
924794
  }
924479
924795
  }
924796
+ convertOptionalOrNullable(innerReference) {
924797
+ if (this.isPointerAliasReference(innerReference)) {
924798
+ return this.convert({ typeReference: innerReference });
924799
+ }
924800
+ return go_exports.Type.optional(this.convert({ typeReference: innerReference }));
924801
+ }
924802
+ /**
924803
+ * Returns true if the type reference resolves to a named alias that already
924804
+ * generates as a pointer type in Go. Also handles the collapse case where
924805
+ * optional(nullable(named(alias))) should not produce a double pointer.
924806
+ */
924807
+ isPointerAliasReference(reference4) {
924808
+ if (reference4.type === "named") {
924809
+ return this.isAliasToPointerType(reference4.value);
924810
+ }
924811
+ if (reference4.type === "optional" || reference4.type === "nullable") {
924812
+ const inner2 = reference4.value;
924813
+ if (inner2.type === "named") {
924814
+ return this.isAliasToPointerType(inner2.value);
924815
+ }
924816
+ }
924817
+ return false;
924818
+ }
924819
+ /**
924820
+ * Checks if a named type is an alias that already generates as a pointer in Go
924821
+ * (e.g. a nullable primitive like *time.Time). Traverses alias chains.
924822
+ */
924823
+ isAliasToPointerType(typeId) {
924824
+ const seen = /* @__PURE__ */ new Set();
924825
+ let currentTypeId = typeId;
924826
+ while (true) {
924827
+ if (seen.has(currentTypeId)) {
924828
+ return false;
924829
+ }
924830
+ seen.add(currentTypeId);
924831
+ const namedType = this.context.resolveNamedType({ typeId: currentTypeId });
924832
+ if (namedType == null || namedType.type !== "alias") {
924833
+ return false;
924834
+ }
924835
+ const aliasOf = namedType.typeReference;
924836
+ if (aliasOf.type === "optional" || aliasOf.type === "nullable") {
924837
+ return true;
924838
+ }
924839
+ if (aliasOf.type === "named") {
924840
+ currentTypeId = aliasOf.value;
924841
+ continue;
924842
+ }
924843
+ return false;
924844
+ }
924845
+ }
924480
924846
  convertLiteral({ literal: literal3 }) {
924481
924847
  switch (literal3.type) {
924482
924848
  case "boolean":
@@ -965841,7 +966207,7 @@ function getAutomationContextFromEnv() {
965841
966207
  config_branch: process.env.FERN_CONFIG_BRANCH,
965842
966208
  config_pr_number: process.env.FERN_CONFIG_PR_NUMBER,
965843
966209
  trigger: process.env.GITHUB_EVENT_NAME,
965844
- cli_version: "5.51.2"
966210
+ cli_version: "5.52.0"
965845
966211
  };
965846
966212
  }
965847
966213
  function isAutomationMode() {
@@ -966673,7 +967039,7 @@ var CliContext = class _CliContext {
966673
967039
  if (false) {
966674
967040
  this.logger.error("CLI_VERSION is not defined");
966675
967041
  }
966676
- return "5.51.2";
967042
+ return "5.52.0";
966677
967043
  }
966678
967044
  getCliName() {
966679
967045
  if (false) {
@@ -984843,7 +985209,7 @@ function addReplayForgetCommand(cli, cliContext) {
984843
985209
  const dryRun = argv.dryRun;
984844
985210
  try {
984845
985211
  if (argv.all) {
984846
- const result2 = (0, import_generator_cli5.replayForget)({ outputDir, options: { all: true, dryRun } });
985212
+ const result2 = await (0, import_generator_cli5.replayForget)({ outputDir, options: { all: true, dryRun } });
984847
985213
  if (!result2.initialized) {
984848
985214
  cliContext.logger.info("Replay is not initialized. Nothing to forget.");
984849
985215
  return;
@@ -984866,7 +985232,7 @@ function addReplayForgetCommand(cli, cliContext) {
984866
985232
  return;
984867
985233
  }
984868
985234
  if (args.length > 0 && args.every((a3) => a3.startsWith("patch-"))) {
984869
- const result2 = (0, import_generator_cli5.replayForget)({ outputDir, options: { patchIds: args, dryRun } });
985235
+ const result2 = await (0, import_generator_cli5.replayForget)({ outputDir, options: { patchIds: args, dryRun } });
984870
985236
  if (!result2.initialized) {
984871
985237
  cliContext.logger.info("Replay is not initialized. Nothing to forget.");
984872
985238
  return;
@@ -984886,7 +985252,7 @@ function addReplayForgetCommand(cli, cliContext) {
984886
985252
  return;
984887
985253
  }
984888
985254
  const pattern = args.length === 1 ? args[0] : void 0;
984889
- const result = (0, import_generator_cli5.replayForget)({ outputDir, options: { pattern } });
985255
+ const result = await (0, import_generator_cli5.replayForget)({ outputDir, options: { pattern } });
984890
985256
  if (!result.initialized) {
984891
985257
  cliContext.logger.info("Replay is not initialized. Nothing to forget.");
984892
985258
  return;
@@ -984929,7 +985295,7 @@ Remove ${matched.length} patch(es)? [y/N] `, resolvePrompt);
984929
985295
  }
984930
985296
  }
984931
985297
  const patchIds = matched.map((p6) => p6.id);
984932
- const removeResult = (0, import_generator_cli5.replayForget)({ outputDir, options: { patchIds, dryRun: false } });
985298
+ const removeResult = await (0, import_generator_cli5.replayForget)({ outputDir, options: { patchIds, dryRun: false } });
984933
985299
  cliContext.logger.info(
984934
985300
  `Removed ${removeResult.removed.length} patch(es). ${removeResult.remaining} remaining.`
984935
985301
  );