Plugin check: Run
node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"— if it outputs a message, show it to the user before proceeding.
export-solution
Triggers an async Dataverse solution export, polls until complete, downloads the solution zip, and verifies it. Reads .solution-manifest.json to identify the solution; falls back to asking the user.
Prerequisites
- PAC CLI installed and authenticated
- Azure CLI installed and logged in
- Solution exists in the environment (run
setup-solutionfirst if needed)
Phases
Phase 0 — ALM plan gate
plan-almis the front door. When the user expresses an ALM intent (promote / ship / deploy / set up CI-CD / move to staging / push to prod), the orchestrator (/power-pages:plan-alm) should run first. This Phase 0 enforces that and is meant to fail closed when there's no plan, not to be a one-time check the user can dismiss forever.
Skip rule. If this skill was invoked as part of an active plan-alm orchestration, skip Phase 0 entirely and proceed to Phase 1. The gate helper exposes this via its inExecution block — pass through silently to Phase 1 when:
inExecution.status === "active"
The helper computes this from docs/.alm-plan-data.json — PLAN_STATUS === "In Execution" AND LAST_INVOCATION_AT within the last 60 minutes. check-alm-plan.js refreshes LAST_INVOCATION_AT automatically on every invocation that finds the plan in execution, so each in-chain skill keeps the chain alive for the next one — even multi-hour deploys (deploy-pipeline alone can take 60 min per stage) survive the window without the chain incorrectly de-classifying. Stalled chains (no heartbeat for > 60 min) reclassify as stale-heartbeat and Phase 0 gates fire normally so an abandoned plan doesn't silently bypass user confirmation.
When inExecution.status is anything other than "active" ("not-running", "stale-heartbeat", "no-plan"), run the Phase 0 gate flow below. Branch on the remaining helper fields:
Step 1 — Run the gate helper.
node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/check-alm-plan.js" \
--projectRoot "." \
--envUrl "{envUrl from .solution-manifest.json or pac env who, if available}" \
--token "{token, if Phase 1 already acquired one}" \
--solutionId "{solutionId from .solution-manifest.json, if available}"
The helper returns JSON with { exists, deferred, stale, staleness: { reason, detail }, generatedAt, planStatus, ... }. The freshness check requires env credentials + solutionId; without those the helper does an existence-only check.
Step 2 — Branch on the result.
| Result | Behavior |
|---|---|
deferred: true | The user has explicitly deferred ALM for this project (.alm-deferred marker present). Pass through silently to Phase 1 — do not nag. |
exists: false | The user hasn't run plan-alm yet. See Step 3. |
exists: true, stale: false | Plan is current. Pass through silently to Phase 1. |
exists: true, stale: true (reason: solution-modified) | The solution changed after the plan was generated. See Step 4. |
Step 3 — No plan. Tell the user:
<!-- gate: export-solution:0.no-plan | category=intent | cancel-leaves=nothing -->"No ALM plan exists for this project.
/power-pages:plan-almbuilds one — it detects the project state, asks about your promotion strategy (PP Pipelines vs Manual export/import), and orchestrates the right skills (including this one) in the right order. Want me to run plan-alm now?"
🚦 Gate (intent · export-solution:0.no-plan): Fail-closed entry gate when
check-alm-plan.jsreturnsexists:false. Helper-script-backed.
AskUserQuestion:
| Question | Header | Options |
|---|---|---|
Run /power-pages:plan-alm first? | ALM plan gate | Yes — run /power-pages:plan-alm now (Recommended), Continue without a plan (advanced — I know what I'm doing), Cancel |
- Yes (Recommended) → invoke
/power-pages:plan-alm. plan-alm's Phase 7 dispatches back into this skill at the appropriate stage. - Continue without a plan → set
BYPASSED_PLAN_GATE = trueand proceed to Phase 1. - Cancel → exit cleanly.
Step 4 — Stale plan. Tell the user:
<!-- gate: export-solution:0.stale-plan | category=intent | cancel-leaves=nothing -->"ALM plan exists from
{generatedAt}but the source solution has been modified since (at{solution.modifiedon}). Components may have changed. Re-runningplan-almwill refresh the analysis and the rendered HTML."
🚦 Gate (intent · export-solution:0.stale-plan): Fail-closed entry gate when
check-alm-plan.jsreturnsstale:true. Helper-script-backed.
AskUserQuestion:
| Question | Header | Options |
|---|---|---|
| Refresh the plan first? | ALM plan freshness | Refresh — re-run /power-pages:plan-alm (Recommended), Continue with the existing plan, Cancel |
- Refresh (Recommended) → invoke
/power-pages:plan-alm. After completion, re-run the Phase 0 helper once to confirm freshness; if still stale, surface the detail and proceed to Phase 1 anyway (don't infinite-loop). - Continue → set
STALE_PLAN_ACK = trueand proceed to Phase 1. - Cancel → exit cleanly.
Why this gate exists. Direct invocation of export-solution produces a zip without the orchestrator's pre-export completeness check. Users running this skill standalone often miss components that should have been added to the solution (cloud flows, env var values referenced by site settings, sample data references) and ship a zip that imports cleanly into staging but produces a broken site post-deploy. The pre-plan completeness check surfaces those gaps before any zip is built. The gate ensures plan-alm either ran (so completeness was verified and the export was scoped to the right solution lineage) or the user explicitly chose to bypass it.
Phase 1 — Verify Prerequisites
Create all tasks upfront at the start of this phase.
Tasks to create:
- "Verify prerequisites"
- "Identify solution"
- "Configure export"
- "Trigger async export"
- "Download solution zip"
- "Verify export"
- "Present summary"
Steps:
- Run
verify-alm-prerequisites.jswith--require-manifestto confirm PAC CLI auth, acquire a token, verify API access, and validate that.solution-manifest.jsonexists:
Capture output as JSON; extractnode "${CLAUDE_PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" --require-manifest.envUrl(store asenvUrl) and.token(store astoken). If the script exits non-zero, stop and explain what is missing (reference${CLAUDE_PLUGIN_ROOT}/references/dataverse-prerequisites.md).
Phase 1.5 — Ground in current ALM documentation
Reference:
${CLAUDE_PLUGIN_ROOT}/references/alm-docs-grounding.md
Cap this step at ~30 seconds. If MCP search / fetch errors out, log a one-line note and continue — this skill must remain runnable offline.
- Run
microsoft_docs_searchwith the query:Power Pages solution export managed unmanaged ExportSolutionAsync ALM. - Fetch
https://learn.microsoft.com/en-us/power-platform/alm/solution-concepts-alm(and at most one sister page on managed vs unmanaged or solution layering) in parallel viamicrosoft_docs_fetch. - Extract a one-paragraph summary of what Microsoft Learn currently says about export semantics, managed vs unmanaged implications, and async export polling. Compare against
${CLAUDE_PLUGIN_ROOT}/references/solution-api-patterns.mdand flag any divergence inExportSolutionAsync/DownloadSolutionExportDatasignatures. - Use the summary to inform Phase 2+ decisions. Do not silently change skill behavior — surface any divergence to the user as a soft warning before Phase 3.
Phase 2 — Identify Solution
- Look for
.solution-manifest.jsonin project root (usefindProjectRootorglob('**/.solution-manifest.json')) - If found: read
solution.uniqueName,solution.solutionId,environmentUrl- Verify environment URLs match (warn if