plugins/aem/project-management/skills/handover-developer/SKILL.md
Generate comprehensive technical documentation for developers taking over an AEM Edge Delivery Services project. Use when onboarding developers, creating technical handover documentation, or documenting a project's architecture — analyzes codebase structure, custom implementations, design tokens, and produces a complete developer guide.
npx skillsauth add adobe/skills handover-developerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate a complete technical guide for developers. Analyzes the codebase and produces actionable documentation that enables developers to understand, maintain, and extend the project.
Skip if allGuides is set in .claude-plugin/project-config.json (orchestrator already validated).
ALL_GUIDES=$(cat .claude-plugin/project-config.json 2>/dev/null | node -e "
const d = require('fs').readFileSync(0,'utf8');
try { console.log(JSON.parse(d).allGuides ? 'true' : ''); } catch(e) { console.log(''); }
")
if [ -z "$ALL_GUIDES" ]; then
cd "$(git rev-parse --show-toplevel)"
ls scripts/aem.js
fi
If scripts/aem.js does not exist, tell the user this skill requires an AEM Edge Delivery Services project and stop.
All subsequent steps operate from project root. Guides are created at project-guides/.
- [ ] Phase 0: Get org name and authenticate
- [ ] Phase 1: Gather project information
- [ ] Phase 2: Analyze project architecture
- [ ] Phase 3: Document design system
- [ ] Phase 4: Document blocks, models, and templates
- [ ] Phase 5: Generate PDF
cat .claude-plugin/project-config.json 2>/dev/null | node -e "
const d = require('fs').readFileSync(0,'utf8');
try { const o = JSON.parse(d).org; if(o) console.log('org: ' + o); } catch(e) {}
"
If no org name is found, ask the user:
"What is your Config Service organization name? This is the
{org}part of your Edge Delivery Services URLs (e.g.,https://main--site--{org}.aem.page). The org name may differ from your GitHub organization."
Ask as a plain text question — not AskUserQuestion with options. Organization name is mandatory.
mkdir -p .claude-plugin
grep -qxF '.claude-plugin/' .gitignore 2>/dev/null || echo '.claude-plugin/' >> .gitignore
if [ -f .claude-plugin/project-config.json ]; then
cat .claude-plugin/project-config.json | sed 's/"org"[[:space:]]*:[[:space:]]*"[^"]*"/"org": "{ORG_NAME}"/' > /tmp/project-config.json && mv /tmp/project-config.json .claude-plugin/project-config.json
else
echo '{"org": "{ORG_NAME}"}' > .claude-plugin/project-config.json
fi
Replace {ORG_NAME} with the actual organization name.
AUTH_TOKEN=$(node -e "
const fs = require('fs');
try {
const t = JSON.parse(fs.readFileSync(process.env.HOME + '/.aem/ims-token.json', 'utf8'));
if (t.authToken && t.authTokenExpiry > Math.floor(Date.now()/1000) + 60) {
process.stdout.write(t.authToken);
}
} catch (e) {}
")
if [ -z "$AUTH_TOKEN" ]; then
echo "AUTH_REQUIRED"
fi
If AUTH_REQUIRED, invoke the auth skill:
Skill({ skill: "aem-project-management:auth" })
git remote -v | head -1
git branch -a | head -10
Extract: repository owner, repo name, main branch name.
ls helix-config.yaml 2>/dev/null && echo "Uses legacy helix-config" || echo "Uses Config Service (modern)"
The Config Service API is the only reliable source for site information. Do not use fstab.yaml, README, or git remote URLs.
ORG=$(cat .claude-plugin/project-config.json | node -e "
const d = require('fs').readFileSync(0,'utf8');
console.log(JSON.parse(d).org || '');
")
AUTH_TOKEN=$(node -e "
const fs = require('fs');
try {
const t = JSON.parse(fs.readFileSync(process.env.HOME + '/.aem/ims-token.json', 'utf8'));
process.stdout.write(t.authToken || '');
} catch (e) {}
")
curl -s -H "x-auth-token: ${AUTH_TOKEN}" -H "Accept: application/json" \
"https://admin.hlx.page/config/${ORG}/sites.json" > .claude-plugin/sites-config.json
node -e "
const d = require('fs').readFileSync('.claude-plugin/sites-config.json', 'utf8');
const j = JSON.parse(d);
if (!j.sites || !j.sites.length) {
console.error('No sites returned — verify org name and re-authenticate if needed');
process.exit(1);
}
console.log('Found ' + j.sites.length + ' site(s): ' + j.sites.map(s => s.name).join(', '));
"
If validation fails, verify the org name is correct, re-authenticate, and retry.
Fetch per-site config:
curl -s -H "x-auth-token: ${AUTH_TOKEN}" \
"https://admin.hlx.page/config/${ORG}/sites/{site-name}.json"
Extract: code.owner, code.repo, content.source.url, content.source.type.
Multiple sites = repoless setup. Single site = standard setup. Record this — it affects the aem up local dev instructions.
cat .nvmrc 2>/dev/null || cat package.json | grep -A2 '"engines"'
Read site config:
cat .claude-plugin/sites-config.json
ls -la && ls -la blocks/ && ls -la scripts/ && ls -la styles/
ls -la templates/ 2>/dev/null || echo "No templates folder"
Only document files that were actually customized.
git log --oneline --follow {file_path} | head -5
git log --format="%an - %s" --follow {file_path} | head -5
| Git History | Action |
|-------------|--------|
| Only "Initial commit" | Skip — boilerplate default, never worked on |
| Only aem-aemy[bot] commits | Skip — auto-generated |
| Multiple commits by team | Document — customized |
grep -E "^export" scripts/aem.js
Document which functions the project imports from aem.js (e.g., sampleRUM, loadHeader, loadFooter, decorateBlock, loadBlock, loadCSS).
grep -E "^import|^export|^function|^async function|buildAutoBlocks|loadTemplate|getLanguage|getSiteRoot|decorateMain|loadEager|loadLazy|loadDelayed" scripts/scripts.js
Document:
| Pattern | What to Document |
|---------|-----------------|
| import statements | What it imports from aem.js and utils.js |
| loadEager / loadLazy | Any custom logic added to E-L-D phases |
| buildAutoBlocks | Auto-blocking logic |
| loadTemplate / template handling | Template system |
| getLanguage / language detection | Multi-language setup |
| getSiteRoot / site detection | Multi-site configuration |
| External script loading | Which phase — flag if in eager (performance risk) |
grep -E "^import|function|google|analytics|gtag|alloy|martech|OneTrust|launch|chatbot|widget" scripts/delayed.js
Document analytics integrations, marketing tools, performance monitoring. Confirm no render-critical code is in this file.
grep -E "^export|^function" scripts/utils.js 2>/dev/null || echo "No utils.js"
ls scripts/*.js
grep -rl "utils.js" blocks/ scripts/ 2>/dev/null
Document shared utility functions and which blocks/scripts import them.
grep -A 20 '"dependencies"' package.json 2>/dev/null | head -25
grep -r "cdn\|unpkg\|jsdelivr" scripts/ blocks/ --include="*.js" 2>/dev/null
grep -E "^\s*--" styles/styles.css
Organize into categories: Typography, Colors, Spacing, Layout.
grep -E "@font-face|font-family|font-weight|src:" styles/fonts.css 2>/dev/null
ls fonts/ 2>/dev/null
Document: font files and formats, family names, weights, fallback fonts.
grep -E "@media.*min-width|@media.*max-width" styles/styles.css | sort -u
Standard breakpoints: Mobile < 600px, Tablet 600-899px, Desktop 900px+, Large 1200px+. Document any deviations.
grep -A 5 "\.section\." styles/styles.css
grep -A 5 "\.section\[" styles/styles.css
Run silently — do not show output to user.
aem-aemy[bot] commitshead -30 blocks/{blockname}/{blockname}.js
grep -E "^\." blocks/{blockname}/{blockname}.css | head -30
grep -E "classList\.contains|classList\.add" blocks/{blockname}/{blockname}.js
Document for each customized block:
| Field | What to Record | |-------|----------------| | Name | Block folder name | | Purpose | What it does | | DOM Input | Expected HTML structure from CMS | | DOM Output | Transformed structure after decoration | | Variants | CSS classes that modify behavior | | Dependencies | External libraries, other blocks, utils |
Apply boilerplate filtering to models/*.json. Exclude standard boilerplate models (_page.json, _section.json, _button.json, _image.json, _text.json, _title.json) if unchanged. Skip section if all models are boilerplate.
Apply boilerplate filtering to templates/*/. For each customized template, document purpose, how it's applied (template: name in metadata), and what it changes.
Save to project-guides/DEVELOPER-GUIDE.md (run mkdir -p project-guides first).
Read resources/developer-guide-template.md for the full document structure. Fill in all sections using data gathered in Phases 1–4 — replace every [placeholder] with actual project values.
Save the completed markdown to project-guides/DEVELOPER-GUIDE.md with YAML frontmatter (title, date using full date format e.g., "February 17, 2026"). Then immediately invoke PDF conversion:
Skill({ skill: "aem-project-management:whitepaper", args: "project-guides/DEVELOPER-GUIDE.md project-guides/DEVELOPER-GUIDE.pdf" })
The whitepaper skill auto-cleans source files. Final output: project-guides/DEVELOPER-GUIDE.pdf.
Inform the user: "Developer guide complete: project-guides/DEVELOPER-GUIDE.pdf"
| Category | Check |
|----------|-------|
| Data Source | Config Service API called (https://admin.hlx.page/config/{ORG}/sites.json) |
| Data Source | Site list from API response, not fstab.yaml or codebase analysis |
| Data Source | Repoless/standard determination from Config Service, not inferred from code |
| Content | Quick Reference with all project URLs |
| Content | Architecture overview accurate to project |
| Content | Design system fully documented (tokens, fonts, breakpoints) |
| Content | Project-specific blocks documented |
| Content | Custom scripts.js functions documented |
| Content | delayed.js integrations documented |
| Content | Templates documented (if applicable) |
| Content | Local development setup verified |
| Content | Common tasks have clear instructions |
| Content | Troubleshooting section covers common issues |
| Output | PDF generated at project-guides/DEVELOPER-GUIDE.pdf |
| Output | All source files cleaned up (only PDF remains) |
Communication: Never use "EDS" as an acronym — always write "Edge Delivery Services" or "AEM Edge Delivery Services" in all output and documentation.
tools
Use the run-workflow MCP to discover, compose, execute, publish, and save Adobe Firefly workflows. TRIGGER when: user asks what actions are available, what the MCP can do, how to process images/video/3D via workflow, wants to build/run/save/publish a workflow, OR pastes any workflow/batch/execution ID. BARE ID (UUID/workflowId/batchId) = INSPECT ONLY — call inspect_run, NEVER run_workflow_submit. ALWAYS call list_actions first for capability/discovery questions. DO NOT TRIGGER for direct Firefly API calls without MCP (use firefly-api-specs).
tools
Run predefined featured workflows via run-workflow MCP. TRIGGER when user names a featured workflow (retargeting, banners at scale, localization, packaging, banner advertising, etc.) or asks to run a known marketing/production workflow. Requires run-workflow MCP. ALWAYS call get_featured_workflow before compose_workflow. DO NOT TRIGGER for custom one-off workflows with no named template — use run-workflow skill.
tools
Migrate an Adobe Commerce App Builder project from the Integration Starter Kit or Checkout Starter Kit to the new App Management approach. Run from the root of the App Builder project to be migrated. Pass --auto to skip confirmation prompts (suitable for CI or batch use) — auto mode prints a summary of all Q&A questions answered with their defaults. Pass --doc-scan-only to scan README.md and env.dist for outdated content without modifying any files. Use when the user wants to migrate an App Builder project from the Integration Starter Kit or Checkout Starter Kit to the App Management approach, or mentions upgrading their Adobe Commerce extension architecture.
development
Add or modify webhook interceptors in an Adobe Commerce app. Use when the user wants to intercept Commerce operations to validate input, append data, or modify behavior — before or after execution. Requires a base app initialized with commerce-app-init.