Install
$ agentstack add skill-kentico-xperience-by-kentico-kenticopilot-migrate-content-items ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Content Item Director Code Generation
Produces ready-to-use C# code files for the Migration.Tool.Extensions project. Takes the migration plan output from the migrate-plan skill — or a direct text description — as input.
Workflow
Step 1: Read Reference Materials
- Read [content-item-director-api.md](references/content-item-director-api.md) for the complete API patterns, method signatures, and annotated code samples.
- If you need pattern examples for implementation, read [CONTENTITEMDIRECTOREXAMPLE.cs](assets/CONTENTITEMDIRECTOREXAMPLE.cs) for a complete annotated reference implementation showing all patterns.
- If you need context on the migration tool's extension points or configuration, read [migration-tool.md](../_shared/references/migration-tool.md).
- If you need documentation links, read [migration-docs.md](../_shared/references/migration-docs.md).
- If a Kentico documentation lookup tool is available, use it for additional context on content item directors, linked page handling, or page-to-widget conversion.
Step 2: Analyze Input
- If a migration plan file path is provided → read it and extract from the Page Relationship Handling section (linked pages, child-as-ancestor references, pages-to-widgets, template overrides) and the Code Extensions to Implement section (rows where Type =
ContentItemDirectorBase). - If a direct text description is provided → identify linked page strategies, page-to-widget conversions, child page linking, and template override needs.
- Audit linked pages — check the plan's Linked Page Handling or Linked Page Audit section first:
- If the plan explicitly states no linked pages exist (e.g., "No linked pages exist" or "NodeLinkedNodeID is null for all pages"), trust the plan and skip the database query. No
DirectLinkedNodeoverride is needed. - If the plan includes a linked page audit with specific rows, use those rows directly — do not re-query the database for confirmation.
- Only query the KX13 database when the plan does not mention linked pages at all (section missing or silent on the topic):
``sql SELECT t.NodeAliasPath, t.ClassName, t.NodeLinkedNodeID, t2.NodeAliasPath AS LinkedToPath, t2.ClassName AS LinkedToClass FROM CMS_Tree t JOIN CMS_Tree t2 ON t.NodeLinkedNodeID = t2.NodeID WHERE t.NodeLinkedNodeID IS NOT NULL ``
- When linked pages do exist, every one MUST have an explicit handling directive. The default behavior (Materialize) creates duplicate content items silently.
- Ask clarifying questions if widget placement details (editable area name, section type), ancestor levels, or conditional logic scope are ambiguous.
Step 3: Identify Directors to Generate
Determine the set of director classes needed:
- Group by logical concern, not by class name. One director can handle multiple source classes via conditional logic (switch/if on
SourceClassNameorNodeAliasPath). - Only override
Direct()for content item behavior (LinkChildren, AsWidget, OverridePageTemplate, Drop). - Only override
DirectLinkedNode()for linked page behavior (Materialize, Drop, StoreReferenceInAncestor). - A single director can override both methods when the concerns are related.
- One
ServiceCollectionExtensionsclass (or per-director extension methods) for DI registration. - Always generate a linked page director when linked pages exist in the source, even if the migration plan does not explicitly mention them. Unhandled linked pages are the most common source of duplicate content items.
- Always generate Drop directives for utility pages — pages under paths like
/Reused-content/,/Error-pages/,/System/, or similar utility paths should be explicitly dropped unless the plan provides a different mapping. These pages, if not dropped, will be migrated using their source class's default mapping, which often produces incorrect results (e.g., an error page mapped as a ContactPage). - Skip director generation for classes where the default migration behavior is sufficient.
Step 4: Generate Director Code
For each director, generate a class inheriting from ContentItemDirectorBase:
- Override
Direct(ContentItemSource source, IContentItemActionProvider options)for content item directives. - Override
DirectLinkedNode(LinkedPageSource source, ILinkedPageActionProvider options)for linked page directives. - Use
switch/ifonsource.SourceClassName,source.SourceNode.NodeAliasPath, orsource.LinkedNode.NodeClassID/source.LinkedNode.NodeAliasPathfor conditional logic. - Call
options.LinkChildren(fieldName, filteredChildren)for child page linking — always include aWherefilter. - Call
options.AsWidget(widgetIdentifier, null, null, opts => { ... })for page-to-widget conversion — always specify the full location chain:OnAncestorPage().InEditableArea().InSection().InFirstZone(). - Call
options.OverridePageTemplate(templateIdentifier)for template overrides. - Call
options.StoreReferenceInAncestor(level, fieldName)for linked page reference storage — verify ancestor level from the page tree structure. Important: the ancestor content type must support content item reference fields; see theStoreReferenceInAncestorrule below. - Call
options.Materialize()to create independent copies of linked pages. - Call
options.Drop()to skip migration of specific pages. - For
DirectLinkedNode(): if the plan specifiesstore_referenceordropfor specific linked page paths, use path-based or class-based matching. For all other linked pages not explicitly handled, default tooptions.Drop()rather than allowing implicitMaterialize()— duplicated content is harder to clean up than missing content that can be re-migrated with a different strategy. - For
Direct(): include explicitoptions.Drop()for utility/error page paths that should not be migrated. Use path prefix matching:source.SourceNode.NodeAliasPath.StartsWith("/Reused-content/", StringComparison.OrdinalIgnoreCase). Direct()is abstract — do NOT callbase.Direct(source, options)(causesCS0205: Cannot call an abstract base member). For the default case inDirect(), either leave the case empty (no action = default migration behavior) or call a specific action.DirectLinkedNode()is virtual — callingbase.DirectLinkedNode(source, options)in the default case is valid and preserves standard linked page behavior (Materialize).
Step 5: Generate Service Registration
Generate service registration using the per-director extension method pattern:
services.AddTransient()for each director — NOTAddSingleton.- One extension method per director file.
- Include prerequisite comments (e.g., which
appsettings.jsonsettings orIClassMappingregistrations are required).
Step 6: Build Verification
- Build the
Migration.Tool.Extensionsproject to verify the generated code compiles without errors. - If the build fails, analyze the error messages, fix all issues in the generated code, and rebuild.
- Repeat up to 3 attempts. If the build still fails after 3 attempts, present the full build output and error details to the user for manual resolution.
6b. Verify Linked Page Audit Completeness
Skip this sub-step if the migration plan confirmed no linked pages exist. Otherwise, after a successful build:
- Re-read the linked page data from the migration plan (or from the KX13 query results if a query was needed in Step 2).
- Walk through the generated
DirectLinkedNode()override and confirm every linked page has an explicit handling directive (Drop,Materialize, orStoreReferenceInAncestor). Match bySourceClassNameand/orNodeAliasPath. - If any linked pages are not covered by the conditional logic (and would fall through to
base.DirectLinkedNode()which defaults to Materialize), warn that these will be silently duplicated. Recommend adding explicitoptions.Drop()for them. - Verify all director registrations use
AddTransient()— NOTAddSingleton.
Step 7: Present and Refine
- Save files to the user-specified path (default:
Migration.Tool.Extensions/ContentItemDirectors/— generated code belongs in theMigration.Tool.Extensionsproject, matching theMigration.Tool.Extensions.ContentItemDirectorsnamespace). - Provide a summary table: file name → purpose → what it handles.
- List prerequisites (required appsettings, class mappings, XbyK page templates).
- Ask if any directors need adjustment and iterate on feedback.
Rules
- Follow exact API from
migrate-content-items-api.md— do not invent methods that don't exist. - One director per logical concern — use conditional logic inside for multiple classes.
- Every director subclass must have a corresponding
AddTransientregistration (NOTAddSingletonorAddScoped). - Never call
base.Direct()— it is abstract and will not compile. For unhandled cases inDirect(), leave the default branch empty (no action = default migration behavior). - Call
base.DirectLinkedNode()for unhandled cases inDirectLinkedNode()only — it is virtual and defaults to Materialize. - Handle both structured (migration plan) and free-text input.
- This skill generates
ContentItemDirectorBasecode only —IClassMapping,IFieldMigration,IWidgetMigration, andIWidgetPropertyMigrationare separate extension points not covered here. - Do not ask the user about taxonomy GUIDs, tag GUIDs, or lookup dictionary values. The migration plan is the single source of truth for these values — they are resolved at plan creation time and consumed by the
migrate-classesskill at code generation time. If you encounter taxonomy references while reading the migration plan, treat them as informational context for understanding the data model, not something this skill needs to resolve or query for. - For
StoreReferenceInAncestor: verify ancestor level from the page tree (-1= parent,-2= grandparent). Account for non-migrated intermediate types likeCMS.Folder. - For
AsWidget: always specify the full location chain —OnAncestorPage().InEditableArea().InSection().InFirstZone(). - For
LinkChildren: always include aWherefilter onsource.ChildNodesto select appropriate child types. - Namespace:
Migration.Tool.Extensions.ContentItemDirectors(user can override). - File naming convention:
{ConcernName}Director.cs. - Use string constants for class names and field names, following the
Source_/Target_prefix convention from the example. - Add
TODOcomments for values unknown at generation time (e.g., widget type identifiers, editable area names, section identifiers). Class IDs should be available from the migration plan's page type table; only addTODOif genuinely missing. - Do not regenerate
appsettings.jsonor class mapping code — onlyContentItemDirectorBasecode. - If a Kentico documentation lookup tool is available, verify uncertain API details before generating code.
- After generating code, always build the
Migration.Tool.Extensionsproject and fix any compilation errors before considering the task complete.
Gotchas
- Default linked pages to Drop — unless the migration plan explicitly requests Materialize or StoreReference for a linked page, generate
options.Drop()for it inDirectLinkedNode(). This prevents silent content duplication. Add a comment explaining the decision so the user can switch to Materialize if needed. - Handle utility paths explicitly — pages under system/utility paths (
/Reused-content/,/Error-pages/,/System/, etc.) must have explicitDirect()handling (typicallyoptions.Drop()). Never let them fall through to default migration, which maps them based on their source class name and produces unexpected results. - Query KX13 for linked pages only when needed — if the migration plan explicitly states no linked pages exist or includes a complete linked page audit, trust the plan and skip the database query. Only query the KX13 database when the plan is silent on linked pages (no Linked Page Handling/Audit section). Do not re-query for confirmation when the plan already covers it.
Direct()is abstract, not virtual —ContentItemDirectorBase.Direct()is declaredabstract. Callingbase.Direct(source, options)produces compiler errorCS0205: Cannot call an abstract base member. In the default/fallback branch of yourDirect()override, simply do nothing (no action = standard migration behavior). By contrast,DirectLinkedNode()isvirtual, sobase.DirectLinkedNode(source, options)works fine.ICmsTreedoes not haveSourceClassName— child nodes fromsource.ChildNodesand linked nodes fromsource.LinkedNodeareICmsTreeobjects.ICmsTreeexposesNodeClassID(int),NodeAliasPath,NodeName,NodeGUID, etc. — but NOTSourceClassName. TheSourceClassNameproperty exists only on the top-levelContentItemSourceandLinkedPageSourcerecords. When filtering child nodes inLinkChildren, useNodeClassID(e.g.,.Where(c => c.NodeClassID == cafeClassId)). Read class IDs from the migration plan's page type table (ClassID column). Only query theCMS_Classtable if the plan does not include class IDs.ConvertClassesToContentHubmust list every child type used inLinkChildren— if a child content type referenced in aLinkChildrencall is not included in theConvertClassesToContentHubappsettings list, the linking silently fails (no error, no reference created). When generating prerequisite comments on the director or its registration method, explicitly list every child type that must appear inConvertClassesToContentHub. When presenting the summary, cross-check the plan'sConvertClassesToContentHubvalue against all child types used inLinkChildrencalls and warn if any are missing.StoreReferenceInAncestorrequires a compatible ancestor — the ancestor content type must support content item reference fields. If the ancestor is a webpage content type that does not already have a content item reference field defined (viaIClassMappingWithoutSource("contentitemreference")or by the tool auto-creating it),StoreReferenceInAncestorwill fail with "Content item is not reusable, but specifies linked children". Before generatingStoreReferenceInAncestor, verify that the ancestor content type will have the reference field — check the correspondingIClassMappingfor aWithoutSource("contentitemreference")field matching thefieldNameparameter, or confirm the tool will auto-create it.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Kentico
- Source: Kentico/xperience-by-kentico-kenticopilot
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.