Generate comprehensive technical documentation for developers taking over an AEM Edge Delivery Services project. Analyzes codebase structure, custom implementations, design tokens, and produces a complete developer guide.
Generate a complete technical guide for developers. This skill analyzes the codebase and produces actionable documentation that enables developers to understand, maintain, and extend the project.
Skip this step if allGuides flag is set (orchestrator already validated and navigated).
CRITICAL: If NOT skipped, you MUST execute the cd command. Do NOT use absolute paths — actually change directory.
ALL_GUIDES=$(cat .claude-plugin/project-config.json 2>/dev/null | grep -o '"allGuides"[[:space:]]*:[[:space:]]*true')
if [ -z "$ALL_GUIDES" ]; then
# Navigate to git project root (works from any subdirectory)
cd "$(git rev-parse --show-toplevel)"
# Verify it's an Edge Delivery Services project
ls scripts/aem.js
fi
IMPORTANT:
cd command above using the Bash toolproject-root/project-guides/If NOT skipped AND scripts/aem.js does NOT exist, respond:
"This skill is designed for AEM Edge Delivery Services projects. The current directory does not appear to be an Edge Delivery Services project (
scripts/aem.jsnot found).Please navigate to an Edge Delivery Services project and try again."
STOP if check fails. Otherwise proceed — you are now at project root.
YOU MUST SAVE THE FILE TO THIS EXACT PATH:
project-guides/DEVELOPER-GUIDE.md
BEFORE WRITING ANY FILE:
mkdir -p project-guidesproject-guides/DEVELOPER-GUIDE.mdWHY THIS MATTERS: Files must be in project-guides/ for proper organization and PDF conversion.
❌ WRONG: DEVELOPER-GUIDE.md (root)
❌ WRONG: docs/DEVELOPER-GUIDE.md
❌ WRONG: /workspace/DEVELOPER-GUIDE.md
✅ CORRECT: project-guides/DEVELOPER-GUIDE.md
MANDATORY OUTPUT: project-guides/DEVELOPER-GUIDE.pdf
STRICTLY FORBIDDEN:
fstab.yaml — it does NOT exist in most projects and does NOT show all sites.plain.html filesconvert_markdown_to_html tool — this converts the FULL guide to HTML with raw frontmatter visible, which is NOT what we wantproject-guides/REQUIRED WORKFLOW:
mkdir -p project-guides to ensure directory existsproject-guides/DEVELOPER-GUIDE.md (EXACT PATH - no exceptions)project-guides/DEVELOPER-GUIDE.pdf- [ ] 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 Professional PDF
Whenever this skill runs — whether the user triggered it directly (e.g. "generate developer guide") or via the handover flow — you must have the Config Service organization name before doing anything else. Do not skip this phase.
# Check if org name is already saved
cat .claude-plugin/project-config.json 2>/dev/null | grep -o '"org"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1
If no org name is saved, you MUST pause and ask the user directly:
"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."
IMPORTANT RULES:
AskUserQuestion with predefined options — ask as a plain text questionOnce you have the org name (either from saved config or user input), save it for future use:
# Create config directory if needed
mkdir -p .claude-plugin
# Ensure .claude-plugin is in .gitignore (contains auth tokens)
grep -qxF '.claude-plugin/' .gitignore 2>/dev/null || echo '.claude-plugin/' >> .gitignore
# Save org name to config file (create or update)
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 provided by the user.
# Get repository info
git remote -v | head -1
# Get branch info
git branch -a | head -10
Extract:
# Check for helix-config (very old projects)
ls helix-config.yaml 2>/dev/null && echo "Uses legacy helix-config" || echo "Uses Config Service (modern)"
Document: Project configuration method (Config Service for modern projects).
⚠️ MANDATORY DATA SOURCE — NO ALTERNATIVES ALLOWED
You MUST call the Config Service API. This is the ONLY acceptable source for site information.
❌ PROHIBITED APPROACHES (will produce incorrect results):
fstab.yaml — does NOT show all sites in repoless setupsREADME.md — may be outdated or incomplete✅ REQUIRED: Execute and save response:
ORG=$(cat .claude-plugin/project-config.json | grep -o '"org"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"org"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
# Save response to file - Phase 2 depends on this file
curl -s -H "Accept: application/json" "https://admin.hlx.page/config/${ORG}/sites.json" > .claude-plugin/sites-config.json
📁 REQUIRED ARTIFACT: .claude-plugin/sites-config.json
The response is a JSON object with a sites array (each entry has a name field). Extract site names and construct per-site URLs:
https://main--{site-name}--{org}.aem.page/https://main--{site-name}--{org}.aem.live/Multiple sites = repoless setup. Single site = standard setup.
Then fetch individual site config for code and content details:
AUTH_TOKEN=$(cat .claude-plugin/project-config.json | grep -o '"authToken"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"authToken"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
curl -s -H "x-auth-token: ${AUTH_TOKEN}" "https://admin.hlx.page/config/${ORG}/sites/{site-name}.json"
Example response:
{
"code": {
"owner": "github-owner",
"repo": "repo-name",
"source": { "type": "github", "url": "https://github.com/owner/repo" }
},
"content": {
"source": {
"url": "https://content.da.live/org-name/site-name/",
"type": "markup"
}
}
}
Extract from response:
code.owner / code.repo — GitHub repositorycontent.source.url — Content mountpath (e.g., https://content.da.live/org/site/)content.source.type — Content source type (markup, onedrive, google)⚠️ Do NOT use fstab.yaml — use Config Service API instead.
Record the result (repoless or standard) — it is used in the Local Development Setup section to decide whether to include the --pages-url flag for aem up.
# Check for Node version requirements
cat .nvmrc 2>/dev/null || cat package.json | grep -A2 '"engines"'
Read site config from Phase 1:
cat .claude-plugin/sites-config.json
# List top-level structure
ls -la
# List blocks
ls -la blocks/
# List scripts
ls -la scripts/
# List styles
ls -la styles/
# Check for templates
ls -la templates/ 2>/dev/null || echo "No templates folder"
CRITICAL: Only document files that were actually customized.
# Check commit history for a file (skip if only "Initial commit")
git log --oneline --follow {file_path} | head -5
# Check who modified a file (skip aem-aemy[bot] only files)
git log --format="%an - %s" --follow {file_path} | head -5
Rules for filtering:
| 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 |
| Commits with feature descriptions | Document - customized |
IMPORTANT: Files with only an "initial commit" are boilerplate defaults that were never modified. Do not document these - they add no value to the developer guide.
# List what aem.js exports (DO NOT MODIFY this file)
grep -E "^export" scripts/aem.js
Document which functions the project imports from aem.js (e.g., sampleRUM, loadHeader, loadFooter, decorateBlock, loadBlock, loadCSS). This helps developers understand which core utilities are available.
# Extract imports and key function signatures
grep -E "^import|^export|^function|^async function|buildAutoBlocks|loadTemplate|getLanguage|getSiteRoot|decorateMain|loadEager|loadLazy|loadDelayed" scripts/scripts.js
Document these customizations:
| Pattern to Look For | What to Document |
|---|---|
import statements | What it imports from aem.js and utils.js |
loadEager / loadLazy functions | Any custom logic added to E-L-D phases |
buildAutoBlocks function | Auto-blocking logic |
loadTemplate or template handling | Template system |
getLanguage or language detection | Multi-language setup |
getSiteRoot or site detection | Multi-site configuration (see also Phase 1.3) |
Custom decorateMain additions | Page decoration extensions |
| External script loading | Which phase loads it — flag if loaded in eager (performance risk) |
E-L-D check: Note any third-party scripts or heavy logic added to loadEager or loadLazy that could impact LCP. These should typically be in delayed.js.
# Extract imports and function calls to identify integrations
grep -E "^import|function|google|analytics|gtag|alloy|martech|OneTrust|launch|chatbot|widget" scripts/delayed.js
Document:
# Extract exported functions from utils
grep -E "^export|^function" scripts/utils.js 2>/dev/null || echo "No utils.js"
# List all script files
ls scripts/*.js
# Check which blocks/scripts import from utils
grep -rl "utils.js" blocks/ scripts/ 2>/dev/null
Document: Shared utility functions, their purposes, and which blocks/scripts import them.
# Check package.json for dependencies
grep -A 20 '"dependencies"' package.json 2>/dev/null | head -25
# Check for CDN imports in code
grep -r "cdn\|unpkg\|jsdelivr" scripts/ blocks/ --include="*.js" 2>/dev/null
# Get all CSS variables from styles.css
grep -E "^\s*--" styles/styles.css
Organize into categories:
| Category | Example Properties |
|---|---|
| Typography | --body-font-family, --heading-font-family, --font-size-* |
| Colors | --primary-color, --background-color, --text-color |
| Spacing | --spacing-*, --nav-height, --section-padding |
| Layout | --content-width, --grid-gap |
# Extract font-face declarations (font names, weights, formats)
grep -E "@font-face|font-family|font-weight|src:" styles/fonts.css 2>/dev/null
# Check fonts directory
ls fonts/ 2>/dev/null
Document:
# Find media query breakpoints
grep -E "@media.*min-width|@media.*max-width" styles/styles.css | sort -u
Standard EDS breakpoints:
Document any deviations from standard.
# Find section-related styles
grep -A 5 "\.section\." styles/styles.css
grep -A 5 "\.section\[" styles/styles.css
Document available section styles (dark, highlight, narrow, etc.)
Run all filtering silently — do not show output to the user.
aem-aemy[bot] commits (auto-generated)For each block that passed the filter, run:
# Get block purpose from JS comments/structure
head -30 blocks/{blockname}/{blockname}.js
# Get block variants from CSS
grep -E "^\." blocks/{blockname}/{blockname}.css | head -30
# Check for variant handling
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 (from code analysis) |
| 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 |
| Key Functions | Important internal functions |
grep "^import" blocks/{blockname}/{blockname}.js
Apply the same boilerplate filtering to models/*.json. Also exclude standard boilerplate models (_page.json, _section.json, _button.json, _image.json, _text.json, _title.json) if unchanged. If all models are boilerplate, skip this section in the output.
Apply the same boilerplate filtering to templates/*/. For each customized template, document its purpose, how it's applied (template: name in metadata), and what it changes.
project-guides/DEVELOPER-GUIDE.mdGenerate a markdown document with the following structure:
# [Project Name] - Developer Guide
## Quick Reference
| Resource | URL |
|----------|-----|
| Code Repository | {code.owner}/{code.repo} (from Config Service — may be GitHub or Cloud Manager) |
| Preview | https://main--{repo}--{owner}.aem.page/ |
| Live | https://main--{repo}--{owner}.aem.live/ |
| Local Dev | http://localhost:3000 |
## Architecture Overview
### Tech Stack
- Vanilla JavaScript (ES6+)
- CSS3 with Custom Properties
- No build step - files served directly
- Content from Document Authoring (DA)
### Project Structure
├── blocks/ # [X] blocks implemented ├── templates/ # [Y] templates (or N/A) ├── scripts/ │ ├── aem.js # Core library (DO NOT MODIFY) │ ├── scripts.js # Custom page decoration │ ├── delayed.js # Analytics, marketing tools │ └── utils.js # Shared utilities ├── styles/ │ ├── styles.css # Critical styles + design tokens │ ├── fonts.css # Font definitions │ └── lazy-styles.css # Non-critical styles └── icons/ # SVG icons
### Three-Phase Loading (E-L-D)
Edge Delivery Services uses a strict Eager-Lazy-Delayed loading strategy to achieve a Lighthouse score of 100. Every file and script must be placed in the correct phase.
| Phase | Purpose | What Loads | Performance Impact |
|-------|---------|------------|-------------------|
| **Eager** | Render above-the-fold content as fast as possible (LCP) | `styles/styles.css`, first section's blocks, `scripts/scripts.js` | Blocks LCP — keep minimal |
| **Lazy** | Load remaining page content after first paint | Remaining blocks, header, footer, `fonts.css`, `lazy-styles.css` | Runs after Eager completes — safe for non-critical UI |
| **Delayed** | Load non-essential third-party scripts | `scripts/delayed.js` (analytics, marketing tags, chat widgets) | Runs ~3s after page load — never blocks rendering |
**Rules for developers:**
- **Never add third-party scripts to `scripts.js`** — they block LCP. Always use `delayed.js`.
- **Never load fonts eagerly** — `fonts.css` is loaded lazily to avoid render-blocking.
- Blocks in the first section load eagerly; all others load lazily. This is automatic based on DOM position.
- The header and footer are loaded in the lazy phase via `loadHeader()` / `loadFooter()` from `aem.js`.
### Key Files & How They Connect
| File | Role | Connects To |
|------|------|-------------|
| `scripts/aem.js` | Core library — `loadBlock`, `loadCSS`, `decorateBlock`, `sampleRUM`, `loadHeader`/`loadFooter`. **DO NOT MODIFY.** | Imported by `scripts.js` and blocks |
| `scripts/scripts.js` | Entry point — orchestrates E-L-D phases (`loadEager` → `loadLazy` → `loadDelayed`). Contains `buildAutoBlocks`, `decorateMain`, template loading. | Imports from `aem.js`; may import `utils.js` |
| `scripts/delayed.js` | Loaded last via `loadDelayed()` — analytics, marketing tags, third-party scripts. | Called by `scripts.js` in delayed phase |
| `scripts/utils.js` | Shared helpers used across blocks and scripts. | Imported by blocks and `scripts.js` |
| `styles/styles.css` | Critical CSS + design tokens (CSS custom properties). Loaded eagerly. | Referenced by all blocks and pages |
| `styles/fonts.css` | Font `@font-face` declarations. Loaded lazily. | Font families referenced in `styles.css` |
| `styles/lazy-styles.css` | Non-critical global styles. Loaded lazily. | Supplements `styles.css` |
| `blocks/{name}/{name}.js` | Block logic — exports `default function decorate(block)`. Auto-loaded when the block class appears in DOM. | May import from `utils.js` or `aem.js` |
| `blocks/{name}/{name}.css` | Block styles — auto-loaded alongside block JS. | May use CSS custom properties from `styles.css` |
| `templates/{name}/{name}.js` | Template logic — loaded when page metadata has matching `template` value. Customizes page structure before blocks load. | Called by `scripts.js` via `loadTemplate()` |
**Execution flow:** page load → `scripts.js` → `loadEager()` (first section + eager blocks) → `loadLazy()` (remaining blocks, header, footer, `fonts.css`, `lazy-styles.css`) → `loadDelayed()` (loads `delayed.js`)
## Local Development Setup
### Prerequisites
- Node.js [version from .nvmrc]
- AEM CLI: `npm install -g @adobe/aem-cli`
### Setup Steps
```bash
# Clone repository (use the code repo URL from Config Service)
git clone {code-repo-url}
cd {repo}
# Install dependencies
npm install
# Start local server
aem up
If repoless/multi-site was detected in Phase 1.3, also include this subsection in the output:
Local Server — Repoless/Multi-Site
For repoless setups with multiple sites sharing one repository, you must specify the site's preview URL when starting the local server:
aem up --pages-url=https://main--{site}--{org}.aem.pageReplace
{site}and{org}with the actual site and organization names from the Config Service.Without
--pages-url, the AEM CLI cannot resolve content for the correct site and local preview will fail or show wrong content.
If the project is a standard single-site setup, omit the repoless subsection entirely — it would only confuse developers.
npm run lint
--body-font-family: [value];
--heading-font-family: [value];
--primary-color: [value];
--secondary-color: [value];
--background-color: [value];
--text-color: [value];
--spacing-s: [value];
--spacing-m: [value];
--spacing-l: [value];
--nav-height: [value];
| Name | Min-Width | Usage |
|---|---|---|
| Mobile | 0 | Default styles |
| Tablet | 600px | @media (min-width: 600px) |
| Desktop | 900px | @media (min-width: 900px) |
| Large | 1200px | @media (min-width: 1200px) |
| Family | Weights | Usage |
|---|---|---|
| [Font Name] | [weights] | [body/headings] |
| Block | Purpose | Variants | Key Features |
|---|---|---|---|
| [block-name] | [What it does] | variant1, variant2 | [Important details, gotchas] |
[Generate one row per customized block]
| Template | Purpose | Applied Via | Special Behavior |
|---|---|---|---|
| [template-name] | [What type of pages] | template: [name] | [What it does differently] |
[Generate one row per customized template. Skip templates with only "initial commit".]
blocks/{name}/{name}.jsblocks/{name}/{name}.cssexport default function decorate(block)styles/styles.cssscripts/delayed.jsscripts.js (blocks performance)| Environment | URL Pattern | Purpose |
|---|---|---|
| Local | http://localhost:3000 | Development |
| Feature Branch | https://{branch}--{repo}--{owner}.aem.page | PR testing |
| Preview | https://main--{repo}--{owner}.aem.page | Staging |
| Live | https://main--{repo}--{owner}.aem.live | Production |
feature/descriptionfix/descriptiondefault function decorate| Resource | URL |
|---|---|
| EDS Documentation | https://www.aem.live/docs/ |
| Developer Tutorial | https://www.aem.live/developer/tutorial |
| Block Collection | https://www.aem.live/developer/block-collection |
| E-L-D Loading | https://www.aem.live/developer/keeping-it-100 |
| Best Practices | https://www.aem.live/docs/dev-collab-and-good-practices |
[Add project-specific contacts]
---
### 5.1 Convert to Professional PDF (MANDATORY)
**THIS STEP IS NOT OPTIONAL. YOU MUST GENERATE THE PDF NOW.**
1. Save markdown to: `project-guides/DEVELOPER-GUIDE.md`
- File MUST start with YAML frontmatter:
```yaml
---
title: "[Project Name] - Developer Guide"
date: "[Full Date - e.g., February 17, 2026]"
---
```
- **Date format**: Always use full date with day, month, and year (e.g., "February 17, 2026"), NOT just month and year
2. **IMMEDIATELY after saving the markdown**, invoke the PDF conversion skill:
Skill({ skill: "project-management:whitepaper", args: "project-guides/DEVELOPER-GUIDE.md project-guides/DEVELOPER-GUIDE.pdf" })
3. Wait for PDF generation to complete (whitepaper skill auto-cleans source files)
**DO NOT:**
- Skip the PDF conversion step
- Tell user "PDF will be generated later" — generate it NOW
### 5.2 Deliver to User
After PDF is generated, inform the user:
"✓ Developer guide complete: project-guides/DEVELOPER-GUIDE.pdf"
---
## Output
**FINAL OUTPUT:** `project-guides/DEVELOPER-GUIDE.pdf`
All source files (.md, .html, .plain.html) are deleted after PDF generation. Only the PDF remains.
**Location:** `project-guides/` folder
---
## Success Criteria
**Data Source Validation (CRITICAL):**
- [ ] Config Service API was called (`https://admin.hlx.page/config/{ORG}/sites.json`)
- [ ] Site list came from API response, NOT from fstab.yaml or codebase analysis
- [ ] Repoless/standard determination came from Config Service, NOT inferred from code
**Content Validation:**
- [ ] Quick Reference with all project URLs
- [ ] Architecture overview accurate to project
- [ ] Design system fully documented (tokens, fonts, breakpoints)
- [ ] Project-specific blocks documented
- [ ] Custom scripts.js functions documented
- [ ] delayed.js integrations documented
- [ ] Templates documented (if applicable)
- [ ] Local development setup verified
- [ ] Common tasks have clear instructions
- [ ] Troubleshooting section covers common issues
- [ ] Resources linked
**Output Validation:**
- [ ] PDF generated successfully
- [ ] All source files cleaned up (only PDF remains)
---
## Tips for Clear Documentation
1. **Focus on what's unique** - Document project-specific implementations
2. **Use code examples** - Show actual code from the project
3. **Document the "why"** - Explain reasoning behind custom implementations
4. **Include gotchas** - Note any tricky behavior or edge cases
5. **Test the setup instructions** - Verify they work from scratch
6. **Keep it maintainable** - Don't over-document things that change often