Plugin check: Run
node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"— if it outputs a message, show it to the user before proceeding.
plan-alm
An 8-phase orchestrator that gathers ALM strategy from the user, generates an HTML deployment plan, gets approval, then executes the plan by calling existing skills in sequence.
Overview
This skill detects the current project state (existing solution, pipeline), asks targeted questions about the desired promotion strategy (Power Platform Pipelines or Manual export/import), generates a visual docs/alm-plan.html, gets user approval, and then invokes setup-solution, setup-pipeline (or export-solution), and deploy-pipeline (or import-solution) in the correct order.
Do NOT create tasks at the start — strategy is unknown until Phase 2 completes. Create all tasks in Phase 3 once the strategy is determined.
Phase 1 — Detect Project State
Do NOT create tasks yet. Use natural language progress reporting only during this phase.
Steps:
-
Detect prior ALM deferral for this project. Before any discovery work, check whether the project root contains a
.alm-deferredmarker file. The marker is written by users who explicitly opted ALM-skill validators out of "missing artifacts" warnings (e.g. "this site is handled separately" or "ni-dev — no ALM"). If a user is now invokingplan-alm, we should surface that the marker is present and ask what to do, rather than silently proceeding (which would build a plan the user previously decided not to maintain) or silently removing the marker (which would re-enable nags on every other ALM skill).
<!-- gate: plan-alm:1.deferral | category=progress | cancel-leaves=deferral-marker -->node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/check-alm-plan.js" --projectRoot "."🚦 Gate (progress · plan-alm:1.deferral):
.alm-deferredmarker present — continue and remove, continue and keep marker, or cancel. Determines whether downstream ALM skills resume gate enforcement.The helper returns
{ deferred, deferral, ... }. Ifdeferred === true, read the deferral reason (deferral.reasonor the raw marker text) and ask viaAskUserQuestion:"This project has an
.alm-deferredmarker —{reason}. ALM was previously deferred here, so the other ALM skills (setup-solution,setup-pipeline,deploy-pipeline, …) skip their plan-completeness checks for this project. How would you like to proceed?"Question Header Options How would you like to proceed? ALM deferral marker Continue planning and remove the marker (Recommended), Continue planning but keep the marker (record deferral context in plan), Cancel - Continue and remove marker (Recommended) → delete
.alm-deferred(the user is re-engaging with ALM). SetDEFERRAL_CLEARED = trueand proceed to step 1. - Continue and keep marker → set
DEFERRAL_PRESERVED = trueandDEFERRAL_REASON = {reason}. Proceed to step 1. Surface a one-line note in the Phase 1 step 9 user report (e.g. "Note:.alm-deferredis preserved — other ALM skills will continue to skip plan-completeness checks for this project.") so the user remembers the marker remains in effect after planning. - Cancel → exit cleanly (don't touch the marker).
If
deferred === false, skip this step silently and proceed to step 1. - Continue and remove marker (Recommended) → delete
-
Resolve the site identity from the local project. ALM skills are normally invoked from a site-root directory where
pac pages download-code-site(or a create-site scaffold followed by a deploy) has written.powerpages-site/website.yml. That YAML file is the source of truth forwebsiteRecordIdandsiteName.Resolution order (first match wins):
.powerpages-site/website.yml(preferred, present for every deployed site) — read with theReadtool and extract:idfield →websiteRecordIdnamefield →siteName(the file uses short keys; it isname:, notadx_name:)
powerpages.config.json(fallback, used during plugin development from this repo root or for sites scaffolded but not yet deployed) — readsiteNameandwebsiteRecordId.
If neither is found, stop with:
"No Power Pages site found in the current directory. Run this skill from your site project root (where
.powerpages-site/exists afterpac pages download-code-site). If you haven't created the site yet, run/power-pages:create-sitefirst."environmentUrlis always re-confirmed frompac env whoin step 4 — it does not need to come from either source. -
Check for
.solution-manifest.jsonin the project root:- Store
SOLUTION_DONE = trueif found,falseotherwise - If found, read
solution.uniqueNameand store asSOLUTION_UNIQUE_NAME
- Store
-
Check for
docs/alm/last-pipeline.jsonin the project root:- Store
PIPELINE_DONE = trueif found,falseotherwise - If found, read
pipelineNameandstages[]for later use
- Store
-
Run silently:
pac env whoCapture the
Environment URLand display name. Store asDEV_ENV_URLandDEV_ENV_NAME. -
Run silently:
pac env list --output json 2>/dev/nullStore output as
ENV_LISTfor pre-filling environment URLs in Phase 2. -
Acquire dev environment token (silently):
node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" --envUrl "{DEV_ENV_URL}"Store
.tokenasDEV_TOKENand.userIdasuserId. If this fails (auth error), setDEV_TOKEN = nulland continue — contents discovery will be skipped gracefully. -
Discover and classify site settings (if
DEV_TOKENis available andwebsiteRecordIdis known):Use Node.js
httpsmodule to query. Paginate via@odata.nextLink— sites with > 500 settings would otherwise silently truncate, dropping tier classifications and underreportingplannedEnvVarCount. SendPrefer: odata.maxpagesize=5000so Dataverse emits the continuation link, then loop until exhausted:GET {DEV_ENV_URL}/api/data/v9.2/mspp_sitesettings?$filter=_mspp_websiteid_value eq '{websiteRecordId}'&$select=mspp_name,mspp_value&$top=5000 Authorization: Bearer {DEV_TOKEN} Prefer: odata.maxpagesize=5000 OData-MaxVersion: 4.0 OData-Version: 4.0 Accept: application/jsonOn each response, append
value[]to the running array. If@odata.nextLinkis present, GET that URL with the same headers (no need to re-add the filter — the nextLink already encodes the query). Stop when the response has no@odata.nextLink. Cap at 100 iterations for safety.Classify the returned settings using
${CLAUDE_PLUGIN_ROOT}/scripts/lib/classify-site-settings.js— the single source of truth for the credential regex and tier mapping shared withsetup-solutionPhase 5. Either pipe the JSON array of{name, value}rows into the script's stdin (CLI mode) orrequire()it inline:echo '<JSON array of {name,value}>' \ | node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/classify-site-settings.js"Output (the four-bucket shape that downstream phases +
setup-solutionconsume directly):SITE_SETTINGS_DATA = { keepAsIs: [{name}], // regular settings (Tier 3 — Search/Bootstrap/WebApi/feature flags) authNoValue: [{name}], // Authentication/* or AzureAD/* with empty value (Tier 2b — added as-is, set in target env) promoteToEnvVar: [{name, value}], // Authentication/* or AzureAD/* with value (Tier 2a — setup-solution offers env-var promotion) credentialNeedsDecision: [{name, value}] // ConsumerKey/ConsumerSecret/ClientId/ClientSecret/AppSecret/AppKey/ApiKey/Password (Tier 1 — bulk-with-override prompt in setup-solution Phase 5.4.C) }Tier semantics in plain English (so reviewers reading the plan know what each bucket implies):
- **Tier 1 (`credenti