<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>FlagLint | FlagLint Blog</title><description>FlagLint documentation and engineering notes for auditing LaunchDarkly Node.js SDK usage, previewing safe OpenFeature migrations, and enforcing the boundary in CI.</description><link>https://flaglint.dev/</link><language>en</language><item><title>Feature Flag Technical Debt in TypeScript: Find, Measure, and Clear It</title><link>https://flaglint.dev/blog/feature-flag-technical-debt-typescript/</link><guid isPermaLink="true">https://flaglint.dev/blog/feature-flag-technical-debt-typescript/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TypeScript’s type system enforces interface contracts and catches argument-type mismatches at compile time — but it cannot see which of your modules still depend on the LaunchDarkly SDK, which of those call sites can be automatically rewritten, or how many engineer-hours the migration backlog represents.&lt;/p&gt;
&lt;p&gt;Feature flag technical debt in TypeScript codebases compounds quietly. A team ships a boolean flag behind &lt;code dir=&quot;auto&quot;&gt;ldClient.boolVariation&lt;/code&gt;, the rollout succeeds, and the code moves on. Six months later the flag is still evaluated on every request. The surrounding code has grown around it. The LaunchDarkly SDK is a locked-in transitive dependency for the entire module that contains it. And no one has a reliable count of how many of these exist, because the only tool most teams reach for is a &lt;code dir=&quot;auto&quot;&gt;grep&lt;/code&gt; that conflates static flag keys, wrapper functions, and bulk calls into one undifferentiated list.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://flaglint.dev&quot;&gt;FlagLint&lt;/a&gt; is a free open-source CLI that parses TypeScript source files using an AST scanner to enumerate every LaunchDarkly SDK call site, classify each one by call type and risk, compute a readiness score, and output a migration plan to OpenFeature. No LaunchDarkly API key required.&lt;/p&gt;
&lt;!-- Image 1 (intro): &quot;A close up of a laptop computer with code on the screen&quot; by Behnam Norouzi
     Source: https://unsplash.com/photos/a-close-up-of-a-laptop-computer-with-code-on-the-screen-lYFERR5dTG4
     Alt: Close-up of a laptop screen showing multi-colored programming code in a dark environment --&gt;
&lt;div&gt;&lt;h2 id=&quot;why-grep-misses-typescript-feature-flag-technical-debt&quot;&gt;Why grep misses TypeScript feature flag technical debt&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Running &lt;code dir=&quot;auto&quot;&gt;grep -r &quot;ldClient&quot; ./src&lt;/code&gt; gives you a count. It does not give you a classification. Every result looks equivalent in grep output, but three structurally different situations hide behind the same pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Static flag key, direct call&lt;/strong&gt; — the flag key is a string literal; the call type is &lt;code dir=&quot;auto&quot;&gt;boolVariation&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;stringVariation&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;numberVariation&lt;/code&gt;; the return type is known. FlagLint can generate a safe rewrite for this automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrapper function with a dynamic key&lt;/strong&gt; — the function accepts a &lt;code dir=&quot;auto&quot;&gt;flagKey&lt;/code&gt; parameter and calls the LaunchDarkly SDK internally. FlagLint cannot statically determine which flag is being evaluated, verify the call type, or confirm the return type. This is a high-risk call type.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detail evaluation or bulk call&lt;/strong&gt; — &lt;code dir=&quot;auto&quot;&gt;boolVariationDetail&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;allFlagsState&lt;/code&gt; have no direct OpenFeature provider equivalent and cannot be safely transformed by a static rewriter.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All three groups require completely different migration approaches — but grep cannot distinguish them.&lt;/p&gt;
&lt;p&gt;Run &lt;code dir=&quot;auto&quot;&gt;flaglint scan&lt;/code&gt; against your source directory to get the AST-based inventory:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scan&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Real output from the enterprise checkout service included with FlagLint (5 source files):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Scanning examples/enterprise-checkout-service/src/...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ 19 flag usages found across 11 unique flags (65ms)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ℹ  1 dynamic flag key(s) require manual review&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# FlagLint Scan Report&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Scanned:** 5 files in 65ms&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Flag usages:** 19 across 11 unique flags&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Stale candidates:** 0 flags flagged for review&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Flag Inventory&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Flag Key              | Usages | Files | Call Types                                                 | Status   |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|-----------------------|--------|-------|------------------------------------------------------------|----------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| (dynamic key)         | 7      | 3     | variationDetail, boolVariation, stringVariation, ...       | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| checkout-experiment   | 1      | 1     | boolVariationDetail                                        | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| (dynamic key)         | 1      | 1     | allFlagsState                                              | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| checkout-v2           | 1      | 1     | boolVariation                                              | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| payment-provider      | 1      | 1     | stringVariation                                            | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| one-click-checkout    | 1      | 1     | boolVariation                                              | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| checkout-currency     | 1      | 1     | stringVariation                                            | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| discount-percentage   | 1      | 1     | numberVariation                                            | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| max-discount-amount   | 1      | 1     | numberVariation                                            | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| discount-config       | 1      | 1     | jsonVariation                                              | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| pricing-tier-config   | 1      | 1     | jsonVariation                                              | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| recommendations-variant | 1    | 1     | stringVariation                                            | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| bulk-discount-enabled | 1      | 1     | boolVariation                                              | ✓ Active |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Seven of the nineteen usages resolve to a dynamic flag key. All seven originate from &lt;code dir=&quot;auto&quot;&gt;flags-wrapper.ts&lt;/code&gt;, which accepts &lt;code dir=&quot;auto&quot;&gt;flagKey&lt;/code&gt; as a parameter and proxies calls to the LaunchDarkly SDK. Grep would list those seven as equivalent entries alongside the statically-keyed calls in &lt;code dir=&quot;auto&quot;&gt;checkout.ts&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;pricing.ts&lt;/code&gt;. The AST scanner surfaces the wrapper boundary.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;measuring-the-flag-debt&quot;&gt;Measuring the flag debt&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;flaglint scan&lt;/code&gt; gives you the inventory. &lt;code dir=&quot;auto&quot;&gt;flaglint audit&lt;/code&gt; adds risk classification, a readiness score, and an optional effort estimate in engineer-hours:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;examples/enterprise-checkout-service/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Real output:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Auditing examples/enterprise-checkout-service/...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# FlagLint Audit Report&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Files scanned:** 16&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Duration:** 97ms&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Summary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Total Flags | High Risk | Medium Risk | Total Usages |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|-------------|-----------|-------------|--------------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| 13          | 3         | 10          | 27           |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Dynamic Keys | Detail Evals | Bulk Calls | Stale Signals | Safely Automatable | Manual Review |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|--------------|--------------|------------|---------------|--------------------|---------------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| 7            | 1            | 1          | 0             | 18                 | 9             |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; **Staleness:** No staleness signals detected. Heuristics checked: keyword match&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; (flag key contains old/deprecated/legacy/temp/tmp/test/demo), path pattern&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; (test/spec/mock files, deprecated/old/legacy directories), and minFileCount threshold.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; Git-history-based staleness (last evaluation date) requires git metadata and is not&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; available in a pure static scan.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Migration Readiness&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Migration readiness: **67/100** · moderate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[█████████████████░░░░░░░░] 67%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;18 safely automatable  ·  9 require manual review&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The readiness score is the fraction of direct LaunchDarkly SDK call sites that FlagLint can rewrite automatically. A score of 67 means 18 of the 27 call sites are safely transformable. The remaining 9 require a human to resolve before an automated pass can run on those files.&lt;/p&gt;
&lt;p&gt;The staleness signal column surfaces flag keys whose names carry heuristic staleness signal — keywords like &lt;code dir=&quot;auto&quot;&gt;old&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;deprecated&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;legacy&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;tmp&lt;/code&gt; in the flag key itself. Zero here means no staleness signal at the source level. Staleness detection does not require a LaunchDarkly API key or runtime data.&lt;/p&gt;
&lt;p&gt;Add &lt;code dir=&quot;auto&quot;&gt;--effort-estimate&lt;/code&gt; to convert the count into a planning number:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--effort-estimate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This appends a three-phase estimate: automatable call sites at approximately 0.25 engineer-hours each, manual review call sites at 1.5–3 hours each, plus 30% overhead for validation and testing. Supplying &lt;code dir=&quot;auto&quot;&gt;--hourly-rate 150&lt;/code&gt; appends a dollar range to the summary. The estimate is a planning heuristic calibrated to call-site complexity, not a billing projection.&lt;/p&gt;
&lt;!-- Image 2 (middle): &quot;Code displayed on computer screens&quot; by Jakub Żerdzicki
     Source: https://unsplash.com/photos/code-displayed-on-computer-screens-v-jFS1AsHXo
     Alt: Multiple monitors displaying syntax-highlighted source code in a dark office environment --&gt;
