All AEM as a Cloud Service skills: component development, Workflow model design, development, triggering, launchers, debugging, and triaging, Dispatcher config authoring, advisory, incident response, performance tuning, and security hardening, Rapid Development Environment (RDE) deployment, log inspection, snapshots, and troubleshooting via `aio aem rde`, Jackrabbit Oak query/index tuning (property, Lucene index definitions), and extracting/inventorying JCR queries (XPath, SQL2, QueryBuilder) across a codebase.
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Generates rules for AEM Modernize Tools: structure rewrite (static → editable template pages), component rewrite (parsys → responsive grid), policy import (/etc/designs/<name> → /conf/.../policies/). Plus the one-per-project service configs and repoinit.
Inputs come from the confirmed .migration/template-context.yml — see template-modernization-context.md. Each rule type runs independently for plan rows where its column is true. A row with Create editable? also true runs the editable generator first, same pass. Do not invent values here; if context still has needs-user-confirm / missing, stop.
Generates the three types of rules consumed by the AEM Modernize Tools package (com.adobe.aem.aem-modernize-tools):
| Rule Type | What it converts | Output location |
|---|---|---|
| Structure Rewrite Rules | Static template pages → editable template pages | ui.apps/.../modernization/structure-rewrite-rules/ + ui.config/.../osgiconfig/config.author/ |
| Component Rewrite Rules | Legacy parsys nodes → responsive grid containers | ui.apps/.../modernization/component-rewrite-rules/ |
| Policy Import Rules | /etc/designs/<design> → /conf/.../policies/ | ui.apps/.../modernization/policy-import-rules/ |
Each rule type is independent. The user may ask for one, two, or all three in a session.
STOP. Before generating any file, ensure the confirmed context covers:
/apps/<appId>/ target and matching ui.apps / ui.config / ui.content paths./conf.modernization/structure-rewrite-rules/.modernization/component-rewrite-rules/./etc/designs/ references and design names used by the project./conf/.../settings/wcm/policies/ trees per app.com.adobe.aem.modernize.*.cfg.json files already present.RepositoryInitializer-aem-modernize.cfg.json already exists.If any required field is ambiguous, unresolved, or still marked needs-user-confirm / missing, stop and resolve it before writing files.
Converts pages that use a static template to use an editable template. Requires two artifacts per template: an XML rule node (deployed via ui.apps) and an OSGi factory config (deployed via ui.config).
File path pattern:
ui.apps/src/main/content/jcr_root/apps/<appId>/modernization/structure-rewrite-rules/<templateName>.xmlMinimal format (use for templates with no special container or component handling):
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Convert <appId> <templateName> static template to editable template"
staticTemplate="/apps/<appId>/templates/<templateName>"
editableTemplate="/conf/<appId>/settings/wcm/templates/<templateName>"/>Rules:
jcr:primaryType is always nt:unstructuredstaticTemplate = full JCR path to the existing static template nodeeditableTemplate = full JCR path to the already-created editable template under /conf.xml) must match the template name — used as the node name when installedFile path pattern:
ui.config/src/main/content/jcr_root/apps/<appId>/osgiconfig/config.author/
com.adobe.aem.modernize.structure.rule.PageRewriteRule-<appId>-<templateName>.cfg.jsonMinimal config (templates with a simple single container, no special handling):
{
"static.template": "/apps/<appId>/templates/<templateName>",
"sling.resourceType": "<appId>/components/structure/<templateName>",
"editable.template": "/conf/<appId>/settings/wcm/templates/<templateName>",
"container.resourceType": "wcm/foundation/components/responsivegrid"
}Optional properties — add only when discovered in the project:
All four properties operate on JCR node names — the names of the child nodes directly under the static page's
jcr:content(e.g.par,rightpar,header,image), notsling:resourceTypevalues.PageRewriteRulematches each child bynode.getName(). Listing a resourceType here matches nothing (silent no-op) forignore/order/rename, and forremoveremoves nothing while leaving the dead node in place. Read the actual child node names from the static template'sjcr:content/ a sampled/contentpage before populating any of these.
| Property | Type | When to add |
|---|---|---|
ignore.components | String[] | Node names of children that must remain on the page's jcr:content root (not moved into the new responsive grid) — e.g. targeting, LiveSyncConfig, fixed runtime nodes. List the node names. |
rename.components | String[] | When a child node must be renamed as it is moved into the root grid. Format: "oldName=newName" per entry (node names; the rename also supports intermediate-path targets like "par=container/container"). |
order.components | String[] | Node names, in the desired final order inside the root container. Any found-but-unspecified children are appended in arbitrary order. |
remove.components | String[] | Node names of children to delete from page content during conversion — e.g. fixed structural nodes (logo, nav, header) no longer rendered by the editable template's page component. Destructive: each named node is removed from the page. List node names, never resourceTypes — a wrong/missing name silently removes the wrong node or nothing. |
How to determine sling.resourceType:
/apps/<appId>/components/structure/<templateName>/sling.resourceType value is <appId>/components/structure/<templateName>ui.appsHow to determine container.resourceType:
wcm/foundation/components/responsivegridHow to detect ignore.components:
ui.apps/…/components/structure/<templateName>/sling:include / data-sly-resource calls that reference non-content nodes (e.g. targeting, LiveSyncConfig, header/footer fixed zones)jcr:content child name, e.g. targeting) and list it — not its resourceTypejcr:content rather than swept into the gridHow to detect rename.components:
par in the static template but the editable template expects responsivegrid, add "par=responsivegrid" to rename.componentsRequired once per app. Controls which folder the StructureRewriteRuleService scans for rule nodes.
File path:
ui.config/src/main/content/jcr_root/apps/<appId>/osgiconfig/config.author/
com.adobe.aem.modernize.structure.impl.StructureRewriteRuleServiceImpl.cfg.json{
"search.paths": [
"/apps/<appId>/modernization/structure-rewrite-rules"
]
}PID note: The OSGi component is StructureRewriteRuleServiceImpl (the @Component lives on the impl class, with no explicit name=), so the configuration PID — and therefore the .cfg.json filename — is the impl class's fully-qualified name (…structure.impl.StructureRewriteRuleServiceImpl). A config named after the interface (…structure.StructureRewriteRuleService) is not picked up: the service runs with empty search.paths and no rule folder is scanned.
Note: If multiple apps share rules, add all paths to the array. Only one config file is needed — it covers all apps.
Each new folder under modernization/ needs a .content.xml to set the JCR node type:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
jcr:primaryType="sling:Folder"
jcr:title="<AppId> Structure Rewrite Rules"/>Create .content.xml for modernization/ itself and for modernization/structure-rewrite-rules/ if they don't already exist.
Converts legacy wcm/foundation/components/parsys nodes in page content to responsive grid containers. This runs on content (not on templates) via the AEM Modernize Tools UI.
File path pattern:
ui.apps/src/main/content/jcr_root/apps/<appId>/modernization/component-rewrite-rules/<ruleName>.xmlA typical rule name is parsys-to-container.
Format:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Convert wcm parsys to <appId> responsive grid container"
sling:resourceType="wcm/foundation/components/parsys">
<patterns
jcr:primaryType="nt:unstructured">
<parsys
jcr:primaryType="nt:unstructured"
sling:resourceType="wcm/foundation/components/parsys"/>
</patterns>
<replacement
jcr:primaryType="nt:unstructured">
<container
jcr:primaryType="nt:unstructured"
sling:resourceType="<appId>/components/content/container"
layout="responsiveGrid"
cq:copyChildren="{Boolean}true"/>
</replacement>
</jcr:root>Rules:
sling:resourceType on the root node = the source type being matched (always wcm/foundation/components/parsys)<patterns>/<parsys> = the node pattern to match. Use sling:resourceType="wcm/foundation/components/parsys" to match any parsys<replacement>/<container> = the output node. sling:resourceType here is the app's content container, not the WCM gridcq:copyChildren="{Boolean}true" — always include this to preserve existing child content components inside the converted containercontainer in the example) is arbitrary — the actual JCR node name is preserved from the sourceHow to determine the replacement sling:resourceType:
<appId>/components/content/containerwcm/foundation/components/responsivegrid or core/wcm/components/container in the appsling:resourceTypeMultiple apps: Create one rule file per app under each app's own modernization/component-rewrite-rules/ folder. Register both search paths in the service config (see B2).
File path:
ui.config/src/main/content/jcr_root/apps/<appId>/osgiconfig/config.author/
com.adobe.aem.modernize.component.impl.ComponentRewriteRuleServiceImpl.cfg.json{
"search.paths": [
"/apps/<appId>/modernization/component-rewrite-rules"
]
}Multiple apps — a single config file can list multiple search paths:
{
"search.paths": [
"/apps/<appId1>/modernization/component-rewrite-rules",
"/apps/<appId2>/modernization/component-rewrite-rules"
]
}PID note: As with the structure service, the @Component is on the impl class (…component.impl.ComponentRewriteRuleServiceImpl) with no explicit name=, so that impl FQN is the configuration PID and the .cfg.json filename. A config named after the interface (…component.ComponentRewriteRuleService) is inert. If a legacy project already carries the interface-named config, migrate its search.paths onto the impl PID and remove the inert one.
Same pattern as A4 — create .content.xml for modernization/component-rewrite-rules/ if absent:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
jcr:primaryType="sling:Folder"
jcr:title="<AppId> Component Rewrite Rules"/>Maps legacy /etc/designs/<designName> to the new /conf/<appId>/settings/wcm/policies/ tree. Required when pages use design dialogs or when the modernization tool needs a design-to-policy mapping to import dialog values.
File path pattern:
ui.apps/src/main/content/jcr_root/apps/<appId>/modernization/policy-import-rules/<designName>.xmlFormat:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Map <designName> to conf policy tree"
design="/etc/designs/<designName>"
policyPath="/conf/<appId>/settings/wcm/policies/<appId>"/>Rules:
design = the full JCR path to the existing design node under /etc/designs/policyPath = the target path under /conf where policies will be created/importedWhen to skip policy import rules:
/etc/designs is not used at all in the project, skip this sub-path entirelyOne config file — same rule as structure and component: the @Component is on the impl class with no explicit name=, so the configuration PID is the impl FQN.
File path:
ui.config/.../osgiconfig/config.author/
com.adobe.aem.modernize.policy.impl.PolicyImportRuleServiceImpl.cfg.json{
"search.paths": [
"/apps/<appId>/modernization/policy-import-rules"
]
}PID note: Do not also create an interface-named …policy.PolicyImportRuleService.cfg.json — the interface is not a @Component, so that file is inert. If a legacy project already has one, move its search.paths onto the impl PID and delete the interface-named file.
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
jcr:primaryType="sling:Folder"
jcr:title="<AppId> Policy Import Rules"/>The AEM Modernize Tools store job tracking data under /var/aem-modernize/. This path must be initialized by repoinit before any jobs can run.
Check first: Glob **/osgiconfig/config.author/org.apache.sling.jcr.repoinit.RepositoryInitializer-aem-modernize.cfg.json. If it exists, skip.
If absent, create:
ui.config/.../osgiconfig/config.author/
org.apache.sling.jcr.repoinit.RepositoryInitializer-aem-modernize.cfg.json{
"scripts": [
"create path (sling:Folder) /var/aem-modernize\ncreate path (sling:Folder) /var/aem-modernize/job-data\ncreate path (sling:Folder) /var/aem-modernize/job-data/structure\ncreate path (sling:Folder) /var/aem-modernize/job-data/component\ncreate path (sling:Folder) /var/aem-modernize/job-data/policy\ncreate path (sling:Folder) /var/aem-modernize/job-data/full"
]
}Do not add $[secret:] or $[env:] placeholders to this file — repoinit scripts do not support interpolation.
ui.appsAll XML rule nodes are deployed via ui.apps. Confirm the filter.xml (or filters.xml) for the ui.apps package includes the modernization/ subtree.
Expected filter entry:
<filter root="/apps/<appId>/modernization"/>If the filter is missing, add it. Do not add filters for /var/aem-modernize — that path is created by repoinit, not packaged.
After generating files, report:
Sub-path A — Structure Rewrite Rules
XML rules created : <list of templateName.xml files>
OSGi configs created: <list of PageRewriteRule-*.cfg.json files>
Service config : StructureRewriteRuleServiceImpl.cfg.json (created / already existed)
Sub-path B — Component Rewrite Rules
XML rules created : <list of ruleName.xml files per app>
Service config : ComponentRewriteRuleServiceImpl.cfg.json (created / already existed)
Sub-path C — Policy Import Rules
XML rules created : <list of designName.xml files>
Service config : PolicyImportRuleServiceImpl.cfg.json
Repoinit initializer : created / already existed
Filter entries : added / already present
Review required:
<list any ambiguous items, missing editable templates, or unknown design paths>Post-generation: run sections 2–7 of template-modernization-validation.md. Section 3.1 (cq:copyChildren presence) is the highest-impact check — a missing flag silently destroys parsys children at conversion time.
needs-user-confirm / missing, stopcq:copyChildren="{Boolean}true" is mandatory on component rewrite replacements — omitting it destroys existing content inside parsyssling.resourceType, container resourceType, or design paths — wrong values cause silent failures at runtime/confPost-generation: run sections 2–7 of template-modernization-validation.md. Section 3.1 (cq:copyChildren presence) is the highest-impact check — a missing flag silently destroys parsys children at conversion time.
.tessl-plugin
skills
aem-rde
aem-workflow
code-assessment
content-distribution
create-component
assets
references
dispatcher
incident-response
references
dispatcher-foundation
performance-tuning
references
dispatcher-foundation
security-hardening
references
dispatcher-foundation
technical-advisory
references
dispatcher-foundation
workflow-orchestrator
references
dispatcher-foundation
ensure-agents-md
references
migration
references
dispatcher
legacy-ui
template-modernization
validation
scripts
dispatcher-tool
validate-migration