From dx-aem
Configure AEM-specific settings for the dx workflow. Detects AEM project structure, component paths, and brands. Appends aem section to .ai/config.yaml. Run after /dx-init.
npx claudepluginhub easingthemes/dx-aem-flow --plugin dx-aemThis skill is limited to using the following tools:
You configure AEM-specific settings by detecting the project structure and appending an `aem:` section to `.ai/config.yaml`.
Orchestrates content-first development workflow for AEM Edge Delivery Services. Ensures test content exists before implementing JS/CSS/block changes, bug fixes, or core functionality.
Generates developer guide for AEM Edge Delivery Services projects by analyzing codebase structure, custom implementations, and design tokens. Use for onboarding or technical handovers.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
You configure AEM-specific settings by detecting the project structure and appending an aem: section to .ai/config.yaml.
CRITICAL: When aem-init is re-run, EVERY step MUST execute. NEVER stop early. If the user chooses "Keep as-is" for config, skip to step 6 and continue through ALL remaining steps (6→7→8→9). Each step validates:
| File Category | Re-run Behavior |
|---|---|
| Config (aem: section in config.yaml) | Ask: keep / modify / regenerate |
| Component index (.ai/project/component-index.md) | Offer to re-scan if stale |
| Component discovery (.ai/project/component-discovery.md) | Offer to re-scan if stale (AEM dialog changes on QA) |
| Shared rule extensions (pr-review.md, pr-answer.md AEM sections) | Validate sections exist, re-append if missing |
| Rule templates (.claude/rules/be-.md, fe-.md) | Compare against plugin template → if template changed and user hasn't customized: update. If user customized: show diff, ask |
| Copilot instructions | Read from .claude/rules/ via COPILOT_CUSTOM_INSTRUCTIONS_DIRS — no copies needed |
| Copilot agents (.github/agents/AEM*.md) | Compare against plugin template → update silently if changed |
Inline execution from dx-init: When this skill is run inline from
/dx-initstep 7 (AEM auto-chain), skip Copilot-related steps: 8a (Copilot question), 8e (AEM Copilot agent templates), and 8f (copilot-instructions.md update). Also skip step 9 (Confirm). These are handled by dx-init steps 9 and 10 instead. SetCOPILOT_ENABLED=falseas default — dx-init handles Copilot output separately.
Check if .ai/config.yaml exists. If not: "Run /dx-init first to set up the base project config."
Check if .ai/config.yaml already has an aem: section:
Search for AEM component definitions using Glob (no Bash needed):
Glob: "**/jcr_root/apps/*/components/*/.content.xml"
From the results, extract:
/apps/myproject/components/content/myproject from path patternscomponentGroup in .content.xml filesLook for AEM Maven modules:
| Check | Module Type |
|---|---|
*-core/pom.xml with bundle packaging | Java/OSGi bundle |
*-apps/pom.xml with content-package | HTL + dialogs |
*-clientlibs*/pom.xml or frontend/ | Frontend build |
*-config*/pom.xml | Configuration |
*-all*/pom.xml | Deployable container |
Look for frontend source locations using Glob (no Bash needed):
Glob: "*/frontend/"
Glob: "**/ui.frontend/"
Glob: "**/clientlib.config.js"
Glob: "**/clientlibs/**/.content.xml"
Look for brand directories using Glob (no Bash needed):
Glob: "**/brands/clientlibs/*/"
Ask all four URLs. Author = content editing and dialog testing. Publisher = user-facing website rendering. These are always different instances.
**AEM Author URL (local dev)?** Content editing, dialog testing, component configuration. (default: http://localhost:4502)
**AEM Publisher URL (local dev)?** User-facing website rendering. (default: http://localhost:4503)
**Remote Author URL (QA/stage)?** For clickable author links in research output. (leave blank to use dev URL)
**Remote Publisher URL (QA/stage)?** For user-facing verification on QA environment. (leave blank to use dev URL)
Present detected values:
## Detected AEM Configuration
| Property | Value |
|----------|-------|
| Component Path | `/apps/<prefix>/components/content/` |
| Component Prefix | `<prefix>` |
| Component Group | `<group>` |
| Resource Type Pattern | `<prefix>/components/content/<name>` |
| Java Package | `<package>` (if detected) |
| Frontend Dir | `<path>` |
| Brands | <list or "single-brand"> |
| AEM Author (Dev) | `http://localhost:4502` |
| AEM Publisher (Dev) | `http://localhost:4503` |
| AEM Author (QA) | `<url or "same as dev">` |
| AEM Publisher (QA) | `<url or "same as dev">` |
**Correct?** Type "yes" or tell me what to change.
Append aem: section to .ai/config.yaml:
aem:
component-path: "/apps/<prefix>/components/content"
component-prefix: "<prefix>"
component-group: "<group>"
resource-type-pattern: "<prefix>/components/content/<name>"
java-package: "<package>" # if detected
frontend-dir: "<path>" # if detected
brands: [<list>] # if multi-brand
author-url: "http://localhost:4502" # Author — dialog/component editing
author-url-qa: "<url>" # Remote author — clickable links in research output
publish-url: "http://localhost:4503" # Publisher — user-facing website
publish-url-qa: "<url>" # Remote publisher — user-facing verification on QA
demo-parent-path: "<path>" # Parent for AI-created demo pages (e.g., /content/mysite/en/demo)
# selector: "<exporter-selector>" # Uncomment if project uses custom selector
Ask: "Scan for existing components and generate .ai/project/component-index.md? This helps /aem-component and /dx-ticket-analyze find files faster."
If yes:
.content.xml files under the component path.ai/project/component-index.md as a lookup table (create .ai/project/ if needed)Ask: "Query AEM QA for component dialog fields, variants, and pages? This creates .ai/project/component-discovery.md for richer AI planning. Requires AEM QA to be reachable."
If yes:
Prerequisites check:
aem.author-url-qa must be configured. If not: warn "Set aem.author-url-qa in config to enable component discovery." and skip.mcp__plugin_dx-aem_AEM__getNodeContent on the component root path. If unreachable: warn "AEM QA not reachable — component-discovery.md skipped. Run /aem-init again when QA is available." and skip.Discovery logic:
For each component found by Step 6a's Glob scan (scoped to this repo's aem.component-path):
Dialog fields — mcp__plugin_dx-aem_AEM__getNodeContent on <component-path>/<name>/_cq_dialog with depth 6. Extract: field name, sling:resourceType (maps to field type), fieldLabel, fieldDescription, required flag.
Variants in this repo — Glob sibling directories with same name suffix under the component path. E.g., if component is productcard, check for *-productcard or productcard-* siblings.
Variants in other repos — mcp__plugin_dx-aem_AEM__searchContent for components whose sling:resourceSuperType equals this component's resourceType. Cross-reference with repos: section in config.yaml to note owning repo.
Pages using component — mcp__plugin_dx-aem_AEM__searchContent by sling:resourceType under the content roots from content-paths.yaml or aem.demo-parent-path parent. Limit to 5 pages.
Authored values — For the first page found, mcp__plugin_dx-aem_AEM__getNodeContent on the component instance (depth 4). Extract actual field values to reveal semantic meaning.
Parallelism: Dispatch one Explore agent per ~10 components. Each agent receives the component list, AEM QA URL, and query instructions. Agents use mcp__plugin_dx-aem_AEM__* tools directly.
Output: Write .ai/project/component-discovery.md with format:
# AEM Component Discovery
**Generated:** <date>
**Instance:** <aem.author-url-qa> (QA)
**Repo:** <scm.repo or project name>
**Scope:** <aem.resource-type-pattern>* (<count> components)
## <component-name>
**Resource Type:** <full resource type>
**Owner:** <repo name> (this repo)
**Variants (this repo):** <comma-separated list or "none">
**Variants (other repos):**
- `<variant-name>` → <repo-name> (superType: <this component's resourceType>)
### Dialog Fields
| Field | Type | Label | Description | Required |
|-------|------|-------|-------------|----------|
| <name> | <type> | <fieldLabel> | <fieldDescription> | <yes/no> |
### Sample Authored Values (from <page-path>)
| Field | Value |
|-------|-------|
| <name> | <value or "(empty)"> |
### Pages (<count>)
- <page-path-1>
- <page-path-2>
...
### Cross-Repo Dependencies
- **superType:** <superType resource type> → <repo name> (base)
For brand-override repos: dialog fields note inherited from <base> vs LOCAL (this repo).
Re-run behavior: If .ai/project/component-discovery.md already exists, ask "Re-scan component discovery? (recommended after dialog changes on QA)". If no: keep existing, report "validated (kept existing)".
Append AEM-specific focus areas to the shared rules in .ai/rules/ (installed by dx-init). This ensures both local dx skills and automation agents apply AEM review criteria.
Idempotent: check if the section already exists before appending.
If .ai/rules/pr-review.md exists:
## AEM/Sling Patterns (use Grep tool, not Bash)
## AEM/Sling Patterns
- Verify Sling Model annotations: correct adaptables, resource types, injection strategies
- Check @Self @Via(ResourceSuperType) delegation patterns
- Validate OSGi service references use proper injection
- Ensure ResourceResolver lifecycle is respected (no leaked resolvers)
## Frontend Patterns
- Check component-loader registration matches class name
- Verify config files externalize DOM selectors and CSS classes
- Check PubSub event usage matches constants from commons/constants/events.js
- Validate SCSS uses @use/@forward (not @import)
If .ai/rules/pr-answer.md exists:
## AEM/Sling Patterns (use Grep tool, not Bash)
## AEM/Sling Patterns
- Understand @Self @Via(ResourceSuperType) delegation
- Understand Sling Model injection strategies
- Know when OSGi service patterns are intentional
## Frontend Patterns
- Understand component-loader registration
- Know config externalization patterns
- Understand PubSub event communication
- SCSS uses @use/@forward (not @import)
If .ai/rules/pr-review.md or .ai/rules/pr-answer.md does not exist, skip and note: "Shared rules not found — run /dx-init first."
Unified convention templates (templates/rules/) contain dual frontmatter (paths: for Claude Code, applyTo: for Copilot CLI). Each template is copied to .claude/rules/. Copilot CLI reads rules from this same location via COPILOT_CUSTOM_INSTRUCTIONS_DIRS env var (set by /dx-init step 9a-bis) — no .github/instructions/ copies needed.
**Support GitHub Copilot CLI?** (default: yes)
If yes:
- AEM Copilot agent definitions are generated in `.github/agents/`
If no:
- No Copilot agents generated
Set COPILOT_ENABLED=true or COPILOT_ENABLED=false based on the answer.
Ensure .claude/rules/ exists. The Write tool creates parent directories automatically, so no explicit mkdir is needed.
.claude/rules/project.role from .ai/config.yamlproject.role | Directories |
|---|---|
frontend | shared/ + fe/ |
backend | shared/ + be/ |
fullstack | shared/ + be/ + fe/ |
config | shared/ only |
| (not set) | All directories (backward compatibility) |
Glob: "<plugin-path>/templates/rules/<dir>/*.md.template"
Derive target filename by stripping the .template suffix (e.g., be-sling-models.md.template → be-sling-models.md).
If .claude/rules/<target> already exists:
Read both the existing file and the plugin template. Compare content:
.template (user can diff manually).Check for topic overlap (new files only):
Scan existing .claude/rules/*.md — read their frontmatter (description and paths fields). Compare against the template's paths glob and topic (filename prefix: fe- or be-). If a file with overlapping paths and similar topic exists:
.template (user can diff manually)No conflict:
Copy the file to .claude/rules/<target>, stripping the .template extension.
Read from .ai/config.yaml:
| Variable | Config source |
|---|---|
{{COMPONENT_PREFIX}} | aem.component-prefix |
{{DAM_PATH}} | aem.dam-path |
{{CSS_PREFIX}} | aem.component-prefix (fallback: project.prefix) |
{{BUILD_TOOL}} | toolchain.build-tool |
{{CSS_COMPILER}} | toolchain.css-compiler |
{{NODE_VERSION}} | toolchain.node |
{{COMPONENT_BASE_CLASS}} | toolchain.component-base-class |
{{RESOURCE_TYPE_PATTERN}} | aem.resource-type-pattern |
For each variable, if the config value is empty or missing, leave the placeholder as-is (consumer will fill in or the value is not relevant for this project).
Note:
.github/instructions/copies are no longer generated. Copilot CLI reads rules directly from.claude/rules/viaCOPILOT_CUSTOM_INSTRUCTIONS_DIRSenv var. Rules with dual frontmatter (paths:+applyTo:) work in both tools from a single source.
Instruction templates no longer exist as separate files. The unified rule templates in templates/rules/ contain both paths: (Claude Code) and applyTo: (Copilot CLI) frontmatter. Step 8c handles both outputs.
Only if the user answered "yes" to Copilot support in step 7a.
Use Glob to find agent template files (no Bash needed):
Glob: "<plugin-path>/templates/agents/*.agent.md.template"
For each result:
.template suffix from the filename.github/agents/<AgentName>.agent.mdOn re-run: If .github/agents/<target> already exists, compare against the plugin template. Update silently if the template has changed (Copilot agents are not user-customized). Report "validated" or "updated".
If .github/copilot-instructions.md exists (generated by /dx-init), append an AEM agents section:
## AEM Agents
| Agent | Purpose | Invoke |
|-------|---------|--------|
| AEMBefore | Pre-development baseline snapshot | `@AEMBefore <component>` |
| AEMAfter | Post-deployment verification | `@AEMAfter <component>` |
| AEMSnapshot | Component inspection | `@AEMSnapshot <component>` |
| AEMDemo | Dialog screenshot + authoring guide | `@AEMDemo <component>` |
| AEMComponent | Find pages using a component | `@AEMComponent <component>` |
| AEMVerify | Bug verification on AEM | `@AEMVerify <component>` |
If the file doesn't exist, skip this step (it means /dx-init didn't generate Copilot files).
### Convention Files
**Rules** (`.claude/rules/`): <N> written, <N> skipped
<If Copilot enabled:>
**Copilot instructions**: read from `.claude/rules/` via `COPILOT_CUSTOM_INSTRUCTIONS_DIRS` (no copies needed)
**Copilot Agents** (`.github/agents/`): <N> written, <N> skipped
## AEM Initialized
**Component Path:** `<path>`
**Brands:** <list>
**Config:** `.ai/config.yaml` (aem section appended)
### Next Steps
- `/aem-component <name>` — look up a component's files and pages
- `/aem-snapshot <name>` — capture baseline before development
- `/aem-verify <name>` — verify after deployment
Check if .ai/project/project.yaml exists. If not, print: "No project.yaml found. To add project knowledge, create .ai/project/project.yaml. See docs/authoring/seed-data-guide.md for format." Then skip to step 11.
If project.yaml exists:
project.yaml → brands[] listsingle-market: true: auto-confirm this brand and its single market. Report "Auto-confirmed: ()"repos: from .ai/config.yaml (written by /dx-init)repos[] from .ai/project/project.yaml for platform and ado-project datarepos:, add it with name and path (ask: "Local path for ? (default: ../<name>)")repos:, enrich with fields from project.yaml: platform, ado-projectpath is missing on any entry, ask: "Local path for ? (default: ../<name>)"base-branch if not set (default: same as scm.base-branch)repos:, if role: is missing or capabilities: is present:
role: to the repo's entry in config.yamlcapabilities: exists on that entry, remove itAsk via AskUserQuestion (one at a time):
From project.yaml → defaults:
qa-author-url → write to aem.author-url-qaqa-publish-url → write to aem.publish-url-qa (if present)Update top-level repos: section (NOT under aem:):
For each repo, ensure these fields are present:
repos:
- name: <repo>
path: <relative-path>
role: <role> # preserved from dx-init
platform: <platform> # NEW — from project.yaml
ado-project: <project> # NEW — from project.yaml (if different from scm.project)
base-branch: <branch> # NEW — user input or scm.base-branch default
Remove aem.repos if present (migrated to top-level repos:).
Remove aem.current-repo (redundant — current repo is implicit).
Keep aem.platform (quick self-identification for the current repo).
Append/update these fields in the aem: section of .ai/config.yaml:
aem:
# ... existing aem-init fields (component-path, etc.) ...
platform: <derived from project.yaml>
active-brands: [<brands>]
active-markets: [<market codes>]
author-url-qa: <url from defaults>
publish-url-qa: <url from defaults>
docs-repo: <url>
auto-commit: <bool>
auto-pr: <bool>
If aem: section already has project config (active-brands, active-markets):
Migration from aem.repos: On re-run, if .ai/config.yaml has aem.repos: but top-level repos: is missing or incomplete, migrate: copy entries from aem.repos to top-level repos:, mapping local-path → path. Then remove the aem.repos section. Report what was migrated.
Copy rule templates from the plugin templates/rules/ directory to .claude/rules/:
audit.md.template → .claude/rules/audit.md (remote change audit rules)qa-basic-auth.md.template → .claude/rules/qa-basic-auth.md (QA/stage auth credentials)For qa-basic-auth: the rule now reads credentials from env vars first (QA_BASIC_AUTH_USER etc.), with .ai/config.yaml as legacy fallback. If project.yaml defines defaults.qa-basic-auth, still substitute into the rule template for backward compatibility.
On re-run: Compare existing file against template. If identical: skip. If template changed and user hasn't customized: update silently. If user customized: show diff, ask.
Check if AEM_INSTANCES is already set in the shell environment:
echo "$AEM_INSTANCES"
If set: Report: "AEM_INSTANCES detected in shell environment — AEM MCP will use it." Skip to step 12.
If not set: Print setup instructions:
AEM MCP requires the AEM_INSTANCES environment variable.
Add to your shell profile (~/.bashrc or ~/.zshrc):
export AEM_INSTANCES="local:http://localhost:4502:admin:admin"
Format: name:url:user:pass (comma-separated for multiple instances).
Then restart your terminal or run: source ~/.zshrc
Alternative: add to .claude/settings.local.json under "env" (Claude Code only, not Copilot CLI).
Also update .claude/settings.local.json — add the AEM_INSTANCES key as a placeholder. Do NOT overwrite existing values:
a. If file exists: Merge AEM_INSTANCES into the existing "env" object.
b. If file does not exist: Create it with both base dx env vars and AEM env vars:
{
"env": {
"QA_BASIC_AUTH_USER": "",
"QA_BASIC_AUTH_PASS": "",
"QA_BASIC_AUTH_FALLBACK_USER": "",
"QA_BASIC_AUTH_FALLBACK_PASS": "",
"AXE_API_KEY": "",
"AEM_INSTANCES": "local:http://localhost:4502:admin:admin"
}
}
Report: "Updated .claude/settings.local.json with AEM_INSTANCES. Preferred: set in shell profile for both Claude Code and Copilot CLI."
Run component discovery for best results: The
component-discovery.mdfile (generated in Step 6b) provides dialog field semantics, component variants, and page locations to all downstream skills (/dx-req,/dx-plan,/dx-step). Without it, these skills rely on Glob/Grep only and may miss field meanings and brand-specific variants. Re-run/aem-initafter major dialog changes on QA.
## AEM Initialized
**Component Path:** `<path>`
**Brands:** <list>
**Active Markets:** <codes>
**Repos:** <count> configured (<count> with local paths)
**Config:** `.ai/config.yaml` (aem section updated)
### Seed Data Status
| File | Status |
|------|--------|
| component-index.md | <generated / exists / skipped> |
| component-discovery.md | <generated / exists / skipped / QA unreachable> |
| project.yaml | <found / not found> |
| file-patterns.yaml | <found / not found> |
| content-paths.yaml | <found / not found> |
| architecture.md | <found / not found> |
| features.md | <found / not found> |
| component-index-project.md | <found / not found> |
### Next Steps
- `/aem-component <name>` — look up a component's files and pages
- `/aem-refresh` — refresh seed data from docs repo
- `/aem-snapshot <name>` — capture baseline before development
- `/aem-verify <name>` — verify after deployment
/aem-init — First run after /dx-init. Detects ui.frontend/src/core/components/ and ui.frontend/src/brand/components/ directories, identifies brands from the project's component directories, and appends aem: section to .ai/config.yaml with author URL, publisher URL, component paths, and active markets.
/aem-init (re-run to update markets) — Detects existing aem: section in config. Asks whether to re-detect or update specific values. User updates active-markets from [gb, de] to [gb, de, fr]. Refreshes seed data files in .ai/project/.
/aem-init (with component index scan) — After detecting project structure, asks "Scan AEM for component index?" User confirms. Queries AEM author instance for all components under /apps/, builds component-index-project.md with component names, resource types, and dialog field counts.
".ai/config.yaml not found — run /dx-init first"
Cause: /aem-init requires the base config created by /dx-init.
Fix: Run /dx-init first to create .ai/config.yaml, then run /aem-init to add the AEM section.
Component index scan fails or returns empty
Cause: AEM author instance is not running or not reachable at the configured URL.
Fix: Start AEM locally or verify aem.author-url in .ai/config.yaml. The component index is optional — skip it and run /aem-init again later when AEM is available.
Seed data files missing after init
Cause: The plugin's data/ directory doesn't contain seed files for this project, or the copy step was skipped.
Fix: Run /aem-refresh to pull seed data from the docs repo or a local path. Check that .ai/project/ contains project.yaml, file-patterns.yaml, content-paths.yaml, and architecture.md.
AskUserQuestion tool to pause and wait for the user's response. Never proceed past a question without receiving the user's answer first. Present numbered options in the question text, then STOP and wait. Do not batch multiple questions into one message — ask one, wait for the answer, then continue..ai/config.yaml must exist before adding AEM configaem: section to existing config