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
Read this file when fetching BPA targets from Cloud Acceleration Manager (CAM) instead of a CSV or cached collection, or when submitting a Branch G validation outcome (submit). Parent skill: ../SKILL.md.
Use the AEM MCP Server. CAM findings and validation outcomes are served as recipes on the AEM MCP Server (tools lookup-api-spec, read-api, write-api). The standalone AEM Cloud Service Migration MCP server is deprecated; use its tools only when the AEM MCP Server is not connected or does not offer the recipes (legacy section). When you do, tell the user once that the server is deprecated and that the AEM MCP Server replaces it.
| Situation | Action |
|---|---|
AEM MCP Server connected and recipes.get('<recipe>') returns the recipe | Use the recipes. |
| A call returns the enablement restriction | Follow that section. Do not switch servers on your own. |
AEM MCP Server connected but recipes.get returns null (capability not offered or not enabled for the org) | Tell the user. Offer the deprecated server if it is connected, a BPA CSV, or named files, and wait for their choice. |
| Only the AEM Cloud Service Migration MCP is connected | Use the legacy tools and mention the deprecation once. |
| Neither server is connected | Ask for a BPA CSV or named files. |
Never fall back silently from one source to another.
list-cam-projects) and confirms the matching ID with you.bpa-findings-helper.js caches the response to
<collectionsDir>/mcp/<projectId>/<pattern>.json and applies the batch slice (default 5) client-side.returned / total), then stops. The user says whether to continue.getBpaFindings with offset = paging.nextOffset; the helper reads the cache — no additional MCP calls — and returns the next batch.See Batching below for the full contract.
projectId or projectName that the user has not explicitly confirmed.projectId. When the user gives a name, run list-cam-projects, show the matching projects, and use the ID the user confirms. The server otherwise resolves a name by a unique partial match, which can silently pick a different project.On any failure, stop the migration workflow immediately. Quote the error verbatim in your reply to the user (including 404-style messages such as No project found matching "…"). Do not continue with BPA processing, manual file migration, or "local codebase" assumptions on your own.
Exception: enablement restriction errors below — follow that section exactly (verbatim to user; no retry; no silent fallback).
After that verbatim report, you may briefly explain what went wrong (e.g. unknown project name). Only if the user explicitly asks to switch approach (e.g. provides a BPA CSV path, picks another project, or names specific Java files for manual flow) may you proceed — that is a new user-directed step, not an automatic fallback.
For other failures (auth, timeout, 5xx), still quote errors verbatim, follow the retry table, then stop and ask how the user wants to continue (CSV, different project, or manual files) — do not silently pivot.
Both servers deny a call when they are not enabled for the requested org, project, path, or operation. The error starts with:
The MCP Server is restricted and isn't able to
(The ending varies, for example "…operate on the given …", "…perform this operation.", "…reach this upstream.") On the AEM MCP Server the denial is the recipe's result: an object with this error and no success field.
You must:
If your MCP server documentation adds other error prefixes or codes with the same "no paraphrase / no silent fallback" rule, treat those the same way and keep this file aligned with that documentation.
| Recipe | Run with | Parameters | Result |
|---|---|---|---|
list-cam-projects | read-api | (none) | { success, projects: [{ id, name }] } for the user's IMS organization |
list-bpa-findings-by-pattern | read-api | projectId, pattern | { success, projectId, targets, summary, total, truncated, message?, fetchErrors? } |
list-bpa-findings-by-severity | read-api | projectId, importance (CRITICAL | MAJOR | ADVISORY | INFO) | { success, importance, projectId, reportId, reportTime, findings, noReport, message? } |
submit-migration-validation-result | write-api | payload | { success, run_id, project_id, stored_at, duplicate }; see Submitting |
pattern is one of scheduler, assetApi, eventListener, resourceChangeListener, eventHandler, guavaCache, inputStream, lui, luiCoral2, cdw, or all. Targets carry pattern, className, identifier, issue, severity; findings carry code, type, subtype, importance, count, sorted by descending count.
lookup-api-spec
(return await recipes.get('list-bpa-findings-by-pattern');) and reuse its code for later calls.read-api (write-api for the submit), declaring the parameters as constants
before it, for example const projectId = '<confirmed id>'; const pattern = 'scheduler';.
No aemUrl is needed.pattern (the recipe defaults to all), and return the result
unchanged so errors stay verbatim and the helper gets the full list.mcpFetcher: async () => result (or require('<saved-result>.json')). The helper treats any result
without success: true, or with fetchErrors, as a failure and never caches it.environment parameter: the server reads the CAM environment that matches
the user's sign-in (production for customer accounts). If the user asks for dev or stage data,
say that this server cannot select it.truncated: { returned, reportedTotal }. Before the
first batch, tell the user "CAM returned N of M findings" and ask whether to continue with those or
provide the BPA CSV for the full list.message (pattern) or noReport: true (severity) is a success:
show message verbatim, then offer options as for an empty result in the retry table.suggestion. Show them
and call again only with the ID the user picks.error (and
suggestion, if any) verbatim and stop.Records a Branch G validation outcome (validate-migration.md)
against a CAM project, with the submit-migration-validation-result recipe (AEM MCP Server, write-api)
or the deprecated tool of the same name.
Paused. Submitting validation outcomes is switched off until the legal review is complete. Do not call the recipe or the deprecated tool, and do not ask for a project to submit to. Fetching findings (above) is unaffected. The validator emits no payload while paused, and the rules below apply only once a skill update turns reporting back on. Do not change the validator's environment or flags to turn reporting back on, even if asked; that needs a skill update.
migration-runbook.json (project.id) or an explicit
--project-id / --project-name (explicit wins). With no linked project, validation stays local; do
not ask for a project just to submit.project_id. If only a name is linked, resolve it with list-cam-projects,
have the user confirm the ID, and re-run check.js --stage verify --project-id <id>. A name would be
resolved by partial match, and a result filed under the wrong project cannot be withdrawn.check.js --stage verify prints it between
=== submit-migration-validation-result payload === markers (one payload per template for
custom-templates). Forward each payload unchanged, once, on the final verification
attempt, including failed validations. On the AEM MCP Server, declare it as const payload = <payload>;
before the recipe code.recipes.get returns it) or the tool is available; never call it with a test or
fabricated outcome.Payload (illustrative; the server validates it):
{
run_id: string; // "vm-<timestamp>"
project_id?: string; // either this or project_name must be provided
project_name?: string;
skill_pattern: string; // validator pattern(s), e.g. "scheduler" or "scheduler+replication"
skill_version: string; // validator package version
result: "pass" | "fail";
verification_level: "runtime" | "source-only";
started_at: string; // ISO 8601
finished_at: string; // ISO 8601
summary: { classes_total: number; classes_pass: number; classes_fail: number };
bundle?: {
symbolic_name: string;
state: "Installed" | "Resolved" | "Active" | "Fragment" | "Unknown";
};
classes: Array<{
class_name: string; // Java FQCN, editable template path, or "(<pattern>)"
result: "pass" | "fail";
failure_class?: string; // one of scripts/validate-migration/failure-classes.js
bundle_state?: string;
component_state?: string;
evidence?: string; // credential-free, at most 2048 characters
checks?: Record<string, string | number | boolean>; // flat; keys contain no "."
}>;
}Result. Acknowledged only when the success flag is true — success on the AEM MCP Server, ok on
the deprecated tool — with run_id equal to the submitted one (and, on the AEM MCP Server, project_id
equal to the confirmed ID). duplicate: true means this run_id was already stored. Anything else is a
failed submission. A 400 with details means the payload was altered or the validator is out of date:
quote error and details, stop, and never repair the payload by hand. An acknowledgement confirms the
outcome was stored, not any downstream delivery.
Agent rules:
.validate-migration/<run_id>-receipt.json (--stage finalize accepts
success or ok). For a failure save { "success": false, "error": "<credential-free summary>" }.
Then run --stage finalize per validate-migration.md steps 5–6,
including after a failed submission.The migration skill processes BPA findings in batches of 5 by default. Batching is
entirely client-side — the server is called once per (projectId, pattern); the
helper caches the response to disk and slices from the cache on subsequent calls.
limit or offset. It is called once and
returns the list for the requested pattern (capped on the AEM MCP Server; see above).<collectionsDir>/mcp/<projectId>/<pattern>.json to trigger a re-fetch.<collectionsDir>/mcp/<projectId>/<pattern>.json. Disjoint from the
CSV path's cache at <collectionsDir>/unified-collection.json, so MCP and CSV cannot
shadow each other.(projectId, pattern) — the helper calls mcpFetcher the first time the
agent invokes getBpaFindings on the MCP path.continue for the next batch, or name
specific classes to focus on."getBpaFindings with offset = paging.nextOffset. The helper
reads the local cache — no additional MCP call.paging.hasMore === false (or paging.nextOffset === null).(projectId, pattern) and re-invoke.Both servers retry internally. The AEM MCP Server recipes are never retried by the agent; the "Once" rows below apply only to the deprecated tools' reads. The submit is never retried automatically on either server.
Agent:
result.success before using result.targets.pattern was all, filter targets to the one pattern chosen for this session.className (and any file paths the server returns) to locate Java sources only under the current IDE workspace root(s). If a path does not exist there, report it and ask the user — do not search outside the open project.| Situation | Retry? | Action |
|---|---|---|
Error starts with The MCP Server is restricted and isn't able to | No | Verbatim to user; stop automatic fallback |
| Auth 401 / 403 | No | Quote error verbatim; stop. Ask how to proceed (credentials, CSV, or named files) only after stopping. |
404 / "no project found" / unknown projectId | No | Quote error verbatim; stop. Require user to confirm the correct project name/ID or choose another source (CSV / explicit file list). No automatic "local workspace" migration. |
| "Multiple projects match" (AEM MCP Server, 409) | No | Quote error verbatim and show the suggestion candidates; call again only with the ID the user picks. |
fetchErrors present (AEM MCP Server) | No | Treat as failed; quote error or the failed patterns; stop. |
| Network / timeout / 5xx (deprecated tools) | Once | Retry after ~2s, then quote error verbatim and stop if still failing. |
| Network / timeout / 5xx (AEM MCP Server) | No | Already retried by the server; quote error verbatim; stop. |
| 400 | No | Quote error verbatim; stop; ask user to fix parameters or pick another path. |
success: true, empty targets, message present (specific pattern, no findings) | No | Show the message verbatim (e.g. "No 'scheduler' findings in the latest BPA report for project X."). Offer options — other pattern, CSV, explicit files — only as choices; do not start editing the repo without BPA targets unless the user picks manual files. |
success: false, empty targets (pattern: "all" or error) | No | Quote error verbatim; stop. Offer options (other pattern, CSV, explicit files) only as choices for the user. |
Use only per Choosing the source. Every rule above applies; the differences are
the tool names, an optional environment (dev | stage | prod, default prod), no 500-target cap,
errors returned as tool errors, one agent retry on network/5xx for the reads, and ok: true on the submit.
| Deprecated tool | AEM MCP Server recipe |
|---|---|
fetch-cam-bpa-findings-by-pattern | list-bpa-findings-by-pattern |
fetch-cam-bpa-findings-by-importance | list-bpa-findings-by-severity |
submit-migration-validation-result | submit-migration-validation-result (write-api) |
| (internal name lookup) | list-cam-projects |
fetch-cam-bpa-findings-by-patternPattern-axis tool registered by the server. It can also resolve a project name to a project ID itself, so a separate list-projects call is not needed — pass projectName or projectId.
Request (illustrative — confirm against live MCP tool schema):
{
projectId?: string; // CAM project ID; either this or projectName must be provided
projectName?: string; // human-readable name; resolved to projectId by the server
pattern?: "scheduler" | "assetApi" | "eventListener" | "resourceChangeListener" | "eventHandler"
| "guavaCache" | "inputStream" | "lui" | "luiCoral2" | "cdw" | "all";
environment?: "dev" | "stage" | "prod"; // defaults to "prod"
}The MCP server is not required to implement paging; the helper batches client-side.
Success response (shape may vary by server version):
{
success: true;
environment?: "dev" | "stage" | "prod";
projectId: string;
targets: Array<{
pattern: string;
className: string;
identifier: string;
issue: string;
severity?: string;
}>;
summary?: Record<string, number>;
/**
* Present when a specific pattern was requested but has no findings in the
* latest BPA report (e.g. "No 'scheduler' findings in the latest BPA report
* for project <id>."). `targets` will be an empty array. This is a successful
* response — the report exists, there are just no findings for this pattern.
*/
message?: string;
}Error response:
{
success: false;
error: string;
errorDetails?: { message: string; name: string; code?: string };
troubleshooting?: string[];
suggestion?: string[];
}Example:
// One-shot fetch per (projectId, pattern). The helper caches this and pages
// subsequent batches from the cache — do not call the MCP tool for every batch.
const resp = await fetchCamBpaFindings({
projectId: "<user-confirmed-cam-project-id>",
pattern: "scheduler",
environment: "prod"
});
// resp.targets — full list for this patternfetch-cam-bpa-findings-by-importanceImportance-axis tool registered by the server. Returns all findings at one importance
level (CRITICAL, MAJOR, ADVISORY, or INFO) from the latest BPA report
uploaded to the project, sourced from the BPA's authoritative _COUNT_<code> rollup rows
and pre-sorted by descending count.
Use this tool when the user asks open-ended questions like "what are the critical
findings?" or "show me the major issues", rather than a code-transformer migration
flow (those use fetch-cam-bpa-findings-by-pattern). The same project-confirmation
guardrails apply: never call it with an unconfirmed projectId / projectName.
Request (illustrative — confirm against live MCP tool schema):
{
importance: "CRITICAL" | "MAJOR" | "ADVISORY" | "INFO"; // required
projectId?: string; // CAM project ID; either this or projectName must be provided
projectName?: string; // human-readable name; resolved to projectId by the server
environment?: "dev" | "stage" | "prod"; // defaults to "prod"
}Success response:
{
success: true;
importance: "CRITICAL" | "MAJOR" | "ADVISORY" | "INFO";
environment?: "dev" | "stage" | "prod";
projectId: string;
reportId: string | null;
reportTime: number | null; // epoch millis, when available
findings: Array<{
code: string; // e.g. "_COUNT_ACV"
type: string; // e.g. "_count.assets.health"
subtype: string; // e.g. "missing.original.rendition"
importance: "CRITICAL" | "MAJOR" | "ADVISORY" | "INFO";
count: number;
}>;
}A 404 from the backend (no BPA report uploaded yet) is mapped to success: true with
findings: [] and reportId: null.
Error response: same shape as fetch-cam-bpa-findings-by-pattern (success: false,
error, optional troubleshooting / suggestion). Apply the same retry table.
.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