&lt;div&gt;&lt;h2 id=&quot;the-three-risk-tiers-in-the-flag-debt-inventory&quot;&gt;The three risk tiers in the flag debt inventory&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every flag key in the audit report lands in one of three tiers:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;High risk — cannot be automated:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;dynamic key&gt;&lt;/code&gt; (7 usages across 3 files) — the flag key is a runtime variable, not a string literal. FlagLint marks every dynamic flag key as high risk because it cannot statically determine which flag is being evaluated, verify the call type, or confirm the return type. The resolution is to trace back to the call sites that supply the key parameter, then extract each unique flag key to a named constant. Re-running &lt;code dir=&quot;auto&quot;&gt;flaglint audit&lt;/code&gt; after that change will reclassify the previously-dynamic entries as automatable.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;checkout-experiment&lt;/code&gt; (1 usage) — &lt;code dir=&quot;auto&quot;&gt;boolVariationDetail&lt;/code&gt; is a detail evaluation call type. OpenFeature has a &lt;code dir=&quot;auto&quot;&gt;getBooleanDetails&lt;/code&gt; equivalent, but the reason vocabulary differs: the LaunchDarkly SDK returns &lt;code dir=&quot;auto&quot;&gt;TARGETING_MATCH&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;RULE_MATCH&lt;/code&gt;; OpenFeature uses its own reason strings. Code that inspects &lt;code dir=&quot;auto&quot;&gt;reason.kind&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;reason.ruleId&lt;/code&gt; must be updated by hand alongside the call site.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;*&lt;/code&gt; (1 usage) — &lt;code dir=&quot;auto&quot;&gt;allFlagsState&lt;/code&gt; is a bulk call with no OpenFeature provider equivalent. The resolution is to enumerate the specific flag keys the bulk call feeds and replace them with individual named-key calls. If full flag state at application startup is genuinely required, retain the LaunchDarkly SDK client for that bootstrap path while migrating all other call sites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Medium risk — automatable with review:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;discount-config&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;pricing-tier-config&lt;/code&gt; are &lt;code dir=&quot;auto&quot;&gt;jsonVariation&lt;/code&gt; call types. They are safely automatable, but OpenFeature’s object value API returns &lt;code dir=&quot;auto&quot;&gt;unknown&lt;/code&gt;. After the rewrite, confirm that any code that casts or destructures the return value still compiles and behaves correctly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Automatable — safe to transform:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Eight flag keys — &lt;code dir=&quot;auto&quot;&gt;checkout-v2&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;payment-provider&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;one-click-checkout&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;checkout-currency&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;discount-percentage&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;max-discount-amount&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;recommendations-variant&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;bulk-discount-enabled&lt;/code&gt; — are called with &lt;code dir=&quot;auto&quot;&gt;boolVariation&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;stringVariation&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;numberVariation&lt;/code&gt; using static string literal flag keys. FlagLint can rewrite all of these.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-argument-order-inversion&quot;&gt;The argument order inversion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The automatable rewrite is not a text substitution. The LaunchDarkly SDK and OpenFeature provider place the fallback value and evaluation context in different argument positions:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// LaunchDarkly SDK — (flagKey, context, fallback)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// OpenFeature provider — (flagKey, fallback, context)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The flag key is identical. The fallback value and evaluation context swap positions. A naive find-and-replace migration that does not track argument order evaluates every flag with the wrong context on the first request and returns the wrong result silently. FlagLint’s AST rewriter moves all three arguments to the correct positions for each automatable call type.&lt;/p&gt;
&lt;p&gt;Preview every transformation before any file is touched:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--dry-run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The dry-run output shows a reviewable diff for each automatable call site alongside the OpenFeature provider setup steps. No files are modified.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;applying-the-migration-plan-and-enforcing-the-boundary&quot;&gt;Applying the migration plan and enforcing the boundary&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once you have reviewed the dry-run output and set up the OpenFeature provider:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--apply&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This applies all safe rewrites in-place. Run your test suite after the apply. Then lock the boundary in CI:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-direct-launchdarkly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;flaglint validate&lt;/code&gt; exits non-zero when any direct LaunchDarkly SDK call is detected. Add it to your GitHub Actions workflow and direct LaunchDarkly SDK calls become a build failure from that point forward, blocking regressions as the migration lands across multiple PRs.&lt;/p&gt;
&lt;!-- Image 3 (end): &quot;Coding code on multiple computer screens&quot; — Unsplash
     Source: https://unsplash.com/photos/coding-code-on-multiple-computer-screens-O3ChbcT94NM
     Alt: Multiple computer monitors displaying programming code in a developer workspace --&gt;
