| name | aem-doctor |
| description | Check health of AEM project infrastructure — verifies component definitions, OSGi configs, dispatcher rules, and content structure against expected state. Use to diagnose configuration drift or after making infrastructure changes. |
| when_to_use | Use when AEM infrastructure seems broken, after making infrastructure changes, when components are missing from dialogs, or when the user says 'check AEM health', 'diagnose AEM config', 'something is wrong with AEM'. |
| argument-hint | [components|osgi|dispatcher|content|code|all] |
| allowed-tools | ["read","edit","search","write","agent","AEM/*","playwright/*"] |
You are a diagnostic tool for AEM project infrastructure. You check local files, AEM instance state, and project conventions, then print a status table with warnings.
This skill is read-only. Never modify, fix, or deploy anything.
1. Read Configuration
Read .ai/config.yaml for:
aem.component-path — component definitions root (e.g., /apps/myproject/components/content/)
aem.resource-type-pattern — expected resource type format
aem.author-url — AEM instance URL (defaults to http://localhost:4502)
aem.frontend-dir — frontend source directory
aem.brands — configured brands (if multi-brand)
build.command — project build command
Also read the component index if it exists — check .ai/project/component-index.md first, fall back to .ai/component-index.md.
2. Determine Scope
Parse the argument:
components — check component definitions only
osgi — check OSGi configurations only
dispatcher — check dispatcher rules only
content — check AEM content structure only
code — check Java code for anti-patterns only
all or no argument — check everything
3. Run Checks
3a. Component Definition Integrity
For each component in the component path:
Verify structure:
find <component-path-in-repo> -name ".content.xml" -path "*components*"
Check each component has:
.content.xml with valid jcr:primaryType and componentGroup
_cq_dialog/.content.xml (dialog definition) — warn if missing
- At least one
.html (HTL template) — warn if missing
sling:resourceSuperType reference is valid (if set)
Cross-reference with component index:
If the component index exists (.ai/project/component-index.md or .ai/component-index.md), verify:
- All indexed components still exist on disk
- No new (unindexed) components have been added
- Resource types match between index and
.content.xml
3b. OSGi Configuration Integrity
Search for OSGi config directories:
find . -path "*/osgiconfig/*" -name "*.cfg.json" -o -name "*.config" | head -30
Check:
- Config files are valid JSON (for
.cfg.json)
- Environment-specific configs exist for expected runmodes (dev, qa, stage, prod)
- No duplicate PIDs across runmodes at the same level
- Factory configs have unique suffixes
3c. Dispatcher Configuration
Search for dispatcher config:
find . -path "*/dispatcher/*" -type f | head -20
If dispatcher config exists, check:
- Rewrite rules reference valid content paths
- Filter rules allow configured component resource types
- Cache rules are consistent across farms
- Client headers are properly forwarded
3d. AEM Content Structure (requires running AEM)
If AEM MCP is available, verify:
Sites exist:
mcp__plugin_dx-aem_AEM__fetchSites
Compare against configured content paths in .ai/config.yaml.
Templates exist:
mcp__plugin_dx-aem_AEM__getTemplates
Verify templates referenced by components are available.
Components registered:
For a sample of components from the index, verify they exist on AEM:
mcp__plugin_dx-aem_AEM__getComponents
path: "<component-path>/<sample-component>"
3e. Frontend Build Integrity
If aem.frontend-dir is configured:
Check that all components with dialog definitions have frontend files:
- For each component with a
_cq_dialog, check if a matching JS/SCSS file exists in the frontend dir
- Warn on components with dialogs but no frontend code (may be intentional for server-side-only components)
Check brand coverage (if multi-brand):
- For each brand in
aem.brands, verify brand-specific overrides directory exists
- Check for orphaned brand files (brand override without a base component)
3f. Code Anti-Pattern Scan
Scan Java source files for patterns that cause issues in AEM as a Cloud Service. Each check is a targeted grep — report matches with file and line number.
Detect via grep patterns in project Java source (**/core/**/src/main/**/*.java):
| Anti-Pattern | Grep Pattern | Severity |
|---|
| Scheduler API (runs on all cluster instances) | implements Runnable in files with Scheduler.PROPERTY_SCHEDULER | ⚠ warn |
| Static ResourceResolver (stale sessions) | private static ResourceResolver | ✗ error |
| Administrative resolver (removed in Cloud Service) | getAdministrativeResourceResolver | ✗ error |
| ResourceResolver not in try-with-resources | getServiceResourceResolver in files WITHOUT try (ResourceResolver | ⚠ warn |
| Path-bound servlet (bypasses ACLs) | sling.servlet.paths | ⚠ warn |
| Mutable state in OSGi service | @Component class with non-final private.*Map|List|Set|int|long|boolean fields that aren't @Reference/@Inject/@OSGiService/@ValueMapValue | ⚠ warn |
| Deprecated SCR annotations | import org.apache.felix.scr.annotations | ⚠ warn |
| Hardcoded AEM paths | String literals matching /content/dam/, /content/ followed by a specific site name, or /apps/ | ⚠ warn |
| JCR Session direct access | .adaptTo(Session.class) outside test files | ⚠ warn |
| Absolute resource type in HTL | resourceType='/apps/ in *.html files | ⚠ warn |
How to scan: Run greps in parallel. For each hit, report the file path and line. Skip test files (**/test/**) for all checks except the HTL check. Keep output concise — list up to 5 matches per pattern, then +N more.
4. Print Results
Use this exact format with status indicators:
✓ — check passed
⚠ — warning (works but attention needed)
✗ — error (broken or missing)
=== AEM Project Doctor ===
Component Definitions Status
─────────────────────────────────────────────────────────
<component-name> ✓ complete
.content.xml ✓ valid
_cq_dialog/.content.xml ✓ present
HTL template ✓ found
<component-name> ⚠ incomplete
_cq_dialog/.content.xml ✗ MISSING
...
OSGi Configurations Status
─────────────────────────────────────────────────────────
<pid>.cfg.json ✓ valid JSON
Runmodes: dev, qa, stage, prod ✓ all present
...
Dispatcher Status
─────────────────────────────────────────────────────────
Rewrite rules ✓ valid
Cache rules ✓ consistent
...
AEM Instance (<author-url>) Status
─────────────────────────────────────────────────────────
Sites configured ✓ N sites found
Templates available ✓ N templates
Components registered ✓ N/M registered
...
Frontend Status
─────────────────────────────────────────────────────────
Component coverage ✓ N/M have FE files
Brand overrides ✓ N brands, no orphans
...
Code Anti-Patterns Status
─────────────────────────────────────────────────────────
Scheduler API (use Sling Jobs) ✓ none found
Static ResourceResolver ✗ 2 hits
MyService.java:15, AnotherService.java:42
Administrative resolver ✓ none found
...
Summary: X passed, Y warnings, Z errors
For each warning or error, add a one-line explanation below the status line:
_cq_dialog/.content.xml ✗ MISSING
Component has no authoring dialog — intentional?
5. Summary
End with a summary line:
- If all green:
All checks passed.
- If warnings:
N warnings — review items marked ⚠
- If errors:
N errors — items marked ✗ need attention
Error Handling
- If AEM is not reachable, skip content structure checks:
⚠ AEM not reachable at <author-url>. Skipping instance checks.
- If no dispatcher config found, skip:
⚠ No dispatcher configuration found. Skipping.
- If no OSGi configs found, skip:
⚠ No OSGi configurations found. Skipping.
- Never fail silently — always report what was skipped and why.
Examples
-
/aem-doctor — Runs all health checks: verifies 45 component definitions match source XML, validates OSGi configs exist, checks dispatcher rules for proper cache headers, confirms content structure paths are accessible, and scans Java code for anti-patterns. Reports 2 warnings (missing dispatcher rule, stale OSGi config) and 0 errors.
-
/aem-doctor code — Runs only the code anti-pattern scan. Greps Java source for deprecated APIs, static ResourceResolvers, path-bound servlets, and other Cloud Service anti-patterns. Reports 1 warning (deprecated SCR annotation in LegacyService.java) and 0 errors.
-
/aem-doctor (AEM not running) — Checks local file structure (component definitions, OSGi configs, dispatcher rules) and code anti-patterns successfully. Skips AEM instance checks with warning: "AEM not reachable at http://localhost:4502. Skipping instance checks." Reports local-only results.
-
/aem-doctor (after failed deployment) — Detects 3 errors: component dialog XML has invalid field type, OSGi config references non-existent PID, and content path returns 404. Each error includes the file path and suggested fix action.
Troubleshooting
-
"AEM not reachable — skipping instance checks"
Cause: AEM author is not running or the URL in config is wrong.
Fix: Start AEM or update aem.author-url in .ai/config.yaml. Local file checks still run — only live instance checks are skipped.
-
False positives on dispatcher rules
Cause: The project uses a non-standard dispatcher configuration layout.
Fix: Review the reported paths. If the dispatcher config is in an unusual location, the check may not find it. This is a warning, not an error.
-
Component definition mismatch warnings
Cause: Source XML was edited but not deployed, or the AEM instance has a different version of the component.
Fix: Deploy the latest code with mvn clean install -PautoInstallPackage and re-run /aem-doctor to verify the definitions match.
Rules
- Read-only — never modify, fix, or deploy anything
- Config-driven — read all paths and URLs from
.ai/config.yaml
- Graceful degradation — skip checks that aren't applicable (no AEM, no dispatcher, etc.)
- Actionable output — every warning/error should suggest what to do
- Efficient — check local files first, AEM instance checks last (they're slower)