Automated Search & Navigation to Graph Migration with Claude Code
A Claude Code plugin that scans your S&N codebase, applies Graph SDK transformations, and validates the result. Install once, run one command.
TL;DR
- The cg-migrate Claude Code plugin automates the Search & Navigation to Graph SDK migration. It scans your .NET solution for every Find API usage, rewrites the code to use Optimizely.Graph.Cms.Query, and validates the result by building the project and attributing compiler errors back to the specific transformation that caused them.
- Five skills cover the full workflow: /migrate-all runs the entire pipeline end-to-end, or use the individual skills for more control: /migrate-scan discovers all S&N usage and classifies complexity, /migrate-transform rewrites the code, /migrate-validate checks the build, and /migrate-map handles one-off translations.
- It understands your codebase structure. The scan traces dependency graphs through wrapper classes and DI registrations, so indirect consumers of Find are identified too.
- You keep control over the hard decisions. When a pattern has no direct Graph equivalent (like UnifiedSearch or custom FilterForVisitor implementations), the plugin generates a decisions file and pauses for your input before continuing.
- Install with two commands and the plugin loads on every Claude Code session automatically.
1. Why This Plugin Exists
The Graph SDK blog post on Optimizely World covers how to write queries with Optimizely.Graph.Cms.Query. If you have not read it yet, start there. It walks through Where(), SearchFor(), Facet(), and the rest of the API that replaces Search & Navigation.
What the blog post does not cover is the mechanical work of actually migrating a real codebase. A typical Optimizely solution has Search & Navigation calls spread across dozens of files. Some are straightforward Search<T>().Filter().GetResult() chains. Others are wrapped behind repository classes, injected through DI containers, or built up conditionally with .OrFilter() composition. Finding all of that, understanding the dependency graph, and rewriting it file by file takes time that could be spent on the parts of a CMS 13 migration that genuinely require human judgment.
The cg-migrate plugin handles the mechanical part. It is a set of Claude Code skills that scan your solution, apply the known API mappings from the Graph SDK blog post, flag the patterns that need human decisions, and validate the result against the compiler. You stay in control of architecture questions. The plugin handles the find-and-replace at scale.
2. Getting Started
Prerequisites
You need Claude Code installed and a .NET solution that currently uses the Search & Navigation (EPiServer Find) SDK.
Before running the migration, check out a dedicated branch with no pending changes. The solution should build cleanly and all tests should pass. This gives the plugin a clean baseline so it can distinguish migration-introduced errors from pre-existing issues.
Installation
Run these two commands in Claude Code:
/plugin marketplace add episerver/content-graph-sdk-migrations /plugin install cg-migrate@content-graph-sdk-migrations
The plugin loads automatically on every subsequent Claude Code session. No configuration files to edit.
What Gets Installed
The plugin adds five slash commands to your Claude Code session:
| Command | Purpose |
|---|---|
| /migrate-all | Run the full migration pipeline end-to-end: scan, transform, validate, and fix |
| /migrate-scan | Discover all S&N usage across the solution and produce a migration manifest |
| /migrate-map | Translate a single file or code snippet from S&N to Graph SDK |
| /migrate-transform | Apply transformations to all files identified by the scan |
| /migrate-validate | Build the project and map each compiler error back to the transformation that caused it |
Three Ways to Use the Plugin
You do not have to use all five commands. Pick the approach that fits where you are in your migration:
| Approach | Commands | When to use |
|---|---|---|
| Full pipeline | /migrate-all | You want to migrate the entire solution end-to-end in one run. The command handles scan, transform, validate, and build-fix phases automatically, pausing only for manual decisions. |
| Step by step | /migrate-scan → /migrate-transform → /migrate-validate | You want to review the scan report before transforming, re-run a single phase after making manual edits, or iterate on specific files without restarting the full pipeline. |
| One-off translation | /migrate-map | You want to translate a single file or code snippet, understand how a specific S&N pattern maps to the Graph SDK, or handle a file individually before or after a larger migration run. |
The rest of this post covers each approach in detail. If you just want to get started, skip to section 3 for the full pipeline or section 5 for one-off translations.
3. Running the Full Migration
The fastest way to migrate is /migrate-all. It orchestrates the entire pipeline in a single invocation:
/migrate-all --project path/to/YourSolution.sln
If you omit --project, the plugin searches for .sln files in the current directory.
What Happens
The command runs six phases sequentially:
Phase 1: Scan. Invokes /migrate-scan to discover all Find SDK usage, trace dependency graphs, and classify each pattern by complexity. If no migration targets are found, the command stops early.
Phase 2: Confirm. Presents a summary of what will be transformed automatically and what will require manual decisions, then pauses for your confirmation before making any changes.
Phase 3: Transform. Invokes /migrate-transform to apply all known API mappings, swap NuGet packages, and flag items for manual review.
Phase 4: Validate. Invokes /migrate-validate to build the project and attribute compiler errors to specific transformation rules. If errors are found, it automatically retries the transformation on the failed files for a second pass.
Phase 5: Build & Fix. Rebuilds the project and iteratively fixes remaining compiler errors that have clear automated solutions, such as CS0266 type mismatches, CS1061 missing members, CS0246 unknown types, and NuGet version conflicts. Errors that require business logic decisions are deferred for you to handle manually.
Phase 6: Report. Generates a comprehensive migration report at .cg-migrations/migrate-all/ that ties together the results from every phase: targets scanned, files transformed, errors fixed, and remaining manual items with prioritized next steps.
The final report classifies the migration status as Complete (all errors resolved), Partial (some errors remain), or Nothing to migrate (no targets found).
Use /migrate-all for the typical workflow. The individual skills (/migrate-scan, /migrate-transform, /migrate-validate) are still available when you need to re-run a specific phase or want more control over the process.
4. Scanning Your Codebase
If you want to inspect the migration scope before committing to a full run, or if you need to re-run just the scan phase, use /migrate-scan directly:
/migrate-scan --project path/to/YourSolution.sln
If you omit --project, the plugin searches for .sln files in the current directory.
The scan runs in four phases:
Phase 1: NuGet Detection. The scan parses .csproj files to find which projects reference EPiServer.Find packages and what versions they use.
Phase 2: Direct Reference Scan. Using regex patterns from a built-in registry of 50+ S&N API patterns, the scan locates every file that calls into the Find SDK. Each match is categorized by type (filtering, facets, sorting, search, DI registration, etc.).
Phase 3: Dependency Graph Tracing. This is where the scan goes beyond simple text search. It follows constructor injection, DI registrations, and wrapper class hierarchies up to 5 hops deep. If you have a SearchService that wraps IClient and a SearchController that injects SearchService, both get flagged as migration targets even though only SearchService contains direct Find API calls.
Phase 4: Complexity Classification. Every identified pattern gets one of three labels:
| Classification | Meaning | Example |
|---|---|---|
| direct-swap | 1:1 Graph SDK equivalent exists | .Filter() to .Where(), .Take() to .Limit() |
| pattern-rewrite | Needs restructuring but the approach is known | .OrFilter() composition to BuildFilter<T>().Or() |
| manual | No automated equivalent, needs human decision | UnifiedSearch, custom FilterForVisitor |
Scan Output
The scan produces a console summary with statistics and a detailed markdown report at .cg-migrations/migrate-scan/scan-report.md. The report includes every target file, the specific patterns found, the dependency tree, and the complexity breakdown.
Add .cg-migrations/ to your .gitignore. This directory is for migration artifacts and should not be committed.
5. Translating Individual Queries
For one-off translations or to understand how a specific pattern maps to the Graph SDK, use /migrate-map:
/migrate-map
Point it at a file or paste a code snippet. The skill applies 17 translation rules covering the full S&N API surface.
Before and After
var result = searchClient.Search<ArticlePage>() .For("chocolate cake") .InField(x => x.Title, 3.0) .InField(x => x.Summary, 2.0) .Filter(x => x.Category.Match("Dessert")) .Filter(x => x.IsPublished.Match(true)) .TermsFacetFor(x => x.Category) .OrderByDescending(x => x.PublishDate) .Take(10) .Skip(20) .GetResult();
var result = await _graphClient .QueryContent<ArticlePage>() .SearchFor("chocolate cake") .UsingField(x => x.Title, boost: 3) .UsingField(x => x.Summary, boost: 2) .Where(x => x.Category == "Dessert" && x.IsPublished) .Facet(x => x.Category) .OrderBy(x => x.PublishDate, OrderDirection.Descending) .Limit(10) .Skip(20) .IncludeTotal() .GetAsContentAsync();
The /migrate-map skill is useful for building intuition about the mapping rules before running a full transformation, and for handling files you want to review individually.
6. Transforming the Full Codebase
Once you have reviewed the scan report and understand the scope, run the full transformation:
/migrate-transform --project path/to/YourSolution.sln
You can optionally pass the scan report path if you have already run /migrate-scan:
/migrate-transform --project path/to/YourSolution.sln --scan-report .cg-migrations/migrate-scan/scan-report.md
How the Transform Works
The transform runs in six phases:
Phase 1: Decision Resolution. For every pattern classified as manual in the scan, the transform generates a decisions file at .cg-migrations/migrate-decisions.md. This file groups patterns and presents strategy options. The plugin pauses here and asks you to choose a strategy for each group before continuing.
Example decision groups and strategies:
| Pattern Group | Suggested Strategy |
|---|---|
| UnifiedSearch (searches across multiple content types) | type-specific-queries (create separate QueryContent<T>() per type) |
| ContentIndexer / .Track() calls | remove (comment out, no Graph equivalent for direct indexing) |
| Custom site/visitor filters | custom-implementation (you provide the replacement logic) |
| .UsingSynonyms() | per-filter-synonym (move to .Match("value", SynonymSlot.One)) |
Phase 2: NuGet Package Setup. Adds Optimizely.Graph.Cms.Query to the relevant projects and tracks the S&N packages for removal.
Phase 3: Code Transformation. Each file gets 13 transformation steps applied in sequence: namespace updates, DI registration changes, query entry points, full-text search, filtering, sorting, pagination, facets, projections, execution (all made async), result access patterns, manual pattern strategies, and cleanup of orphaned using statements.
Phase 4: Cleanup. Removes using EPiServer.Find statements that are no longer needed.
Phase 5: Build Validation. Runs dotnet build and auto-fixes common issues like NuGet conflicts and generic constraint mismatches (the Graph SDK requires where T : class, IContentData rather than just IContentData). Iterates up to 2 cycles.
Phase 6: Reporting. Generates a transform report at .cg-migrations/migrate-transform/transform-report.md with per-file results.
If you run /migrate-transform again, it reads your previous decisions. You can edit the file directly to change strategies for specific patterns or files.
7. Validating the Result
After the transform completes, run validation:
/migrate-validate --project path/to/YourSolution.sln
The validation phase does more than just run dotnet build. It parses every compiler error, matches it against a built-in lookup table of known migration-related error signatures, and attributes each failure to the specific transformation rule that likely caused it.
Validation Output
The report separates errors into three buckets:
| Bucket | Meaning |
|---|---|
| Migration-caused (high confidence) | File was transformed AND error matches a known migration pattern |
| Possibly migration-related (medium confidence) | Error matches a known cause but the file was not directly transformed |
| Pre-existing / out-of-scope | Error does not match any migration pattern |
Each error includes the file path, line number, error code, message, and the likely cause with a suggested fix.
Example Error Attribution
CS1061 in SearchService.cs:42 'IContentQuery<ArticlePage>' does not contain a definition for 'TotalMatching' Likely cause: Rule 11 (Result Access) Suggested fix: Add .IncludeTotal() to the query chain and access result.Total
After reporting, the validation skill offers to re-run /migrate-transform on just the failed files for a second pass (max 2 rounds). This catches cases where the first pass produced valid but incomplete transformations.
8. Full Workflow at a Glance
# 1. Check out a clean branch git checkout -b feature/graph-migration # 2. Run the full migration /migrate-all --project src/MySolution.sln # 3. Make decisions for manual patterns when prompted # 4. Review the migration report # (open .cg-migrations/migrate-all/*-migration-report.md) # 5. Fix any remaining manual items and commit
That is the simplest path. If you need more control, the individual skills (/migrate-scan, /migrate-transform, /migrate-validate) can be run separately and repeated as needed. The decisions file and scan report persist between runs, so you can iterate without losing your choices.
9. What the Plugin Handles vs. What You Handle
| The plugin handles | You handle |
|---|---|
| Finding every S&N API call across the solution | Reviewing the scan report for completeness |
| Tracing wrapper classes and DI dependency chains | Deciding strategies for manual patterns |
| Applying the 17 known API translation rules | Custom filter/visitor logic that has no Graph equivalent |
| Making methods async when execution changes to GetAsync() / GetAsContentAsync() | Business logic changes (e.g., if UnifiedSearch was used to merge results, you decide how to restructure) |
| Adding generic constraints (class, IContentData) | Integration test updates |
| Attributing compiler errors to specific transformation rules | Verifying that query results are functionally equivalent |
| NuGet package additions | NuGet package removals (tracked but left for you to confirm) |
Looking Ahead
The plugin's pattern registry will continue to grow as partners encounter additional S&N patterns during real-world migrations.
If you run into a Search & Navigation pattern that the plugin does not handle, the validation report will call it out. File an issue on the GitHub repository at episerver/content-graph-sdk-migrations so the pattern can be added to the registry.
Next Steps
- Install the plugin with /plugin marketplace add episerver/content-graph-sdk-migrations
- Run /migrate-all on your solution to migrate end-to-end, or start with /migrate-scan to preview the scope first
- Review the migration report and fix any remaining manual items
- Read the Graph SDK blog post to understand the API you are migrating to
Resources:
- Introducing the Optimizely CMS 13 Graph SDK (the companion blog post covering the Graph SDK API)
- Plugin Repository: episerver/content-graph-sdk-migrations
Running /migrate-scan is read-only. It does not modify any files. You can run it against any branch at any time to understand the scope of the migration before committing to the transformation.
This guide is based on the cg-migrate Claude Code plugin for migrating Search & Navigation to the Optimizely Graph SDK. The plugin is actively maintained and the pattern registry grows with each release.
Comments