&lt;div&gt;&lt;h2 id=&quot;clearing-the-manual-review-backlog-incrementally&quot;&gt;Clearing the manual review backlog incrementally&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Work through the high-risk items in batches. After each batch, re-run &lt;code dir=&quot;auto&quot;&gt;flaglint audit&lt;/code&gt; to watch the readiness score climb. At 80 or above, the remaining feature flag technical debt in TypeScript can be handled in a single automated pass — &lt;code dir=&quot;auto&quot;&gt;flaglint migrate --apply&lt;/code&gt; clears it and &lt;code dir=&quot;auto&quot;&gt;flaglint validate --no-direct-launchdarkly&lt;/code&gt; confirms the boundary is clean.&lt;/p&gt;
&lt;p&gt;The audit plus the CI gate is a closed loop: audit measures what exists, migrate rewrites what is safe, validate blocks regressions, audit confirms progress. You can run the full cycle on a large codebase before writing a single line of migration code. The readiness score tells you up front whether you are looking at a two-sprint effort or a six-month program, and the migration plan tells you exactly which call sites require which kind of attention.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/guides/launchdarkly-to-openfeature-nodejs/&quot;&gt;LaunchDarkly to OpenFeature Node.js migration guide&lt;/a&gt; — the six-step workflow: audit, provider setup, dry-run, apply, validate&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/guides/manual-review-patterns/&quot;&gt;Manual review patterns&lt;/a&gt; — resolving dynamic flag keys, detail evaluations, and bulk calls before running migrate&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/cli/audit/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;flaglint audit&lt;/code&gt; CLI reference&lt;/a&gt; — all options, output formats (JSON, Markdown, HTML), and exit codes&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/concepts/migration-readiness/&quot;&gt;Migration readiness concept&lt;/a&gt; — grade thresholds and the formula behind the readiness score&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/tutorials/enforce-in-github-actions/&quot;&gt;Enforce in GitHub Actions&lt;/a&gt; — CI workflow to block direct LaunchDarkly SDK regressions&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>launchdarkly</category><category>openfeature</category><category>typescript</category><category>flag-debt</category><category>migration</category></item><item><title>Enforcing Your LaunchDarkly to OpenFeature Migration in GitHub Actions</title><link>https://flaglint.dev/blog/enforce-launchdarkly-migration-github-actions/</link><guid isPermaLink="true">https://flaglint.dev/blog/enforce-launchdarkly-migration-github-actions/</guid><pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You started your LaunchDarkly to OpenFeature migration three weeks ago. The first sprint went well—five files converted, OpenFeature provider wired in, existing tests green. Then a teammate opened a PR for a new service. Inside it: two fresh &lt;code dir=&quot;auto&quot;&gt;ldClient.boolVariation()&lt;/code&gt; calls. Not malicious. They just forgot. You merge it anyway because it is not worth blocking the PR over. Two weeks later there are six more.&lt;/p&gt;
&lt;p&gt;This is migration drift. It is the most common reason LaunchDarkly to OpenFeature migration projects stall: there is no gate on new direct LaunchDarkly SDK calls landing in main. Without a CI check that fails on any new call site, every PR can quietly add to the flag debt you are actively paying down.&lt;/p&gt;
&lt;p&gt;FlagLint addresses this with two commands—&lt;code dir=&quot;auto&quot;&gt;audit&lt;/code&gt; to measure the existing flag debt and &lt;code dir=&quot;auto&quot;&gt;validate&lt;/code&gt; to enforce the boundary—and a one-step GitHub Actions integration that adds the gate with two lines of YAML.&lt;/p&gt;
&lt;!-- Image 1 (intro): &quot;a close up of a computer screen with many lines of code on it&quot; by Timothy Cuenat
     Source: https://unsplash.com/photos/a-close-up-of-a-computer-screen-with-many-lines-of-code-on-it-NH0pmKaZeuk
     Alt: Close-up of a monitor displaying dense multi-colored lines of syntax-highlighted source code
     Placement: below opening paragraph --&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-baseline-your-flag-debt-before-you-gate&quot;&gt;Step 1: Baseline your flag debt before you gate&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before you block anything in CI, run &lt;code dir=&quot;auto&quot;&gt;flaglint audit&lt;/code&gt; against your source directory. This produces a readiness score and a per-flag-key inventory—the snapshot you will measure progress against, and the list you need when deciding what to exclude during the transition period.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Real output from the &lt;code dir=&quot;auto&quot;&gt;src/&lt;/code&gt; directory of the enterprise checkout service shipped with FlagLint examples:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Auditing examples/enterprise-checkout-service/src/...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# FlagLint Audit Report&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Scanned at:** 2026-06-24T03:20:27.050Z&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Scan root:** /home/user/flaglint/examples/enterprise-checkout-service/src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Files scanned:** 5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Duration:** 63ms&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Summary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Total Flags | High Risk | Medium Risk | Total Usages |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|-------------|-----------|-------------|--------------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| 13 | 3 | 10 | 19 |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Dynamic Keys | Detail Evals | Bulk Calls | Stale Signals | Safely Automatable | Manual Review |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|--------------|--------------|------------|---------------|-------------------|---------------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| 7 | 1 | 1 | 0 | 10 | 9 |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Migration Readiness&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Migration readiness: **53/100** · moderate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[█████████████░░░░░░░░░░░░] 53%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;10 safely automatable  ·  9 require manual review&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Flag Debt Inventory&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Flag Key | Risk | Usages | Files | Call Types | Reasons |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|----------|------|--------|-------|------------|---------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `&amp;#x3C;dynamic key&gt;` | 🔴 High | 7 | 3 | variationDetail, boolVariation, stringVariation, numberVariation, jsonVariation | dynamic key |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `checkout-experiment` | 🔴 High | 1 | 1 | boolVariationDetail | detail evaluation |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `*` | 🔴 High | 1 | 1 | allFlagsState | bulk call |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `checkout-v2` | 🟢 Automatable | 1 | 1 | boolVariation | safely automatable |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `payment-provider` | 🟢 Automatable | 1 | 1 | stringVariation | safely automatable |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `one-click-checkout` | 🟢 Automatable | 1 | 1 | boolVariation | safely automatable |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `checkout-currency` | 🟢 Automatable | 1 | 1 | stringVariation | safely automatable |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `discount-percentage` | 🟢 Automatable | 1 | 1 | numberVariation | safely automatable |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `max-discount-amount` | 🟢 Automatable | 1 | 1 | numberVariation | safely automatable |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `discount-config` | 🟡 Medium | 1 | 1 | jsonVariation | safely automatable, json variation |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `pricing-tier-config` | 🟡 Medium | 1 | 1 | jsonVariation | safely automatable, json variation |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `recommendations-variant` | 🟢 Automatable | 1 | 1 | stringVariation | safely automatable |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `bulk-discount-enabled` | 🟢 Automatable | 1 | 1 | boolVariation | safely automatable |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Audit complete: 13 flags — 3 high risk, 10 medium risk&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Migration readiness: 53/100  ·  moderate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[█████████████░░░░░░░░░░░░] 53%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;10 safely automatable  ·  9 require manual review&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The readiness score of 53 means 10 of the 19 direct LaunchDarkly SDK call sites can be automatically rewritten by &lt;code dir=&quot;auto&quot;&gt;flaglint migrate --apply&lt;/code&gt;. The remaining 9 require manual work: 7 use a dynamic flag key (a variable, not a string literal), 1 is a detail evaluation returning reason metadata, and 1 is a bulk &lt;code dir=&quot;auto&quot;&gt;allFlagsState&lt;/code&gt; call with no single-flag OpenFeature equivalent. The staleness signal count of zero means no flag keys carry source-level stale signal—no keys contain &lt;code dir=&quot;auto&quot;&gt;old&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;deprecated&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;legacy&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;tmp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Save this output as your progress baseline.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-run-the-validate-command-locally&quot;&gt;Step 2: Run the validate command locally&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;flaglint validate --no-direct-launchdarkly&lt;/code&gt; exits non-zero when any direct LaunchDarkly SDK call is found in the scanned directory. Before wiring it into CI, run it locally so you know exactly what the gate will report:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-direct-launchdarkly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Real output from the same &lt;code dir=&quot;auto&quot;&gt;src/&lt;/code&gt; directory:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Scanning examples/enterprise-checkout-service/src/...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✗ validate --no-direct-launchdarkly: 19 direct LaunchDarkly evaluation call(s) found.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;analytics.ts:51:43 — variationDetail(&quot;(dynamic key)&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;analytics.ts:76:23 — boolVariationDetail(&quot;checkout-experiment&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;analytics.ts:104:22 — allFlagsState(bulk inventory)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;checkout.ts:40:9 — boolVariation(&quot;checkout-v2&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;checkout.ts:49:9 — stringVariation(&quot;payment-provider&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;checkout.ts:58:9 — boolVariation(&quot;one-click-checkout&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;checkout.ts:67:9 — stringVariation(&quot;checkout-currency&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;flags-wrapper.ts:48:9 — boolVariation(&quot;(dynamic key)&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;flags-wrapper.ts:67:11 — boolVariation(&quot;(dynamic key)&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;flags-wrapper.ts:70:11 — stringVariation(&quot;(dynamic key)&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;flags-wrapper.ts:73:11 — numberVariation(&quot;(dynamic key)&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;flags-wrapper.ts:75:9 — jsonVariation(&quot;(dynamic key)&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pricing.ts:46:9 — numberVariation(&quot;discount-percentage&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pricing.ts:55:9 — numberVariation(&quot;max-discount-amount&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pricing.ts:69:9 — jsonVariation(&quot;discount-config&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pricing.ts:83:9 — jsonVariation(&quot;pricing-tier-config&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;product.ts:52:9 — boolVariation(&quot;(dynamic key)&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;product.ts:61:9 — stringVariation(&quot;recommendations-variant&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;product.ts:70:9 — boolVariation(&quot;bulk-discount-enabled&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;These files must migrate to OpenFeature before this rule passes.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Run `flaglint migrate --dry-run` to review the migration plan.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each finding shows file path, line number, column, call type, and flag key. All call types are tracked: &lt;code dir=&quot;auto&quot;&gt;boolVariation&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;stringVariation&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;numberVariation&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;jsonVariation&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;variationDetail&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;boolVariationDetail&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;allFlagsState&lt;/code&gt;. Dynamic flag keys appear as &lt;code dir=&quot;auto&quot;&gt;(dynamic key)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The gate exits non-zero the moment any direct LaunchDarkly SDK call is detected, blocking any new flag from bypassing the OpenFeature provider. When validate finds zero violations, it exits cleanly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ validate --no-direct-launchdarkly: no direct LaunchDarkly evaluation calls found.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That line is what you are working toward.&lt;/p&gt;
&lt;!-- Image 2 (middle): &quot;screens display coding text, representing programming work&quot; by Jakub Żerdzicki
     Source: https://unsplash.com/photos/screens-display-coding-text-representing-programming-work-gCyjEr-g2oI
     Alt: Multiple screens displaying coding text representing active software development work
     Placement: between Step 2 and Step 3 --&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-add-the-github-actions-gate&quot;&gt;Step 3: Add the GitHub Actions gate&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;FlagLint ships a composite GitHub Actions action. The minimum setup is two lines:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;FlagLint&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;pull_request&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;runs-on&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ubuntu-latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;actions/checkout@v4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flaglint/flaglint@main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;directory&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The action runs &lt;code dir=&quot;auto&quot;&gt;flaglint validate ./src --no-direct-launchdarkly&lt;/code&gt; and exits 1 when any direct call is found. Do not set &lt;code dir=&quot;auto&quot;&gt;continue-on-error: true&lt;/code&gt; on the FlagLint step. The job failing is the mechanism—that is what blocks the PR.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;excluding-the-bootstrap-file&quot;&gt;Excluding the bootstrap file&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Your OpenFeature provider setup module legitimately imports from the LaunchDarkly SDK to instantiate the provider. Exclude it with &lt;code dir=&quot;auto&quot;&gt;--bootstrap-exclude&lt;/code&gt; so the gate does not fire on it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flaglint/flaglint@main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;directory&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;extra-args&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;--bootstrap-exclude &quot;src/provider/setup.ts&quot;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You can pass multiple exclusion patterns:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;extra-args&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&gt;-&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--bootstrap-exclude &quot;src/provider/setup.ts&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--bootstrap-exclude &quot;src/bootstrap/**&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The excluded files can call the LaunchDarkly SDK directly. Everything else cannot. The &lt;code dir=&quot;auto&quot;&gt;--bootstrap-exclude&lt;/code&gt; flag accepts glob patterns, so a single &lt;code dir=&quot;auto&quot;&gt;&quot;src/provider/**&quot;&lt;/code&gt; covers a provider directory with multiple files.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-add-sarif-annotations-for-inline-pr-diff-visibility&quot;&gt;Step 4: Add SARIF annotations for inline PR diff visibility&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Job-level failure tells engineers something is wrong. SARIF annotations tell them exactly which line is wrong, directly in the PR diff. Add the SARIF upload step alongside the gate:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;FlagLint Policy&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;pull_request&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;runs-on&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ubuntu-latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;permissions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;contents&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;read&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;security-events&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;actions/checkout@v4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Validate no direct LaunchDarkly calls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flaglint/flaglint@main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;directory&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;extra-args&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&gt;-&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--bootstrap-exclude &quot;src/provider/setup.ts&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--format sarif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;--output flaglint-validation.sarif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Upload validation SARIF&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;always()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;sarif_file&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flaglint-validation.sarif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;SARIF findings use rule id &lt;code dir=&quot;auto&quot;&gt;flaglint.direct-launchdarkly&lt;/code&gt;. With &lt;code dir=&quot;auto&quot;&gt;security-events: write&lt;/code&gt;, GitHub annotates each violation inline on the relevant PR diff line as a code scanning alert. Set &lt;code dir=&quot;auto&quot;&gt;if: always()&lt;/code&gt; on the upload step—not on the validate step—so GitHub receives the SARIF file even after the job fails, and annotations appear regardless of whether the PR passes.&lt;/p&gt;
&lt;!-- Image 3 (end): &quot;a coder&apos;s workspace, filled with code and keyboards&quot; by Jakub Żerdzicki
     Source: https://unsplash.com/photos/a-coders-workspace-filled-with-code-and-keyboards-FjtWczJWRlc
     Alt: A developer&apos;s multi-monitor workstation showing source code with a glowing keyboard in a dark room
     Placement: between Step 4 and the transition period section --&gt;
&lt;div&gt;&lt;h2 id=&quot;managing-the-transition-period&quot;&gt;Managing the transition period&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If your service already has 19 direct LaunchDarkly SDK calls when you add the gate, CI will immediately fail. Two approaches handle the transition:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start with SARIF-only, then harden.&lt;/strong&gt; Set &lt;code dir=&quot;auto&quot;&gt;continue-on-error: true&lt;/code&gt; temporarily on the validate step so violations surface as code scanning alerts without blocking merges. Remove &lt;code dir=&quot;auto&quot;&gt;continue-on-error&lt;/code&gt; once you have migrated the bulk of existing call sites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exclude directories that are mid-migration.&lt;/strong&gt; Use &lt;code dir=&quot;auto&quot;&gt;--bootstrap-exclude&lt;/code&gt; patterns to allow files already in the migration queue through the gate while blocking any new file from adding a direct LaunchDarkly SDK call. Remove each exclusion as you migrate that directory.&lt;/p&gt;
&lt;p&gt;Re-run the audit after each sprint to track how the readiness score moves. The goal is a validate run that exits cleanly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ validate --no-direct-launchdarkly: no direct LaunchDarkly evaluation calls found.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When that is the consistent CI result, the LaunchDarkly to OpenFeature migration is structurally complete. No new direct call sites can land, and the codebase no longer carries flag debt pointing at the LaunchDarkly SDK.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/guides/launchdarkly-to-openfeature-nodejs/&quot;&gt;LaunchDarkly to OpenFeature Node.js migration guide&lt;/a&gt; — the full end-to-end workflow: audit, provider setup, dry-run, apply, validate&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/guides/manual-review-patterns/&quot;&gt;Manual Review Patterns&lt;/a&gt; — how to resolve dynamic flag keys, detail evaluations, and bulk calls before &lt;code dir=&quot;auto&quot;&gt;migrate --apply&lt;/code&gt; can handle them automatically&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/cli/audit/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;flaglint audit&lt;/code&gt; CLI reference&lt;/a&gt; — all options, output formats (JSON, Markdown, HTML), and exit behavior&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/concepts/migration-readiness/&quot;&gt;Migration Readiness concept&lt;/a&gt; — how the readiness score is calculated and what the grade thresholds mean&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/integrations/github-actions/&quot;&gt;GitHub Actions integration reference&lt;/a&gt; — full option table for the composite action, SARIF configuration, and rule id reference&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>launchdarkly</category><category>openfeature</category><category>github-actions</category><category>ci</category><category>migration</category></item><item><title>LaunchDarkly Flag Debt: Audit, Estimate, and Prioritize Your Migration</title><link>https://flaglint.dev/blog/launchdarkly-flag-debt/</link><guid isPermaLink="true">https://flaglint.dev/blog/launchdarkly-flag-debt/</guid><pubDate>Tue, 23 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;LaunchDarkly flag debt accumulates quietly. A team ships a feature behind a flag, verifies the rollout, and moves on. The flag stays. Six months later it is still being evaluated on every request — carrying the original business logic, an implicit dependency on the LaunchDarkly SDK, and a refactoring cost that compounds with every passing sprint.&lt;/p&gt;
&lt;p&gt;At scale, the problem becomes a planning question as much as a technical one. Grepping for &lt;code dir=&quot;auto&quot;&gt;ldClient&lt;/code&gt; gives you a count, but it misses wrappers, misclassifies risk levels, and gives no indication of how long cleanup will actually take. Before you can schedule the work or make the case to your engineering manager, you need a measurement: how many direct LaunchDarkly SDK calls exist, which ones are safe to automate, and how many engineer-hours does this represent?&lt;/p&gt;
&lt;p&gt;FlagLint produces that measurement from static source analysis alone. No LaunchDarkly API key required.&lt;/p&gt;
&lt;!-- Image 1 (intro): &quot;a computer screen with a bunch of code on it&quot; by Chris Ried
     Source: https://unsplash.com/photos/a-computer-screen-with-a-bunch-of-code-on-it-ieic5Tq8YMk
     Alt: A monitor displaying multi-colored syntax-highlighted source code --&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-get-the-flag-debt-inventory&quot;&gt;Step 1: Get the flag debt inventory&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Run &lt;code dir=&quot;auto&quot;&gt;flaglint audit&lt;/code&gt; against your source directory:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Real output from the enterprise checkout service shipped with FlagLint (5 source files, run from &lt;code dir=&quot;auto&quot;&gt;examples/enterprise-checkout-service/&lt;/code&gt;):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Auditing src/...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# FlagLint Audit Report&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Files scanned:** 5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**Duration:** 64ms&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Summary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Total Flags | High Risk | Medium Risk | Total Usages |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|-------------|-----------|-------------|--------------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| 13 | 3 | 10 | 20 |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Dynamic Keys | Detail Evals | Bulk Calls | Stale Signals | Safely Automatable | Manual Review |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|--------------|--------------|------------|---------------|-------------------|---------------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| 8 | 1 | 1 | 0 | 10 | 10 |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Migration Readiness&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Migration readiness: **50/100** · moderate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[█████████████░░░░░░░░░░░░] 50%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;10 safely automatable  ·  10 require manual review&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The readiness score answers the foundational question before any migration begins: what fraction of your direct LaunchDarkly SDK call sites can a tool rewrite automatically? A score of 50 — &lt;em&gt;moderate&lt;/em&gt; — means exactly half require a human to review before any automated step runs. A score below 50 is graded &lt;em&gt;complex&lt;/em&gt;; 80 or above is &lt;em&gt;ready&lt;/em&gt;, meaning the migration can proceed with minimal manual effort.&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;stale signals&lt;/code&gt; column surfaces flag keys that carry staleness signal — flag keys containing keywords like &lt;code dir=&quot;auto&quot;&gt;old&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;deprecated&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;legacy&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;tmp&lt;/code&gt;. Zero here means no flag key names carry obvious staleness signal at the source level. Git-based staleness detection, which checks last-evaluation date against git history, is outside the scope of a static scan.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-measure-the-launchdarkly-flag-debt-in-engineer-hours&quot;&gt;Step 2: Measure the LaunchDarkly flag debt in engineer-hours&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A risk count tells you what you have. It does not tell you what it will cost to fix. Add &lt;code dir=&quot;auto&quot;&gt;--effort-estimate&lt;/code&gt; to get a directional planning number:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--effort-estimate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Real output:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Estimated Migration Effort&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| | Low | High |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|---|---|---|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Automatable calls (10 calls) | 2.5h | 3.8h |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Manual review calls (10 calls) | 15h | 30h |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Validation &amp;#x26; testing | 5.3h | 10.1h |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| **Total** | **22.8h** | **43.9h** |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; Estimates are directional planning guides based on call-site complexity. Actual effort&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; depends on test coverage, team familiarity, and provider setup. FlagLint does not access&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; runtime data or LaunchDarkly billing.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Migration readiness: 50/100  ·  moderate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[█████████████░░░░░░░░░░░░] 50%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;10 safely automatable  ·  10 require manual review&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Estimated migration effort: 22.8h – 43.9h&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Estimates are directional. See the report for assumptions.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The estimate breaks into three phases. &lt;strong&gt;Automation&lt;/strong&gt; covers running &lt;code dir=&quot;auto&quot;&gt;flaglint migrate --apply&lt;/code&gt;, reviewing the generated diffs, and merging — roughly 0.25 engineer-hours per automatable call site. &lt;strong&gt;Manual review&lt;/strong&gt; is where the range widens: each call site that requires human inspection is estimated at 1.5–3h, because the effort depends on what the surrounding code does with the evaluated value and how complex the flag key resolution is. &lt;strong&gt;Validation&lt;/strong&gt; adds 30% of the combined automation and manual total for test runs, CI, and integration checks.&lt;/p&gt;
&lt;p&gt;Supplying &lt;code dir=&quot;auto&quot;&gt;--hourly-rate&lt;/code&gt; converts the estimate to an engineering cost range:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--effort-estimate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--hourly-rate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This appends &lt;code dir=&quot;auto&quot;&gt;Estimated cost: $3,420 – $6,585&lt;/code&gt; to the summary output. It is a planning heuristic calibrated to call-site complexity, not a billing projection.&lt;/p&gt;
&lt;!-- Image 2 (middle): &quot;shallow focus photography of computer codes&quot; by Shahadat Rahman
     Source: https://unsplash.com/photos/shallow-focus-photography-of-computer-codes-BfrQnKBulYQ
     Alt: Close-up shallow-focus photograph of programming code on a laptop screen --&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-read-the-flag-debt-inventory&quot;&gt;Step 3: Read the flag debt inventory&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The audit report includes a per-flag breakdown. This is where you translate the summary numbers into a concrete migration plan:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Flag Key              | Risk           | Usages | Call Types                           | Reasons                     |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|-----------------------|----------------|--------|--------------------------------------|-----------------------------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `&amp;#x3C;dynamic key&gt;`       | 🔴 High        | 8      | boolVariation, stringVariation, ...  | dynamic key, wrapper usage  |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `checkout-experiment` | 🔴 High        | 1      | boolVariationDetail                  | detail evaluation           |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `*`                   | 🔴 High        | 1      | allFlagsState                        | bulk call                   |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `checkout-v2`         | 🟢 Automatable | 1      | boolVariation                        | safely automatable          |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `payment-provider`    | 🟢 Automatable | 1      | stringVariation                      | safely automatable          |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| `discount-config`     | 🟡 Medium      | 1      | jsonVariation                        | safely automatable, json variation |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Three call types drive the high-risk category in this service:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dynamic flag key&lt;/strong&gt; (8 usages across 3 files) — the flag key is a variable or template literal rather than a string literal. In this service, &lt;code dir=&quot;auto&quot;&gt;flags-wrapper.ts&lt;/code&gt; is the source: it accepts &lt;code dir=&quot;auto&quot;&gt;flagKey&lt;/code&gt; as a parameter and calls the LaunchDarkly SDK internally. FlagLint classifies it as a wrapper and marks every call through it as high risk because it cannot statically determine which flag is being evaluated, verify the call type, or confirm the return type. The resolution is to extract each dynamic key path to a named constant per call site so subsequent &lt;code dir=&quot;auto&quot;&gt;flaglint audit&lt;/code&gt; runs can classify them as automatable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Detail evaluation&lt;/strong&gt; (1 usage) — &lt;code dir=&quot;auto&quot;&gt;boolVariationDetail&lt;/code&gt; returns an evaluation reason object alongside the flag value. OpenFeature has a &lt;code dir=&quot;auto&quot;&gt;getBooleanDetails&lt;/code&gt; equivalent, but the reason vocabulary differs from the LaunchDarkly SDK (&lt;code dir=&quot;auto&quot;&gt;TARGETING_MATCH&lt;/code&gt; vs &lt;code dir=&quot;auto&quot;&gt;RULE_MATCH&lt;/code&gt;). Code that inspects &lt;code dir=&quot;auto&quot;&gt;reason.kind&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;reason.ruleId&lt;/code&gt; must be updated alongside the call site. FlagLint cannot safely generate that transformation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bulk call&lt;/strong&gt; (1 usage) — &lt;code dir=&quot;auto&quot;&gt;allFlagsState&lt;/code&gt; has no OpenFeature provider equivalent. This call type requires an architecture decision before the migration can proceed: enumerate the specific flag keys needed explicitly, or retain the LaunchDarkly SDK client for the bootstrap path while migrating all other call sites to OpenFeature.&lt;/p&gt;
&lt;p&gt;The call-site difference between a high-risk and an automatable entry is visible in source:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// High risk — dynamic flag key, cannot be rewritten automatically&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(flagKey&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Automatable — static flag key, safely rewritable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// becomes:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The only structural difference in the automatable rewrite is argument order: the OpenFeature provider convention places the fallback value at position two and the evaluation context at position three. The flag key is preserved exactly. No flag evaluation logic at LaunchDarkly changes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-generate-a-shareable-html-report&quot;&gt;Step 4: Generate a shareable HTML report&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For teams that need to share the findings with engineering leads or allocate sprint capacity, &lt;code dir=&quot;auto&quot;&gt;--format html&lt;/code&gt; produces a self-contained file with no external dependencies:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--effort-estimate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--format&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--output&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flag-debt.html&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The file includes the summary card row, the effort estimate table, and the sortable flag debt inventory. It can be attached to a JIRA ticket, linked in a PR description, or opened locally. No LaunchDarkly credentials appear in the output — the report contains only what the static scan detected.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-5-track-progress-toward-zero-flag-debt&quot;&gt;Step 5: Track progress toward zero flag debt&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;After migrating a batch of call sites, run &lt;code dir=&quot;auto&quot;&gt;flaglint validate&lt;/code&gt; to confirm the OpenFeature boundary holds:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-direct-launchdarkly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Real output before migration begins:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✗ validate --no-direct-launchdarkly: 20 direct LaunchDarkly evaluation call(s) found.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;analytics.ts:51:43 — variationDetail(&quot;(dynamic key)&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;analytics.ts:76:23 — boolVariationDetail(&quot;checkout-experiment&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;analytics.ts:104:22 — allFlagsState(bulk inventory)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;checkout.ts:40:9 — boolVariation(&quot;checkout-v2&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;checkout.ts:49:9 — stringVariation(&quot;payment-provider&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;checkout.ts:58:9 — boolVariation(&quot;one-click-checkout&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;checkout.ts:67:9 — stringVariation(&quot;checkout-currency&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;These files must migrate to OpenFeature before this rule passes.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Run `flaglint migrate --dry-run` to review the migration plan.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Add this command to your CI pipeline. &lt;code dir=&quot;auto&quot;&gt;flaglint validate --no-direct-launchdarkly&lt;/code&gt; exits non-zero when any direct LaunchDarkly SDK call is detected, blocking regressions as the migration lands across multiple PRs. The validate gate is the mechanism that turns a migration plan into a contract.&lt;/p&gt;
&lt;!-- Image 3 (end): &quot;black computer keyboard&quot; by Fotis Fotopoulos
     Source: https://unsplash.com/photos/black-computer-keyboard-DuHKoV44prg
     Alt: A black mechanical keyboard with code visible on a monitor in the background --&gt;
&lt;p&gt;As you resolve manual-review call sites — extracting dynamic flag keys to named constants, migrating detail evaluations by hand, replacing bulk calls with enumerated evaluations — re-run the audit to watch the readiness score climb. At 80 or above, &lt;code dir=&quot;auto&quot;&gt;flaglint migrate --apply&lt;/code&gt; can handle the remaining LaunchDarkly flag debt in a single automated pass, and the CI validate gate will confirm the boundary is clean.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/guides/launchdarkly-to-openfeature-nodejs/&quot;&gt;LaunchDarkly to OpenFeature Node.js migration guide&lt;/a&gt; — six-step end-to-end workflow: audit, provider setup, dry-run, apply, validate&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/guides/manual-review-patterns/&quot;&gt;Manual Review Patterns&lt;/a&gt; — how to resolve dynamic flag keys, detail evaluations, and bulk calls before running migrate&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/cli/audit/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;flaglint audit&lt;/code&gt; CLI reference&lt;/a&gt; — all options, output formats (JSON, Markdown, HTML), and exit behavior&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/concepts/migration-readiness/&quot;&gt;Migration Readiness concept&lt;/a&gt; — grade thresholds and the formula behind the readiness score&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flaglint.dev/docs/tutorials/enforce-in-github-actions/&quot;&gt;Enforce in GitHub Actions&lt;/a&gt; — ready-made CI workflow to block direct LaunchDarkly regressions&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>launchdarkly</category><category>openfeature</category><category>flag-debt</category><category>migration</category><category>nodejs</category></item><item><title>Five LaunchDarkly SDK Patterns That Block Automatic Migration to OpenFeature</title><link>https://flaglint.dev/blog/five-patterns-that-block-migration/</link><guid isPermaLink="true">https://flaglint.dev/blog/five-patterns-that-block-migration/</guid><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Run &lt;code dir=&quot;auto&quot;&gt;flaglint migrate ./src --dry-run&lt;/code&gt; and you will see two kinds of results: call
sites with a generated diff and call sites marked &lt;code dir=&quot;auto&quot;&gt;skip — manual review required&lt;/code&gt;.
The skipped calls are not bugs in the tool. They are patterns where a mechanical
rewrite would change runtime behavior in ways the tool cannot prove are safe.&lt;/p&gt;
&lt;p&gt;This article covers the five patterns that produce skips and what you need to do for each.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;what-makes-a-call-automatable&quot;&gt;What makes a call automatable&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;FlagLint rewrites a call automatically only when it can prove three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The flag key is a static string literal.&lt;/li&gt;
&lt;li&gt;The fallback value type is known (&lt;code dir=&quot;auto&quot;&gt;boolean&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;string&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;number&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;json&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A verified OpenFeature client binding is present in scope.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If any proof fails, the call is left unchanged and flagged for manual review. The
conservative stance is intentional: a wrong rewrite is worse than no rewrite.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-1--dynamic-flag-keys&quot;&gt;Pattern 1 — Dynamic flag keys&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// key comes from a variable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;experimentGroup&lt;/span&gt;&lt;span&gt; === &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v1&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// key comes from a function call&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;discount&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;numberVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;getFlagKey&lt;/span&gt;&lt;span&gt;(user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tier&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// key is assembled at runtime&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;variant&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;feature-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;-rollout&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;control&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenFeature’s evaluation methods are structurally identical to LaunchDarkly’s for
this case — both take &lt;code dir=&quot;auto&quot;&gt;(key, defaultValue, context)&lt;/code&gt;. But if the key is dynamic,
FlagLint cannot know which flag is being evaluated, which means it cannot verify
that the OpenFeature provider has that flag configured, cannot determine the correct
return type, and cannot guarantee the fallback default is the right type for that
specific flag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Extract the dynamic selection into an explicit switch or map, then call evaluation
with a known static key per branch:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;discount&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;numberVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;getFlagKey&lt;/span&gt;&lt;span&gt;(user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tier&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;FLAG_BY_TIER&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Record&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;free: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;discount-free-tier&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pro: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;discount-pro-tier&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;enterprise: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;discount-enterprise-tier&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;FLAG_BY_TIER&lt;/span&gt;&lt;span&gt;[user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tier&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; ?? &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;discount-free-tier&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;discount&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getNumberValue&lt;/span&gt;&lt;span&gt;(flagKey&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Once the key is static in each branch, the surrounding calls become automatable in
the next &lt;code dir=&quot;auto&quot;&gt;migrate&lt;/code&gt; run.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-2--detail-evaluations&quot;&gt;Pattern 2 — Detail evaluations&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;detail&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariationDetail&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;show-banner&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// detail.value — the evaluated value&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// detail.reason — WHY it was that value (RULE_MATCH, FALLTHROUGH, etc.)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// detail.variationIndex — index into the flag&apos;s variation list&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;variationDetail&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-experiment&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;control&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenFeature has an equivalent — &lt;code dir=&quot;auto&quot;&gt;getBooleanDetails()&lt;/code&gt; returns a &lt;code dir=&quot;auto&quot;&gt;{ value, reason, errorCode }&lt;/code&gt; object. But the reason structure is different. LaunchDarkly’s
&lt;code dir=&quot;auto&quot;&gt;EvaluationReason&lt;/code&gt; includes &lt;code dir=&quot;auto&quot;&gt;ruleId&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ruleIndex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;bigSegmentsStatus&lt;/code&gt;, and
&lt;code dir=&quot;auto&quot;&gt;prerequisiteKey&lt;/code&gt;. OpenFeature’s &lt;code dir=&quot;auto&quot;&gt;ResolutionDetails&lt;/code&gt; uses a smaller vocabulary
(&lt;code dir=&quot;auto&quot;&gt;CACHED&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;DEFAULT&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ERROR&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;SPLIT&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;STATIC&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;TARGETING_MATCH&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;UNKNOWN&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Code that consumes &lt;code dir=&quot;auto&quot;&gt;detail.reason.kind === &apos;RULE_MATCH&apos;&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;detail.reason.ruleId&lt;/code&gt;
will need to be updated alongside the evaluation call. FlagLint cannot safely
generate that transformation because the consuming code varies too much.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Migrate these calls manually. For each &lt;code dir=&quot;auto&quot;&gt;variationDetail&lt;/code&gt; call:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replace the evaluation with &lt;code dir=&quot;auto&quot;&gt;getBooleanDetails()&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;getStringDetails()&lt;/code&gt; etc.&lt;/li&gt;
&lt;li&gt;Update any consumers of &lt;code dir=&quot;auto&quot;&gt;reason.kind&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;reason.ruleId&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;variationIndex&lt;/code&gt; to use OpenFeature’s reason vocabulary.&lt;/li&gt;
&lt;li&gt;Add tests that cover the specific reason codes your business logic depends on.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;detail&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariationDetail&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;show-banner&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (detail&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reason&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;RULE_MATCH&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;track&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;rule-matched&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;); }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;detail&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanDetails&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;show-banner&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (detail&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reason&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;TARGETING_MATCH&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;track&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;rule-matched&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;); }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-3--bulk-state-calls&quot;&gt;Pattern 3 — Bulk state calls&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Server-side bootstrap — sends all flag values to the client&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlagsState&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Common in SSR: inject all flags into the page for client-side hydration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;locals&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;flags&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenFeature has no equivalent to &lt;code dir=&quot;auto&quot;&gt;allFlags()&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;allFlagsState()&lt;/code&gt;. The OpenFeature
specification deliberately does not include bulk evaluation — providers are expected
to surface individual flags, and bulk retrieval is a vendor-specific concern.&lt;/p&gt;
&lt;p&gt;The LaunchDarkly OpenFeature provider does not expose &lt;code dir=&quot;auto&quot;&gt;allFlags&lt;/code&gt; through the
OpenFeature client interface. If your code relies on bulk evaluation to bootstrap a
client-side SDK or build a flag snapshot, that architecture requires rethinking, not
just rewriting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option A — Enumerate the flags explicitly.&lt;/strong&gt; If you know which flags are needed
at the injection point, evaluate them individually and bundle the results:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;FLAGS_TO_BOOTSTRAP&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;show-banner&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;new-pricing&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; as const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;flagSnapshot&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;Object&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromEntries&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;FLAGS_TO_BOOTSTRAP&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(key, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, ctx),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Option B — Use the LaunchDarkly provider directly for bootstrapping.&lt;/strong&gt; The
OpenFeature provider wraps the LD Node.js SDK. You can access the underlying client
for the specific bootstrap call while migrating all other evaluation calls to
OpenFeature:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { LaunchDarklyProvider } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@launchdarkly/openfeature-node-server&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LaunchDarklyProvider&lt;/span&gt;&lt;span&gt;(process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LD_SDK_KEY&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; OpenFeature&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setProviderAndWait&lt;/span&gt;&lt;span&gt;(provider);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Access the underlying LD client only for bulk bootstrap&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;(provider&lt;/span&gt;&lt;span&gt; as &lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getClient&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// type-cast needed; provider internals are not public API&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Option B is a transitional pattern. The goal is to eliminate it once you’ve
enumerated the flags that actually need bootstrapping.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-4--configured-wrappers&quot;&gt;Pattern 4 — Configured wrappers&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// A shared evaluation helper used across services&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isFeatureEnabled&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;LDContext&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(flagKey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ctx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Custom wrapper that adds logging and metrics&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;evaluateFlag&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LDContext&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;fallback&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;variation&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fallback);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;metrics&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;flag.evaluation&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; { key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; result });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FlagLint detects wrappers configured in &lt;code dir=&quot;auto&quot;&gt;.flaglintrc&lt;/code&gt; under the &lt;code dir=&quot;auto&quot;&gt;wrappers&lt;/code&gt; key.
When a wrapper is detected, the call is surfaced in the audit and scan output but
never auto-rewritten — because rewriting the call site does not solve the problem.
The wrapper itself contains the direct LD SDK call that needs to change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Migrate the wrapper implementation, not the call sites. The call sites stay the same;
only the internals of the wrapper change:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isFeatureEnabled&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;LDContext&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(flagKey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ctx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — wrapper now delegates to OpenFeature&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isFeatureEnabled&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;EvaluationContext&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(flagKey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;After the wrapper implementation is migrated, configure FlagLint to recognise the
wrapper’s result type so downstream &lt;code dir=&quot;auto&quot;&gt;scan&lt;/code&gt; output is accurate:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;wrappers&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;isFeatureEnabled&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;evaluateFlag&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Wrappers that accept a dynamic &lt;code dir=&quot;auto&quot;&gt;flagKey&lt;/code&gt; parameter will still appear in reports —
that is correct behaviour. The scanner surfaces them for the same reason it surfaces
dynamic keys: it cannot prove which flag is being evaluated.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-5--unknown-fallback-types-jsonvariation&quot;&gt;Pattern 5 — Unknown fallback types (&lt;code dir=&quot;auto&quot;&gt;jsonVariation&lt;/code&gt;)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Untyped JSON — fallback type is object but the shape is unknown&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;jsonVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;pricing-config&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, {}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Typed JSON with a complex or union shape&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;rules&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;jsonVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;routing-rules&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, { routes:&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenFeature’s equivalent is &lt;code dir=&quot;auto&quot;&gt;getObjectValue()&lt;/code&gt;, which returns &lt;code dir=&quot;auto&quot;&gt;JsonValue&lt;/code&gt; — a union
of &lt;code dir=&quot;auto&quot;&gt;string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }&lt;/code&gt;.
When the fallback is &lt;code dir=&quot;auto&quot;&gt;{}&lt;/code&gt; or another untyped object, FlagLint cannot determine the
correct generic type to use, and it cannot verify that the calling code handles the
&lt;code dir=&quot;auto&quot;&gt;JsonValue&lt;/code&gt; type correctly rather than a narrower application-specific type.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Add explicit type annotations to the fallback and the result, then migrate manually:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Define the expected shape&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; PricingConfig {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;basePrice&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;currency&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tiers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;; discount&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt; }[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PricingConfig&lt;/span&gt;&lt;span&gt; = { basePrice: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, currency: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;USD&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, tiers:&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;jsonVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;pricing-config&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getObjectValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;pricing-config&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; as &lt;/span&gt;&lt;span&gt;PricingConfig&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The explicit cast is safe because the provider returns whatever LaunchDarkly sends,
and the schema is defined in the LaunchDarkly dashboard. If the shape might not
match, add a runtime validator (Zod works well here) at the call site.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;seeing-the-full-breakdown-before-you-start&quot;&gt;Seeing the full breakdown before you start&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before migrating, run &lt;code dir=&quot;auto&quot;&gt;flaglint audit ./src&lt;/code&gt; to see how many calls fall into each
category and why:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Audit complete: 18 flags — 5 high risk, 13 medium risk&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Flag Key              | Risk   | Usages | Reasons                           |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|-----------------------|--------|--------|-----------------------------------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| &amp;#x3C;dynamic key&gt;         | High   | 9      | key cannot be resolved statically |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| checkout-experiment   | High   | 1      | detail evaluation                 |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| *                     | High   | 1      | bulk call                         |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| &amp;#x3C;wrappers&gt;            | High   | 4      | configured wrapper                |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| pricing-config        | Medium | 1      | json — unknown shape              |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| checkout-v2           | Medium | 1      | safely automatable                |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The five patterns above account for every high-risk category. Resolving them
one at a time — starting with wrappers, then dynamic keys — progressively reduces
the manual review surface until &lt;code dir=&quot;auto&quot;&gt;migrate --apply&lt;/code&gt; can handle the rest automatically.&lt;/p&gt;</content:encoded><category>launchdarkly</category><category>openfeature</category><category>migration</category><category>nodejs</category><category>devops</category></item><item><title>After the LaunchDarkly Outage: Adding a Vendor-Neutral Abstraction Without a Full Migration</title><link>https://flaglint.dev/blog/after-launchdarkly-outage-vendor-neutral-abstraction/</link><guid isPermaLink="true">https://flaglint.dev/blog/after-launchdarkly-outage-vendor-neutral-abstraction/</guid><pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A provider outage can expose how deeply application code depends on a single
feature-flag SDK. OpenFeature creates a neutral application boundary without
forcing teams to abandon LaunchDarkly.&lt;/p&gt;
&lt;p&gt;This article walks through the local audit, migration preview, and CI enforcement
path that lets teams add that boundary incrementally.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-vendor-lock-in-problem&quot;&gt;The Vendor Lock-In Problem&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Direct SDK calls look like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; LaunchDarkly &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;launchdarkly-node-server-sdk&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;LaunchDarkly&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;(process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LD_SDK_KEY&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Your application code is coupled to LaunchDarkly’s API surface.
Switching providers means rewriting every evaluation call.&lt;/p&gt;
&lt;p&gt;OpenFeature decouples this. Your application calls the OpenFeature API.
The provider (LaunchDarkly, or anything else) is a configuration detail.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-migration-that-stalls&quot;&gt;The Migration That Stalls&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most teams agree to add OpenFeature. Most migrations take 6+ weeks
and nearly break production at least once.&lt;/p&gt;
&lt;p&gt;The reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No inventory of where direct SDK calls live&lt;/li&gt;
&lt;li&gt;Argument-order differences cause subtle production bugs&lt;/li&gt;
&lt;li&gt;Phased migrations partially reverse when new engineers join&lt;/li&gt;
&lt;li&gt;No CI enforcement to prevent new direct calls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of those reasons is an API difference that breaks even careful rewrites — &lt;a href=&quot;https://flaglint.dev/blog/launchdarkly-openfeature-argument-order-bug/&quot;&gt;the argument-order trap between LaunchDarkly and OpenFeature →&lt;/a&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-flaglint-does&quot;&gt;What FlagLint Does&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before you can migrate, you need to know what you’re migrating.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scan&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;AST-based inventory of every direct LaunchDarkly SDK call.
File, line, call type, flag key, whether it’s safely automatable.&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--dry-run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Reviewable diffs for safe call sites. Argument order corrected.
Dynamic keys, detail methods, and bulk calls reported for manual review.&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-direct-launchdarkly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;CI gate. Fails the build if any new direct LD call appears.
The migration doesn’t rot.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;launchdarkly-stays-as-your-provider&quot;&gt;LaunchDarkly Stays As Your Provider&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is not a migration away from LaunchDarkly.&lt;/p&gt;
&lt;p&gt;LaunchDarkly offers an official OpenFeature provider. After the migration,
LaunchDarkly still evaluates your flags — you’re just calling the
OpenFeature API instead of the LaunchDarkly SDK directly.&lt;/p&gt;
&lt;p&gt;The difference: if you need to switch providers, you change the provider
configuration. Your application code doesn’t change.&lt;/p&gt;
&lt;p&gt;→ &lt;a href=&quot;https://flaglint.dev/docs/quickstart&quot;&gt;Start with the scan&lt;/a&gt;&lt;br&gt;
→ &lt;a href=&quot;https://github.com/flaglint/flaglint&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href=&quot;https://flaglint.dev/blog/launchdarkly-openfeature-argument-order-bug/&quot;&gt;Why LaunchDarkly → OpenFeature Migrations Break in Production →&lt;/a&gt;&lt;/p&gt;</content:encoded><category>launchdarkly</category><category>openfeature</category><category>vendor-lock-in</category><category>nodejs</category><category>devops</category></item><item><title>Why LaunchDarkly → OpenFeature Migrations Break in Production</title><link>https://flaglint.dev/blog/launchdarkly-openfeature-argument-order-bug/</link><guid isPermaLink="true">https://flaglint.dev/blog/launchdarkly-openfeature-argument-order-bug/</guid><pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;LaunchDarkly and OpenFeature both evaluate flags with three arguments, but the
fallback and context positions are reversed. A naive codemod can produce
valid-looking code that silently changes runtime behavior.&lt;/p&gt;
&lt;p&gt;This article shows the argument-order trap and why FlagLint uses AST analysis
before rewriting any call site.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-agreement-that-takes-30-minutes&quot;&gt;The Agreement That Takes 30 Minutes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Teams agree on OpenFeature quickly. It makes sense — vendor-neutral,
CNCF-backed, clean abstraction. The decision is easy.&lt;/p&gt;
&lt;p&gt;The migration is not.&lt;/p&gt;
&lt;p&gt;Halfway through, most teams hit a bug that looks like this in production:
a subset of users sees the wrong feature state. The flag evaluation
is returning unexpected values. Everything looked correct in code review.&lt;/p&gt;
&lt;p&gt;The cause is almost always the same thing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-argument-order-trap&quot;&gt;The Argument-Order Trap&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;LaunchDarkly and OpenFeature share method names but differ in argument order:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// LaunchDarkly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(flagKey, context, fallback)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// OpenFeature&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(flagKey, fallback, context)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;fallback&lt;/code&gt; are swapped.&lt;/p&gt;
&lt;p&gt;A search-and-replace migration silently puts &lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt; where &lt;code dir=&quot;auto&quot;&gt;fallback&lt;/code&gt;
should be, and &lt;code dir=&quot;auto&quot;&gt;fallback&lt;/code&gt; where &lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt; should be — across every call
site in your codebase.&lt;/p&gt;
&lt;p&gt;In production: users in your evaluation context see the fallback value.
In code review: the signature looks correct because the argument count matches.
In the post-mortem: nobody can identify when it was introduced.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-grep-misses-it&quot;&gt;Why Grep Misses It&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The typical manual approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Search for &lt;code dir=&quot;auto&quot;&gt;launchdarkly-node-server-sdk&lt;/code&gt; imports&lt;/li&gt;
&lt;li&gt;Find all &lt;code dir=&quot;auto&quot;&gt;ldClient&lt;/code&gt; references&lt;/li&gt;
&lt;li&gt;Search-and-replace method names&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This finds the calls. It does not understand argument semantics.&lt;/p&gt;
&lt;p&gt;A grep-based migration will correctly rename &lt;code dir=&quot;auto&quot;&gt;boolVariation&lt;/code&gt; to
&lt;code dir=&quot;auto&quot;&gt;getBooleanValue&lt;/code&gt; and miss the argument order entirely. The test suite
often misses it too because the values are both valid types — &lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt;
and &lt;code dir=&quot;auto&quot;&gt;fallback&lt;/code&gt; are both objects.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-ast-analysis-catches&quot;&gt;What AST Analysis Catches&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Abstract Syntax Tree (AST) analysis parses your code the same way a
compiler does. It doesn’t match text — it understands structure.&lt;/p&gt;
&lt;p&gt;For a call like:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;AST analysis identifies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The import binding (&lt;code dir=&quot;auto&quot;&gt;ldClient&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;launchdarkly-node-server-sdk&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The method name (&lt;code dir=&quot;auto&quot;&gt;boolVariation&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The argument at position 0: flag key (&lt;code dir=&quot;auto&quot;&gt;&apos;checkout-v2&apos;&lt;/code&gt; — string literal)&lt;/li&gt;
&lt;li&gt;The argument at position 1: context (&lt;code dir=&quot;auto&quot;&gt;ctx&lt;/code&gt; — object reference)&lt;/li&gt;
&lt;li&gt;The argument at position 2: fallback (&lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt; — boolean literal)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When generating the OpenFeature equivalent, it knows to produce:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Argument 2 goes to position 1. Argument 1 goes to position 2.
Argument order corrected. Type preserved. Await preserved.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-ast-analysis-refuses-to-rewrite&quot;&gt;When AST Analysis Refuses to Rewrite&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Not every call can be safely automated. FlagLint identifies these
and routes them to manual review instead of silently rewriting them:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dynamic flag keys:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;feature-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;featureName&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(flagKey, ctx, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The key is not statically knowable. Automated rewrite could produce
incorrect OpenFeature client binding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Detail methods:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariationDetail&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, ctx, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;boolVariationDetail&lt;/code&gt; returns metadata not directly equivalent in
OpenFeature. Requires a different migration pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bulk state calls:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlagsState&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;No direct OpenFeature equivalent. Architecture decision required.&lt;/p&gt;
&lt;p&gt;For all of these, FlagLint reports the location and reason —
it never silently rewrites them.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-ci-gate&quot;&gt;The CI Gate&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Generating diffs is only half the problem. Migration rot is the other half.&lt;/p&gt;
&lt;p&gt;After a phased migration, new engineers joining the codebase don’t
know the rule. They reach for &lt;code dir=&quot;auto&quot;&gt;ldClient&lt;/code&gt; because that’s what they know.
Six months later, you have new direct LD calls and the migration has
partially reversed.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;flaglint validate --no-direct-launchdarkly&lt;/code&gt; exits 1 if any direct
LaunchDarkly evaluation call appears outside the bootstrap file.&lt;/p&gt;
&lt;p&gt;Add it to your GitHub Actions workflow:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Enforce OpenFeature boundary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;npx flaglint@latest validate ./src --no-direct-launchdarkly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Any new &lt;code dir=&quot;auto&quot;&gt;ldClient.boolVariation()&lt;/code&gt; call fails the build. The boundary holds.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;try-it-now&quot;&gt;Try It Now&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Runs locally, no SDK key needed, nothing changes. Gives you a risk-ranked
inventory of every direct LaunchDarkly SDK call in your codebase —
dynamic keys, detail methods, and bulk state calls included — with a
migration readiness score.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--dry-run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Shows the before/after diff for every safely automatable call site.
Dynamic keys and detail methods are reported separately for manual review.&lt;/p&gt;
&lt;p&gt;FlagLint is free, open source, MIT licensed.&lt;/p&gt;
&lt;p&gt;→ &lt;a href=&quot;https://github.com/flaglint/flaglint&quot;&gt;GitHub&lt;/a&gt;&lt;br&gt;
→ &lt;a href=&quot;https://www.npmjs.com/package/flaglint&quot;&gt;npm&lt;/a&gt;&lt;br&gt;
→ &lt;a href=&quot;https://flaglint.dev/docs/quickstart&quot;&gt;Quickstart&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href=&quot;https://flaglint.dev/blog/after-launchdarkly-outage-vendor-neutral-abstraction/&quot;&gt;After the LaunchDarkly Outage: Adding a Vendor-Neutral Abstraction Without a Full Migration →&lt;/a&gt;&lt;/p&gt;</content:encoded><category>launchdarkly</category><category>openfeature</category><category>migration</category><category>nodejs</category><category>devops</category></item></channel></rss>