Skip to content

Sync/upstream 2026 05 28 - #1

Merged
Hjaf merged 4 commits into
mainfrom
sync/upstream-2026-05-28
May 28, 2026
Merged

Sync/upstream 2026 05 28#1
Hjaf merged 4 commits into
mainfrom
sync/upstream-2026-05-28

Conversation

@Hjaf

@Hjaf Hjaf commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Upstream Sync

Merges 3 upstream commits from microsoft/power-platform-skills into this Equinor fork.

Upstream Commit Range

3b2009f..2923950 (upstream/main)

Commits Included

Commit Description
2923950 docs: update Power Pages skill list (microsoft#174)
e55e1d3 feat(power-pages): add security skills (microsoft#151)
2c82e39 Power Pages ALM: 10 new skills + 45 helpers + 6 reference docs (microsoft#167)

Scope

  • 178 files changed (163 added, 15 modified), all in plugins/power-pages/
  • +49,684 lines of new content
  • No conflicts with Equinor-specific content

Key Additions

  • ALM skills: plan-alm, setup-solution, export-solution, import-solution, setup-pipeline, deploy-pipeline, ensure-pipelines-host, force-link-environment, configure-env-variables, diagnose-deployment
  • Security skills: scan-site, manage-firewall, manage-headers, security-review
  • 45 helper scripts in scripts/lib/ for ALM automation
  • 6 reference docs (ALM grounding, CI/CD patterns, deployment error catalog, etc.)
  • 1 GitHub Actions workflow for ALM lint

Merge Strategy

  • Direct merge (no conflicts). No Equinor-specific content existed in modified files.
  • Equinor docs/equinor-alignment/ content untouched.

Review Record

The existing power-pages.json review record (dated 2026-05-27) already documents these upstream additions and passes schema validation. Status remains defer pending detailed security review of the expanded surface area.

Validation

  • node scripts/validate-plugin-reviews.js — all 5 review records pass ✓

Owner Decision Required

  • Detailed review of 45 new ALM helper scripts (network access, secret handling)
  • Security skills interact with WAF and live sites — confirm acceptable for controlled pilot
  • EDS and Tech Radar assessment for new dependencies

tyaginidhi and others added 4 commits May 26, 2026 11:42
…te-to-pipeline lifecycle (microsoft#167)

* Setup-solution skill verified for 100 components site

* Fix export-solution bugs found during live testing against IdeaSphere

- AsyncJobId → AsyncOperationId (actual field name in ExportSolutionAsync response)
- Fix statecode table: Succeeded is statecode=3 not 2
- Fix grep case: solution.xml is lowercase in zip, use grep -i

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix import-solution bugs found during live testing against IdeaSphere

- ImportJobId → ImportJobKey (actual field name in ImportSolutionAsync response)
- StageSolutionUploadId not accepted by ImportSolutionAsync — always use CustomizationFile
- Fix solution.xml grep case in import SKILL.md (grep -qi)
- Clarify StageSolution is pre-flight only, does not alter import call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add AttachmentBlocked import error pattern; fix import SKILL.md case bug

- deployment-error-catalog: add Pattern 11 for AttachmentBlocked (-2147188706)
  discovered during live testing against portal environment
- import SKILL.md: fix grep -q Solution.xml → grep -qi solution.xml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add Phase 5b: resolve AttachmentBlocked errors in import-solution

When ImportSolutionAsync fails with AttachmentBlocked (-2147188706):
1. Identify which extensions in the solution zip are on the blocked list
2. Explain the issue to the user clearly
3. Ask explicit consent before modifying environment settings
4. Remove only the specific blocked extensions (preserve others)
5. Retry the import

Follows same consent pattern as deploy-site Phase 6 (blocked JS handling).
Discovered during live testing against the portal environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Clarify Phase 5b consent: invoke AskUserQuestion directly, not as chat text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* export-solution: ask managed vs unmanaged via AskUserQuestion directly

Replace prose description with explicit AskUserQuestion invocation.
User answers live before export proceeds — not via chat text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove er-diagram-ideasphere.png — not part of skill code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* power pages alm using solutions (#47)

* Setup-solution skill verified for 100 components site

* Fix export-solution bugs found during live testing against IdeaSphere

- AsyncJobId → AsyncOperationId (actual field name in ExportSolutionAsync response)
- Fix statecode table: Succeeded is statecode=3 not 2
- Fix grep case: solution.xml is lowercase in zip, use grep -i

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix import-solution bugs found during live testing against IdeaSphere

- ImportJobId → ImportJobKey (actual field name in ImportSolutionAsync response)
- StageSolutionUploadId not accepted by ImportSolutionAsync — always use CustomizationFile
- Fix solution.xml grep case in import SKILL.md (grep -qi)
- Clarify StageSolution is pre-flight only, does not alter import call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add AttachmentBlocked import error pattern; fix import SKILL.md case bug

- deployment-error-catalog: add Pattern 11 for AttachmentBlocked (-2147188706)
  discovered during live testing against portal environment
- import SKILL.md: fix grep -q Solution.xml → grep -qi solution.xml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add Phase 5b: resolve AttachmentBlocked errors in import-solution

When ImportSolutionAsync fails with AttachmentBlocked (-2147188706):
1. Identify which extensions in the solution zip are on the blocked list
2. Explain the issue to the user clearly
3. Ask explicit consent before modifying environment settings
4. Remove only the specific blocked extensions (preserve others)
5. Retry the import

Follows same consent pattern as deploy-site Phase 6 (blocked JS handling).
Discovered during live testing against the portal environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Clarify Phase 5b consent: invoke AskUserQuestion directly, not as chat text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* export-solution: ask managed vs unmanaged via AskUserQuestion directly

Replace prose description with explicit AskUserQuestion invocation.
User answers live before export proceeds — not via chat text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove er-diagram-ideasphere.png — not part of skill code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add hotfix-solution skill for incremental Power Pages deployments

7-phase skill: discover components modified in a time window, package
into a timestamped hotfix solution, export (managed/unmanaged), and
import to a target environment with full AttachmentBlocked remediation.

- skills/hotfix-solution/SKILL.md — full skill definition
- skills/hotfix-solution/scripts/validate-hotfix.js — validates .last-hotfix.json
- AGENTS.md — updated with skill entry, hooks, validate script, shared refs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add hotfix-solution skill for incremental Power Pages deployments

7-phase skill: discover components modified in a time window, package
into a timestamped hotfix solution, export (managed/unmanaged), and
import to a target environment with full AttachmentBlocked remediation.

- skills/hotfix-solution/SKILL.md — full skill definition
- skills/hotfix-solution/scripts/validate-hotfix.js — validates .last-hotfix.json
- AGENTS.md — updated with skill entry, hooks, validate script, shared refs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add setup-pipeline and deploy-pipeline skills; remove generate-pipeline (#68)

- Replace generate-pipeline (YAML-only stub) with setup-pipeline: full PP Pipelines implementation via OData API, plus GitHub/ADO coming-soon stubs
- Add deploy-pipeline: creates stage runs, validates packages, polls stagerunstatus, handles approval gates, retries, and writes .last-deploy.json
- cicd-pipeline-patterns.md: major expansion with HAR-confirmed API patterns, source code findings (RetryFailedDeploymentAsync, cancel, scheduled deploy, redeployment, platform host BAP API), and live-test bug fixes (18 total)
- References in setup-solution, export-solution, hotfix-solution, solution-api-patterns updated from generate-pipeline → setup-pipeline
- AGENTS.md updated with new skill descriptions, hooks, and architecture tree

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Deployment variables, CI/CD pipeline deploy, and setup-solution improvements (#75)

* Add configure-env-variables skill and update deploy-pipeline for env var support

- New skill: configure-env-variables — creates environmentvariabledefinition records,
  adds them to solution (componenttype 380), generates deployment-settings.json with
  per-stage override values; guides UI step for mspp_sitesetting source linking
- deploy-pipeline: reads deployment-settings.json and injects deploymentsettingsjson
  in stage run PATCH so env var values are overridden per target stage at deploy time
- setup-solution: adds configure-env-variables to suggested next steps

Validated end-to-end against IdeaSphere: ids_LocalLoginEnabled=true in dev,
deployed to staging as false via deploymentsettingsjson injection (stage run abdc2380).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix setup-solution: include tables, Web API settings, and table permissions

Analysis of BYOC Supplier Portal solution (BYOCSupplierPortal_1_0_0_1.zip) revealed
three gaps that cause deployed sites to not work in target environments:

1. Dataverse tables not in solution: added Step 5.3 to read .datamodel-manifest.json
   and add each custom table as ComponentType=1 (EntityDefinitions/MetadataId)

2. Site settings split: replaced all-or-nothing site settings prompt with category-based
   approach — Webapi/* and feature flags included by default, only OAuth secrets
   (ClientSecret, AppSecret, ConsumerSecret) excluded by default

3. Table permissions (powerpagecomponenttype=18) explicitly called out as always-included

Also added Patterns 12/13/14 to deployment-error-catalog.md covering these three failure
modes, and documented Dataverse table AddSolutionComponent pattern in solution-api-patterns.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* setup-solution: add site language, full manifest review, table schema note

- Site language (powerpagesitelanguage, componenttype ~10375) is NOT auto-included
  by AddRequiredComponents:true on the website — must be discovered and added
  explicitly. Confirmed against IdeaSphere: 10375 was missing from solution.

- New Step 5.4: present full manifest to user before writing anything — shows
  website, site language(s), all component categories with counts, site settings
  split (Web API / feature flags / auth config / OAuth secrets), and Dataverse
  tables. User can deselect categories or tables before proceeding.

- Table schema note: solutions carry entity definitions (columns, relationships,
  forms) but NOT data/records. Explicit callout added so users know data migration
  is a separate step.

- solution-api-patterns.md: added site language componenttype (~10375) to the
  known values table + discovery query pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* setup-solution: multi-select OAuth secrets as env vars

Phase 5 now has a dedicated Step 5.4 asking which (if any) OAuth secret site
settings to convert to environment variables instead of just excluding them.
Uses AskUserQuestion with multiSelect:true listing each secret individually,
plus 'Convert all' and 'Exclude all' convenience options.

Converted secrets get environmentvariabledefinition records (type=Secret/100000003)
created and added to the solution as componenttype 380. Phase 7 summary includes
a UI linking checklist with direct Power Pages Management URLs for each converted
secret (same UI-only limitation as configure-env-variables skill).

Step numbering updated: 5.4=OAuth secrets, 5.5=full manifest confirm, 5.6=add components.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* setup-solution: always discover tables from env, include-all as first option

- Step 5.2D: always query EntityDefinitions directly (IsCustomEntity=true,
  IsManaged=false) rather than relying solely on .datamodel-manifest.json.
  Groups by publisher prefix, presents all custom unmanaged tables found.
  .datamodel-manifest.json is still checked but not the only source.

- Step 5.5 table multi-select: 'Include all N tables (Recommended)' is now
  the first option (pre-selected default), followed by individual table options,
  with 'Exclude all tables' last.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* setup-solution: automate OAuth secret linking + add deployment path choice

Phase 5.4: link site settings to env vars via OData PATCH (EnvironmentValue nav
property + if-match/clienthost headers) — no UI step needed (HAR-verified Fix 19).
Phase 7: AskUserQuestion for PP Pipelines (recommended) vs export/import vs later.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* import-solution + deploy-pipeline: env var prompting and post-deploy activation check (#77)

* deploy-pipeline: proactively prompt for env var values before deploy
import-solution: set env var values in target after import (Phase 6b)

deploy-pipeline Phase 5 now queries solution for env var definitions,
diffs against deployment-settings.json, and prompts for any unconfigured
values before calling DeployPackageAsync — not just when validation flags them.

import-solution Phase 6b queries target for env var definitions that landed
without values and prompts the user to set them, including Secret type vars
that need target-specific values (e.g. different OAuth credentials per env).

* Add post-deployment site activation check to import-solution and deploy-pipeline

After a successful import or pipeline deploy, both skills now check whether
the Power Pages site is already activated in the target environment. If not,
the user is asked for consent and /power-pages:activate-site is invoked to
handle subdomain selection, confirmation, and provisioning.

- import-solution: Phase 6c queries for componentType 10374, runs
  check-activation-status.js (PAC already on target), and invokes
  activate-site if not provisioned.
- deploy-pipeline: Phase 7.5 does the same but temporarily switches PAC CLI
  to the target env via `pac env select`, then switches back to source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* deploy-pipeline: tabbed deploy history + solution contents; plan-alm: per-stage activation (#87)

* Add ALM deployment plan skill, 16 shared lib scripts, and solution packaging improvements

- New `plan-alm` skill: 8-phase orchestrator that generates an HTML ALM plan
  (docs/alm-plan.html) with branched question flow for PP Pipelines vs Manual path,
  then executes the plan by invoking existing skills in sequence
- 16 new shared lib scripts in scripts/lib/ covering ALM prerequisites, solution
  management, PP Pipelines API operations, and solution export/download
- 129 unit tests (up from 69) covering all new scripts and the plan-alm validator
- Script wiring: setup-solution, setup-pipeline, deploy-pipeline, export-solution,
  hotfix-solution, configure-env-variables, import-solution all reference shared
  scripts instead of inline API prose
- Cloud flow detect-and-guide in import-solution (Phase 6c) and deploy-pipeline
  (Phase 7.8): detects type-29 workflow components and guides manual registration
- Post-deploy warnings in deploy-pipeline for connection reference binding and
  bot republishing when solution manifest includes cloudFlows or botComponents
- import-solution Phase 2 pre-import zip inspection: warns about connection
  references, bots, and hardcoded org URLs before import proceeds
- solution-api-patterns.md Section 8: documents BYOC vs traditional portal
  packaging differences for cloud flows and bots
- cicd-pipeline-patterns.md: Coming Soon markers on ADO and GitHub Actions sections
- Plugin version bumped to 1.2.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* plan-alm: reorder Phase 2 questions — solution always before strategy

Q1 is now always solution setup (confirm existing or include setup-solution).
Q2 is promotion strategy (PP Pipelines vs Manual). Path-specific questions
renumbered Q3–Q8 for both paths. Host env discovery uses discover-pipelines-host.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Surface plan-alm as natural next step across site lifecycle skills

- setup-solution: Phase 7 now offers plan-alm as the recommended next step
  (replaces direct route to setup-pipeline/export-solution, which bypassed the orchestrator)
- deploy-site: plan-alm added as first suggested next step after deployment
- activate-site: plan-alm added to next steps after site provisioning
- create-site: plan-alm added to later-stage next steps alongside setup-datamodel/add-seo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix plan-alm surfacing: activate-site only, revert setup-solution

- setup-solution: reverted Phase 7 to original (PP Pipelines / Manual / Decide later)
  setup-solution is invoked by plan-alm so must not route back to it
- deploy-site: removed plan-alm from next steps (wrong lifecycle stage)
- create-site: removed plan-alm from next steps (wrong lifecycle stage)
- activate-site: plan-alm suggestion retained — correct handoff point after site goes live

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* deploy-pipeline: tabbed deploy history with solution contents; plan-alm: per-stage activate prompt

## deploy-pipeline
- Rewrote deploy-history-template.html with vertical sidebar tabs (Overview / Solution / Config & Notes) matching the ALM plan layout
- Overview tab: status banner, summary bar, deployment details table, site activation
- Solution tab: solution metadata (version change, type, publisher, component count), validation result badge, Dataverse table chips + bot components from .solution-manifest.json
- Config & Notes tab: env var overrides, AI deployment notes, post-deploy warnings
- Added __SOLUTION_META_ROWS__, __VALIDATION_SECTION__, __SOLUTION_CONTENTS_SECTION__, __PREV_ARTIFACT_VERSION__ placeholders to Phase 7.4
- validate-deploy-pipeline.js: now blocks if deployHistoryFile referenced in .last-deploy.json but not written to disk
- validate-deploy-pipeline.test.js: new test file (7 cases) covering all validator paths

## plan-alm
- Per-stage activation: changed Phase 3 task creation from one "Deploy via pipeline" task to one Deploy + one Activate task pair per target stage
- Phase 7 PP path now loops per stage: deploy → immediately prompt activation → next stage
- AskUserQuestion activation prompt fires right after each successful stage deployment, not deferred to end of plan
- planData steps use per-stage deploy+activate pairs (Deploy to Staging / Activate site in Staging / etc.)
- render-alm-plan.js + alm-plan-template.html: authNoValue rendering fixes, stageClass driven by deployStatus field

## setup-solution
- Phase 1 Step 5: pre-load site settings from .alm-plan-context.json if present
- Three-tier site settings classification (keepAsIs / promoteToEnvVar / authNoValue / excluded)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add solution-splitting decision tree and manifest v2 (#107)

* Add solution-splitting decision tree and manifest v2

Introduces an advanced solution-recommendation layer for plan-alm +
setup-solution + setup-pipeline + deploy-pipeline, based on the spec
in design docs (solution-splitting-logic.md).

New shared libs (under plugins/power-pages/scripts/lib/):
- alm-thresholds.js: default thresholds + .alm-config.json merge
- estimate-solution-size.js: queries Dataverse metadata, estimates
  size/count/schema attrs/web file aggregates/env vars
- compute-split-plan.js: decision tree with priority Schema > Layer >
  Change-Frequency > Config Isolation (additive). Includes asset
  advisory generator (Azure Blob primary, CDN secondary).

Tests (23 passing): alm-thresholds.test.js, compute-split-plan.test.js
cover all five scenarios from the spec (Typical, Feedback Portal,
Prabhat 34x950, Brad 1400 envvars, component-heavy) plus advisory
edge cases.

Template + renderer:
- alm-plan-template.html: tabbed UX (Overview, Size, Advisory, EnvVars,
  Solutions, Pipelines, Execution). Matches reference design doc.
- render-alm-plan.js: rewritten to accept v2 planData fields
  (sizeAnalysis, assetAdvisory, proposedSolutions, breakdown) and
  render all new tabs.

Skill wiring (changes only — no behavior regressions for single-sol sites):
- plan-alm SKILL.md: Phase 1 Step 10 adds estimate + compute-split-plan
  calls. Phase 2 Q1b asks user to accept/override/advisory-first/migrate.
  Phase 3 planData extended with v2 fields.
- setup-solution SKILL.md: Phase 1 Step 6 detects .alm-split-plan.json
  and enters MULTI_SOLUTION_MODE. Phase 6 writes manifest v2 when
  multiple solutions are present.
- setup-pipeline SKILL.md: reads schemaVersion from manifest; creates
  one pipeline per solution in v2 mode. Writes .last-pipeline.json v2
  with pipelines[] array.
- deploy-pipeline SKILL.md: MULTI_PIPELINE_MODE loops through pipelines
  in order for the selected stage.

Sample renders (plugins/power-pages/skills/plan-alm/samples/):
- ideasphere-estimate.json: realistic IdeaSphere profile with 128 MB,
  5 large media files, 7 env vars.
- ideasphere-alm-plan.html: end-to-end sample generated via
  estimate -> compute-split-plan -> render pipeline. Decision tree
  correctly selects Strategy 1 (Layer Split) -> 2 solutions. All
  tabs populated with real classifier output.

Plugin version bumped 1.2.0 -> 1.3.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Apply PR #107 review fixes

Addresses code-review findings on the solution-splitting PR:

- AGENTS.md: remove duplicate "Solution Splitting Decision Tree" section
- alm-thresholds.js: rename classifyTier args to make strict-upper-exclusive
  bounds explicit (greenUpperExclusive, yellowUpperExclusive) + explain
- compute-split-plan.js:
  * isComponentHeavy no longer silently drops sites above hardFlagComponentCount;
    they now route to Strategy 2 with an error-type recommendation calling out
    the hard flag.
  * partitionByChangeFrequency derives sizeMB from count shares so size and
    componentCount stay self-consistent (was using independent 25/20/10/45%
    fractions that didn't match the count allocation).
  * partitionBySchema uses estimate.breakdown.tables when available instead of
    the hard-coded 50/50 heuristic; falls back with a rationale suffix.
- estimate-solution-size.js: exit 1 on auth failure (was exit 0 with empty
  estimate, which silently clobbered prior good output when callers redirected
  stdout to a file). Dedupe repeated `token || resolved` pattern.
- plan-alm SKILL.md Step 10: use tmp-file write pattern so a failed estimator
  preserves the previous good .alm-size-estimate.json / .alm-split-plan.json.
- deploy-pipeline SKILL.md: document partial-deploy risk for MULTI_PIPELINE_MODE
  (no automatic rollback; record per-pipeline status in .last-deploy.json).
- render-alm-plan.js: comment explaining the single-occurrence plan-status regex.
- .gitignore: add ALM split-plan intermediates (.alm-size-estimate.json(.tmp),
  .alm-split-plan.json(.tmp)).
- Tests:
  * New estimate-solution-size.test.js (7 tests) covering classifyPPCs buckets,
    empty input, estimateTotalSize math stability + web file additivity, and
    BYTES_PER sanity.
  * compute-split-plan.test.js adds hard-flag routing + error recommendation,
    Change-Frequency size/count consistency (sum ~ totalSizeMB ±0.5), and
    partitionBySchema breakdown-driven sizing.
- Sample: trailing newline on ideasphere-plan-data.json.

Three validation items deferred to a live Dataverse run on IdeaSphere dev:
OData GUID filter quoting in the estimator, end-to-end multi-solution flow,
and hand-authored sample vs. real estimator output. Tracked in auto-memory
(project_pr107_deferred_validation.md).

Tests: 34/34 ALM unit tests pass (23 pre-existing + 4 new compute-split-plan
+ 7 new estimate-solution-size). render-alm-plan.test.js failures are
pre-existing (the renderer was rewritten in this PR; its tests were not
updated) — tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add ALM-aware-by-default safeguards for Power Pages skills (#108)

* Restore missing ALM skill files from PowerPagesALM merge

The merge commit 49c93ad registered the merge but failed to bring in
any file additions from users/nityagi/PowerPagesALM. This restores all
missing power-pages files: setup-pipeline, deploy-pipeline, export-solution,
import-solution, configure-env-variables, diagnose-deployment, hotfix-solution,
setup-solution SKILL.md files and their validation scripts, plus shared
reference docs and lib scripts added in the ALM branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add ALM-aware-by-default safeguards for Power Pages skills

Fixes the recurring bug where Dataverse records created by skills land in the
Default solution and never travel across environments — e.g. invoice-checker
server logic and crd50_auth_openauth_microsoft_clientsecret env var.

New shared modules:
- scripts/lib/discover-site-components.js — authoritative site inventory +
  diff against a solution (14 tests). Covers all 31 documented
  powerpagecomponenttype values.
- scripts/lib/resolve-target-solution.js — strict 3-step solution resolution
  (explicit arg > .solution-manifest.json > throw); never silently falls back
  to Default (13 tests).
- scripts/lint-skills-alm.js — CI gate: flags SKILL.md that POSTs to Dataverse
  without reading the manifest, scripts that write without importing the
  resolver, and new ppctype values missing from PPC_TYPE_LABELS (10 tests).

Skill updates:
- setup-solution: Phase 1 auto-detects .solution-manifest.json and branches to
  sync mode; Step 5.4b adopts orphan env vars with the publisher prefix.
- export-solution: new Phase 2.5 pre-export completeness check.
- deploy-pipeline: new Phase 3.5 pre-deploy completeness check.
- create-environment-variable.js: now uses the resolver + adds the created
  definition via AddSolutionComponent (fixes the crd50_auth_* orphan case).

Docs:
- AGENTS.md + CLAUDE.md: ALM-aware-by-default principle with the strict
  solution resolution order.
- PLUGIN_DEVELOPMENT_GUIDE.md: new "ALM Checklist for New Skills" section.

All 159 plugin tests pass; alm-lint reports 0 findings on the current tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Extend alm-lint coverage: CI workflow, PATCH/PUT verbs, allowlist, user docs

- .github/workflows/power-pages-alm-lint.yml: new CI job gates PRs that touch
  plugins/power-pages/** on `node lint-skills-alm.js` passing.
- lint-skills-alm.js: PROSE_WRITE_PATTERN now covers POST/PATCH/PUT so skills
  that mutate existing publishers/solutions are also gated. DELETE is
  intentionally excluded (resolver does not apply). Helper-name list extended
  (apiPatch, patchRecord, updateRecord).
- lint-skills-alm.js: adds .almlintignore allowlist support. Each entry is
  `<path-or-glob> <rule-name> <reason-text>` — unknown rules and missing
  reasons throw at parse time. Inline alm-lint-ignore comments still work
  for per-file exceptions; the file handles systemic patterns.
- plugins/power-pages/references/alm-prompts.md: new user-facing walkthrough
  of the sync-mode, orphan-adoption, and pre-export completeness prompts.
  Linked from README.md.
- Tests: +11 (verbs, allowlist parsing, glob matching, filtering) —
  175/175 pass, alm-lint: 0 findings on the current tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address review feedback on ALM safeguards

Reject invalid solution unique names and publisher prefixes up front
instead of silently stripping non-alphanumeric characters — a typo like
"my-sol" no longer becomes a confusing "not found" result. Also guards
OData filters from injection earlier in the call path.

Replace regex-parsing of PPC_TYPE_LABELS in lint-skills-alm with a
direct `require()` of the sibling discover-site-components module, so
formatting changes (comments, multi-line entries) can't silently shrink
the known-set used by the non-waivable DISCOVER-coverage rule.

Normalize rule-name casing in both inline `alm-lint-ignore:` tags and
.almlintignore file entries — previously the inline matcher was
case-insensitive but the file matcher was case-sensitive, so the same
rule name could suppress one path but not the other.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* ALM validation run: 11 fixes + integration tests + live-verified end-to-end (#119)

* plan-alm: 3 ALM planning refinements

1. Solutions tab surfaces an Asset Advisory callout. When compute-split-plan
   recommends 'externalize-media' (bulk media/web-file asset move), the
   Solutions tab now shows a top-of-section callout summarizing the candidate
   count + MB and linking to the Asset Advisory tab via the existing nav
   switch. Without this, reviewers approved splits that a CDN/Blob move would
   have made unnecessary.
   - render-alm-plan.js: new buildAssetAdvisoryCallout()
   - +3 tests covering visible, hidden (no recommendation), and disabled
     (feature off) cases.

2. Single Power Platform Pipeline with multiple stage runs instead of one
   pipeline per solution. A PP Pipeline can already deploy different solutions
   via stage runs that specify different artifactname/solutionid against the
   same stage — creating N pipeline records was wasteful and cluttered the
   Pipelines UI.
   - .last-pipeline.json bumps to schemaVersion: 3 with a single pipelineId +
     stages[] + deploymentOrder[{ solutionUniqueName, solutionId, order }].
   - setup-pipeline Phase 6b: creates ONE pipeline + one stage per target env,
     records deploymentOrder. v2 (legacy pipelines[]) path documented with a
     migration prompt on re-run.
   - deploy-pipeline: new MULTI_RUN_MODE loops over deploymentOrder, creating
     one stage run per solution against the same stageId. Legacy
     MULTI_PIPELINE_MODE kept for v2 manifests.
   - render-alm-plan.js Pipelines tab: "Deployment Pipeline" (singular),
     shows one pipeline header + a Deployment order block listing per-solution
     runs. Future buffer surfaces as "Skipped (empty)".
   - +2 tests covering multi-solution and single-solution paths.

3. Split plans now include a {Prefix}_Future solution reserved for new
   component additions. Without this, every new server-logic / flow / env var
   tended to end up crammed into the wrong layer solution and forced a
   re-plan.
   - compute-split-plan.js: new appendFutureBuffer() called after partition
     when the plan has ≥2 solutions. Single-solution plans skip the buffer
     (no partition to protect). Tagged isFutureBuffer: true, sized at 0 MB /
     0 components, componentTypes: ['Any'].
   - setup-pipeline skips stage-run creation for buffer solutions
     (status: 'skipped-empty'); deploy-pipeline short-circuits them in the
     loop so an empty Future does not fail the deploy.
   - +4 tests (single-solution skip, buffer shape, last-position + naming
     invariant across strategies, appendFutureBuffer unit-level).
   - 4 existing tests updated for the new length/ordering.

Totals: 326/331 tests pass across the plugin; 5 pre-existing failures from
PR #107 (render-alm-plan.test.js scenarios) are unrelated to this change.
alm-lint reports 0 findings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* plan-alm + estimator: pre-plan completeness check, OData filter verified

Item 28 — plan-alm: pre-plan completeness check.
  New Phase 1 Step 11 runs discover-site-components.js against the source env
  and diffs against the current solution. If gaps exist, prompts the user to
  either run /power-pages:setup-solution in sync mode before planning, plan
  anyway (records the gap as KNOWN_GAPS so Phase 3 surfaces it in the Risks
  section), or cancel. Matches the same check already enforced at export
  (export-solution Phase 2.5) and deploy (deploy-pipeline Phase 3.5) — catches
  the drift a plan-turn earlier so reviewers do not invest time in stale plans.

Items 1–3 — OData GUID filter in estimate-solution-size.js verified.
  Tested both quoted (`_powerpagesiteid_value eq '<GUID>'`) and unquoted forms
  against org1e98cc97 (v9.2 endpoint). Both return HTTP 200 with identical
  result sets. Current code uses the quoted form and continues to work; added
  a verification comment inline and updated
  memory/project_pr107_deferred_validation.md with the 2026-04-21 result.

Tests: 326/331 pass, alm-lint: 0 findings. The 5 remaining failures are
pre-existing PR #107 regressions in render-alm-plan.test.js, unrelated to
these changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* estimate-solution-size: fix EntityDefinitions \$top=500 (400 error)

EntityDefinitions endpoint returns HTTP 400 "query parameter \$top is not
supported" on v9.2. Verified 2026-04-22 against org1e98cc97 — the estimator
could never successfully discover custom tables via this path, which made
plan-alm unusable on any site that relied on metadata-based table discovery.

Replaced \$top=500 with \$filter=IsCustomEntity eq true. Filter is
supported on EntityDefinitions even when \$top is not, and it narrows the
payload to custom tables without a post-fetch client filter.

Inline comment added citing the 2026-04-22 live verification so the next
agent doesn't re-add \$top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* estimator + plan-alm + integration tests: siteTotal vs inSolution + Phase 7.7 trigger

Three fixes from today's ALM validation run on SupplierInvoicePortal dev →
staging:

1. Estimator now distinguishes site-total from in-solution counts.
   --solutionId arg queries solutioncomponents for the specific solution and
   returns componentCountSiteTotal + componentCountInSolution + orphansOnSite.
   Fixes the 908-on-site/361-in-solution ambiguity that misled plan reviewers.
   Live verified on SupplierInvoicePortal (6bcbd1a2-…) in org1e98cc97:
   siteTotal=908, inSolution=361, orphansOnSite=547.

2. plan-alm Solutions tab renders a Solution membership banner when the
   estimator has both counts. Calls out orphan count and links to sync-mode.

3. deploy-pipeline Phase 7.7 activation trigger broadened.
   Old trigger required the current solution to contain a website
   componentType=10374. That missed the real-world case where the site
   exists pre-existing on the target and the solution being shipped is
   tables+flows+bots only. New trigger: run whenever powerpages.config.json
   has a websiteRecordId (i.e. this is a Power Pages project). Informational
   secondary check on the solution contents remains.

4. New integration tests at scripts/tests/integration/ use a real HTTP mock
   server (http.createServer) so network code paths, Authorization header
   propagation, and @odata.nextLink pagination are exercised end-to-end.
   Covers discover-site-components.js with 4 scenarios. 330/335 plugin tests
   pass; alm-lint: 0 findings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* estimate-solution-size: stop double-counting table attributes as components

Solution componentCount was inflated 2-3x on schema-heavy sites because the
formula added schemaAttrCount (every table column). In Dataverse, a table
with N columns is ONE solutioncomponents row of type 1 — attributes ride
along, they're NOT individual components.

Live evidence on SupplierInvoicePortal (org1e98cc97):
  Before: componentCount = 393 + 11 + 503 + 1 = 908
  After:  componentCount = 393 + 11 + 1 + 2   = 407
  Actual in solution (solutioncomponents query): 361
  Orphan delta = 46 (was falsely 547)

New formula: ppcs + tables + envVars + cloudFlows (workflow entities, not
the type-33 ppc binding which is already in ppcs). Bots and bot components
remain a known undercount bound; they're captured by the solutioncomponents
query (`inSolution.byComponentType`) when --solutionId is passed.

No test changes — formula previously had no unit-test coverage asserting
the number (only the fields existed). Added an inline comment mapping each
term to its componenttype so the next contributor won't re-add attributes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* estimate-solution-size: add site-scoped bot + botcomponent queries

Before: siteTotal formula skipped bots (type 10137) and bot components
(type 10193) because they aren't reachable from /powerpagecomponents. Live
data on SIP had 4 bots + 30 components in solution — none reflected in
siteTotal — making orphansOnSite look artificially small (46 instead of 78).

Root cause dig: type-27 "Bot Consumer" ppcs store the bot schema reference
in the `content` JSON (`{"botschemaname": "..."}`), NOT in the `name` field
(which is literally the string "Bot Consumer"). The first pass used `name`
and silently returned zero bots.

Now: fetch type-27 ppcs with content column, JSON.parse each, extract
botschemaname, then query bots by schemaname to stay site-scoped (avoids
folding env-wide bots from other projects into this site's total). For each
discovered bot, query botcomponents filtered by parent bot id.

Output gets two new fields: botCountScoped + botComponentCountScoped.

Live re-verification on SupplierInvoicePortal (org1e98cc97):
  siteTotal: 407 → 439 (+2 bots + 30 bot components)
  inSolution: 361 (unchanged — from solutioncomponents query)
  orphansOnSite: 46 → 78 (more accurate — math: 82 ppc orphans + 1 env var
    − 2 bot consumer dupes in solution − 2 solution-only bots − 1 website)

Real orphans on this site: 81 type-3 web files + 1 type-35 server logic +
1 env var = 83 solutionizable records. The 78 "net delta" reflects
accounting offsets for rows that exist in solution but can't be represented
in a site-scoped scan (bot consumer duplicate rows, website itself).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* estimate-solution-size: exclude Vite/Rollup bundle chunks + fix swapped picklist constants

Two bugs fixed:

1. classifyPPCs used WEB_FILE=2 and WEB_PAGE=4, but the authoritative picklist
   (MS Learn + PPC_TYPE_LABELS in discover-site-components.js) says type 2 is
   Web Page and type 3 is Web File. On SIP, webFileCount was reporting 12
   (actually Web Pages) while the site had 208 actual web files. Fixed to
   match the canonical enum; added the full ppc type lookup for completeness.

2. Bundle chunk noise excluded from both siteTotal and inSolution.
   `pac pages upload-code-site` leaves behind hash-suffixed .js/.css chunks
   every rebuild (Vite: Home-BPuZZDcA.js, index-DyzztwOp.js). They stay in
   Dataverse as powerpagecomponent rows linked to the site but aren't
   referenced by the current build's index.html. User feedback: "we do not
   care about random webfiles entries not actually associated with a pages
   site." Added isProbablyBundleChunk() heuristic (regex on hash-like
   suffix + .js/.mjs/.cjs/.css/.map extension); split classified.webFiles
   into webFiles (real content) + bundleChunks (noise).

   siteTotal now subtracts bundleChunks. inSolution subtracts the subset of
   bundleChunks that are in the solution via objectid intersection, so the
   comparison stays apples-to-apples.

Live on SIP (org1e98cc97, SupplierInvoicePortal solution):
                        before today   after #1   after #2 (real)
  siteTotal             908            439        231
  inSolution            361            361        233
  orphans               547 (bogus)    78         0 (real)
  bundleChunkCount      -              -          208 (on site)
  chunksInSolution      -              -          128

Real orphans dropped to 0 because this solution is in fact in sync with the
site's meaningful content; the earlier 82-orphan number was dominated by
81 Vite chunks that don't represent real missing content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* estimate-solution-size tests: update to authoritative picklist + bundle-chunk heuristic

Two test updates for today's picklist + bundle-chunk fixes:

1. classifyPPCs test now uses the authoritative powerpagecomponenttype
   picklist values (type 3 = Web File, type 2 = Web Page, type 8 = Web
   Template, type 11 = Web Role). Prior test asserted against swapped
   constants, which masked the bug that caused SIP's web file count to
   show 12 when the site actually held 208.

2. New test for the isProbablyBundleChunk heuristic. Covers 7 chunk name
   patterns (Vite hash suffixes, Webpack dot-separated hashes, sourcemaps
   with `.js.map`) + 7 real static asset names (hero.jpg, logo.svg,
   favicon.ico, Inter-Regular.woff2, app.js) to prove no false positives.

Regex updated from `\.(?:js|mjs|cjs|css|map)$` to
`\.(?:js|mjs|cjs|css)(?:\.map)?$` to correctly match `.js.map`
sourcemaps whose penultimate segment is already an extension.

All 8 estimate-solution-size tests pass. alm-lint: 0 findings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* countSolutionMembership: add cross-site safety check

Defensive check for solutions that contain ppcs from a site other than the
one we're planning. Rare in practice (usually people build one solution per
site) but possible when someone manually stitches components across sites.
Silent miscounting is worse than a 1-query cost, so wire it up.

When estimator is called with both --solutionId and --websiteRecordId, pass
the set of ppc ids from the site to countSolutionMembership. The helper
filters solutioncomponents type-10373 rows by objectid, flagging any whose
id isn't in the site set. Returns crossSitePpcs[] + the output object gets
a human-readable crossSitePpcWarning or null.

Live check on SupplierInvoicePortal (org1e98cc97):
  solutioncomponents type 10373: 311 rows
  Of those, in site 2bea4a71-...'s ppc set: 311
  Cross-site miss: 0
  warning: null ✓ (healthy)

Separately confirmed that the 82 orphan ppcs on this site (81 Vite bundle
chunks + 1 server logic "invoice-checker") all live in the well-known
Active solution (fd140aae-4df4-11dd-bd17-0019b9312238) — i.e. they're in
Dataverse's default working-copy bucket, not a different user solution.

Test coverage: 2 new integration tests against the http-mock server cover
(a) flagging a cross-site row and (b) skipping the check when
sitePpcIdSet is null. 6/6 integration tests pass, 331/336 full suite
(5 pre-existing render-alm-plan failures + 1 env-dependent auth test,
unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* setup-solution: add Step 5.4c orphan ppc adoption (live-validated)

Today's per-type breakdown on SupplierInvoicePortal exposed a gap not
covered by the existing Step 5.4b: ppc rows (server logic, real pages,
etc.) that live on the site but aren't in the user solution.

invoice-checker server logic was the canonical case — created by
/add-server-logic in an earlier session, never registered into the user
solution, sat in Active. Adopted live today (AddSolutionComponent
componenttype 10373); solution bumped 1.0.0.1 → 1.0.0.2.

Step 5.4c mirrors 5.4b but for powerpagecomponent rows:

  - Calls discover-site-components.js for missing.powerpagecomponents
  - Partitions orphans: real-content vs. bundle-chunk (Vite/Rollup hash)
  - Bundle chunks are reported but NOT prompted — they're stale build
    artifacts, not real content
  - Real-content orphans deduped by (type, name) — older stale dupes
    (e.g. a second index.html from a superseded build) excluded from
    the adoption prompt
  - AskUserQuestion multiSelect for real orphans
  - AddSolutionComponent per selection with componentType: 10373 and
    AddRequiredComponents: false

Documented API gotcha: DoNotIncludeSubcomponents: true returns HTTP 400
0x80040216 for non-Entity components. Leave it unset for type-10373.

Also updated:
  - Sync-mode description to list 5.4c in the step sequence
  - Manifest tag list to include `[ADOPTED ppc — was in Active only]`
  - Phase 5.6 bulk-add list to include adopted orphan ppcs as entry #8

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* estimate-solution-size: address PR #119 review nits

Three nits from the review agent, all addressed:

1. **Cloud-flow counting comment was misleading** — reviewer's concern was
   that `+ classified.cloudFlowLinks.length` appeared to double-count the
   type-33 ppc bindings already in ppcs.length. The code is actually
   correct (cloudFlowLinks.length serves as the workflow count since each
   type-33 binding pairs 1:1 with a type-29 workflow), but the comment
   didn't make that clear. Rewrote the block comment so future readers
   don't stumble on the same concern.

2. **Dropped `componentCount` alias, kept only `componentCountSiteTotal`** —
   per user: "we do not need backward compatibility since ALM skills are
   under active development on a feature branch." compute-split-plan.js
   now reads `estimate.componentCountSiteTotal` (11 call sites updated);
   compute-split-plan.test.js fixtures renamed too (10 call sites).
   render-alm-plan.js was already on the new name — no change needed.

3. **Bot pagination WARN on ceiling** — discoverBotsAndComponents emits a
   stderr WARN when bots.length hits BOTS_MAX_PAGES * BOTS_TOP (1,500) or
   botComponents.length hits COMPS_MAX_PAGES * COMPS_TOP (25,000). These
   caps are unlikely in any real tenant, but silent truncation is worse
   than a visible warning. Constants extracted so the ceiling is easy to
   bump in place.

333/338 tests pass; alm-lint: 0 findings. Live re-check against
SupplierInvoicePortal org1e98cc97:
  siteTotal: 231
  inSolution: 234 (+1 from earlier 233 — invoice-checker adopted)
  orphans: 0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* estimate-solution-size: stop subtracting bundle chunks from inSolution.total

Live validation against SupplierInvoicePortal in dev surfaced that we were
under-counting in-solution components by 128 (the bundle-chunk ppcs). Bundle
chunks that were added to the solution via AddSolutionComponent ARE legitimate
members — they ship with the managed export — so stripping them from the
in-solution count produced numbers that didn't match the Maker UI.

Power Platform UI shows SIP's solution at 362 (= 311 site components + 11
tables + 1 site record + 1 site language + 4 connection references + 2 cloud
flows + 2 agents + 30 agent components). Estimator now matches exactly.

Changes:
- inSolution.total: raw row count, no bundle-chunk subtraction.
- componentCountSiteTotal: raw site inventory (bundle chunks included).
- componentCountSiteActionable: new sub-count that strips bundle chunks for
  the "actionable content" view.
- orphansOnSite: computed from the actionable count so stale upload artifacts
  don't get flagged as content gaps.
- Component-type comment block rewritten with the authoritative
  solutioncomponents enum (10137=Connection Reference, 10192=Bot,
  10193=Bot Component). Earlier comments had these swapped.
- render-alm-plan: headline count prefers componentCountInSolution when
  available (that's what the pipeline ships); falls back to siteTotal.
- Membership banner wording clarified — raw rows vs. owned components,
  "actionable orphan" terminology.

All 40 targeted tests pass. The 5 pre-existing render-alm-plan test
failures (approval-gate / env-var / solutionContents scenarios) are unchanged
— unrelated to this commit and present on the baseline too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* estimate-solution-size: note that solutioncomponent types are dynamic per tenant

Two nits from review:

1) The componenttype block I rewrote yesterday presented 1/29/380/10137/10192/
   10193/10373/10374 as if they were stable constants. They are not — the
   Dataverse solutioncomponent.componenttype picklist is dynamic per tenant
   and AddSolutionComponent callers are expected to resolve values at runtime
   via scripts/lib/discover-component-types.js. (This is already documented
   in MEMORY.md but my rewrite didn't reflect it.)

   countSolutionMembership() in this file was already resolver-free and does
   the right thing (tallies whatever values Dataverse returns). Only the
   comment block needed to stop implying the integers are universal.

2) Added a concrete derivation of how siteTotalComponents evaluates to 439
   for the live SIP reference site, plus the note that connection references
   (4) and the website record itself (1) are not included — that's why the
   diff against inSolution is ~77 rather than exactly matching the 82 orphans
   originally reported.

Also fixed the bot-discovery doc block: same "dynamic, not static" caveat
plus pointer to discover-component-types.js for mutation paths.

40/40 targeted tests still pass.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Power Pages ALM: per-skill telemetry on 6 skills + 5 plan-alm UX fixes from first review (#126)

* skill-tracking: add to 6 ALM skills + extend reference table

Every Power Pages skill must record usage via update-skill-tracking.js in
its final phase (per plugins/power-pages/CLAUDE.md convention). Six ALM
skills were missing the tracking call:

- setup-solution (SetupSolution)
- export-solution (ExportSolution)
- import-solution (ImportSolution)
- hotfix-solution (HotfixSolution)
- diagnose-deployment (DiagnoseDeployment)
- configure-env-variables (ConfigureEnvVariables)

Each now has a "Record Skill Usage" sub-step at the end of its final
phase using the pointer pattern (> Reference: ...skill-tracking-reference.md).

references/skill-tracking-reference.md: added the 8 missing PascalCase
mappings (the 6 above plus setup-pipeline / deploy-pipeline, which
already had the invocation inline but were absent from the table).

Coverage: 23 / 24 power-pages SKILL.md files now record usage; only the
shared report-issue wrapper is intentionally excluded (cross-plugin
shared skill that does not create site artifacts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* plan-alm: 5 UX fixes from first end-to-end review

1. Site identity: prefer .powerpages-site/website.yml (source of truth
   when run from a site root) over powerpages.config.json. The config
   file is kept only as a fallback for plugin-dev scenarios. Reads
   `id` → websiteRecordId and `adx_name` → siteName from the YAML.

2. Plan presentation: render docs/alm-plan.html AND open it in the
   default browser BEFORE prompting for approval. Previously the HTML
   was rendered but users had to approve based on the inline CLI
   summary alone; they couldn't see the full plan (size gauge, signal
   cards, per-solution breakdown) until after approving. Cross-platform
   launch via Node.js — silent + non-blocking.

3. Size gauge: drop the inline fill label when fillPct < 15%. For small
   sites (e.g. 4.9 MB / 95 MB ≈ 4% fill), the "4.9 MB" label inside the
   narrow pill overflowed and rendered as a floating chip that read
   like a different number. The headline size-gauge-value already
   shows the exact MB on the right, so no information is lost.

4. Remove the "Scale data — what we've tested" block from the Size tab.
   Static reference numbers (1k-2k typical, 6k tested, 10+ hour extreme
   case) weren't actionable for reviewers assessing THEIR plan; the
   tier classification on signal cards already conveys where they are.

5. Approved-by capture: Phase 4 options 1 and 2 now capture the approver
   via `git config user.name` (fallback to USER/USERNAME env var, then
   AskUserQuestion), and stamp both `<span id="approved-by">` and
   `<span id="approval-date">` in the HTML. Previously Approved-by was
   left blank in the audit trail.

Test suite: 14 pass / 5 fail — same counts as before these edits
(failures pre-existed and are unrelated to this change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* plan-alm: delete samples/ folder + harden browser-open to PowerShell

Samples folder removal:
The plugins/power-pages/skills/plan-alm/samples/ folder shipped synthetic
IdeaSphere fixtures and a pre-rendered demo HTML (~75 KB total). Nothing
in the repo referenced it — grep across scripts/tests/ came back empty.
It was design scaffolding, not regression-protected, and actively
misleading after today's template changes (the pre-rendered HTML still
contained the old "Scale data" block + the broken size-gauge label).
Real demos should use live projects (e.g. C:\Projects\supplier-invoice-portal)
where docs/alm-plan.html is always current.

Browser-open command hardening (Gap 2 follow-up):
First pass used `cmd /c start "" <path>` via Node spawn with detached:true.
That command exits 0 on sandboxed terminal hosts without actually
launching a browser — the GUI spawn gets suppressed silently. Switched
to platform-specific launchers that respect file associations and are
resilient:
- Windows: `powershell.exe Start-Process` (reliable regardless of parent shell)
- macOS:   `open <path>`
- Linux:   `xdg-open <path>`

Also print a file:// URL alongside the spawn so if the auto-open fails
(headless environment, SSH session, restricted terminal), the user has
a direct clickable link. Updated the user-facing message to include the
URL as a fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ensure-pipelines-host skill + plan-alm UX overhaul + 3-entity site model fix (#134)

* ensure-pipelines-host: Slice A — detection scripts + plan SKILL.md

5 new helpers under scripts/lib + 57 unit tests + plan-only SKILL.md:
- check-env-host-binding.js — POST GetOrgDbOrgSetting('ProjectHostEnvironmentId')
- resolve-env-by-id.js — BAP env GET with $expand; 404-ambiguous handling
- list-tenant-envs.js — tenant-wide enum + per-env Pipelines probe (Production
  sku default, name-hint+admin-perms ranking, --firstHitWins, --includeName)
- verify-host-readiness.js — WhoAmI + solutions filter (covers both presence
  and version capture in one query)
- ensure-pipelines-host-detect.js — orchestrator wrapper that runs cache
  fast-path + Phase 2 resolution order + Phase 5 verify; emits
  .last-host-check.json schema with actionTaken: "none"

Resolution order mirrors ProjectHostProvider.tsx; produces 8 status values
including AvailableUsingPlatformHost / AvailableUnboundCustomHost /
MultipleUnboundCustomHosts / PlatformHostExistsUnbound / CannotRedirect.

Live-validated against SIP (org1e98cc97 → pascalepipelineshost):
  resolutionStatus: AvailableUsingCustomHost
  pipelinesSolutionVersion: 9.1.2026034.260325188
  ready: true (7.8s first run, 5.4s on cache hit)

Recon findings recorded in SKILL.md Open Items 1, 3, 10:
- Pipelines solution uniquename = msdyn_AppDeploymentAnchor (live-confirmed)
- BAP env-list never returns linkedEnvironmentMetadata.templates (0/1000 envs)
- deploymentpipelines?$top=0 returns 400 — switched to solutions filter

Slice B (provision-custom-host + SKILL.md execution wiring) and Slice C
(setup-pipeline delegation) follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ensure-pipelines-host: Slice B (provisioning + validator) + Slice C (setup-pipeline delegation)

Slice B — Custom Host fast-path provisioning:
- provision-custom-host.js — POSTs BAP env-create with the D365_ProjectHost
  template (eng.ms-confirmed: same template PPAC's New custom host button uses
  internally). Polls the lifecycle op URL with Retry-After-aware backoff;
  handles multiple response shapes (properties.provisioningState / state /
  status.code); 5xx-transient retries; 401/403 with explicit guidance.
  Falls back to a direct env GET when the lifecycle op response lacks
  linkedEnvironmentMetadata. Test suite covers 14 scenarios including timeout,
  Failed terminal state, transient 5xx, and synthetic-clock timeout.

- skills/ensure-pipelines-host/scripts/validate-ensure-host.js —
  Stop-hook validator. Accepts schemaVersion 1 or 2; treats CannotRedirect /
  OrgSettingStale / PermissionDenied as documented terminal-error states
  (skill ran successfully even though host isn't usable). Blocks on missing
  required fields, unknown resolutionStatus, ready=false on non-terminal
  status, or ready=true with finalHostEnvUrl missing. 15 tests.

- powerpages-hook-utils.js — registers ensure-pipelines-host in TRACKED_SKILLS
  so the central PostToolUse hook routes the validator on skill stop.

- ensure-pipelines-host/SKILL.md — drops PLAN-ONLY banner; scope note now
  reads as the executable contract.

Slice C — setup-pipeline delegates to ensure-pipelines-host:
- skills/setup-pipeline/SKILL.md Phase 1 step 4 rewritten. The old
  discover-pipelines-host-only call (which only checked tenant-default-custom-
  host) is replaced with ensure-pipelines-host-detect.js — the orchestrator
  wrapper that runs the full resolution order. Branches on resolutionStatus:
    AvailableUsing*      → use directly, continue
    *Unbound* / NoHost   → delegate to /power-pages:ensure-pipelines-host
    CannotRedirect /     → stop with admin-resolution guidance
    OrgSettingStale /
    PermissionDenied
  Phase 3 host-confirmation prompt updated — no more "NOT DETECTED — please
  provide" fallback; HOST_ENV_URL is always populated by Phase 1 (or the
  skill stops before reaching Phase 3).

Cumulative test count: 132 (5 Slice A test files + 2 Slice B test files +
existing regression suites: discover-pipelines-host, validate-deploy-pipeline,
powerpages-hook-utils, lint-skills-alm).

Live-validated against SIP (org1e98cc97):
  ensure-pipelines-host-detect.js → marker → validate-ensure-host.js → exit 0
  Missing marker → validate-ensure-host.js → exit 0 (graceful)

NOT YET LIVE-TESTED: provision-custom-host.js end-to-end (would create a real
Custom Host env in tenant — needs explicit user authorization before that
acceptance test). Unit tests cover all branch paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* plan-alm host integration + ensure-pipelines-host SKILL polish

plan-alm — wires the new ensure-pipelines-host detection into the
orchestrator so the generated docs/alm-plan.html reflects host state
before the user approves the plan.

- SKILL.md Phase 1 step 12 (NEW): runs ensure-pipelines-host-detect.js
  after the completeness check; acquires BAP_TOKEN; tmp-file-then-mv
  pattern; skips when PIPELINE_DONE=true; on detect failure sets
  HOST_RESOLUTION = { status: 'DetectionFailed' } and continues.
  Maps wrapper output (resolutionStatus / finalHostEnvUrl /
  isPlatformHost) into the plan-alm HOST_RESOLUTION shape with
  hostType = isPlatformHost ? 'platform' : (finalHostEnvUrl ? 'custom'
  : null).

- SKILL.md Phase 2 Q4 (REWRITTEN): 7-row branch table on
  HOST_RESOLUTION.status. AvailableUsing*/Unbound*/PlatformHost*
  populate HOST_ENV_URL and set WILL_ENSURE_HOST / WILL_PROVISION_CUSTOM
  flags as appropriate. CannotRedirect blocks the skill. OrgSettingStale
  / PermissionDenied / DetectionFailed fall back to manual entry with
  pac env list pre-fill.

- SKILL.md Phase 3 planData: new hostResolution block with the 9 spec
  fields. Documented field-by-field mapping inline so the orchestrator
  can populate it deterministically.

- SKILL.md Phase 3 risks: 5 new entries (NoHost,
  AvailableUnboundCustomHost, MultipleUnboundCustomHosts,
  PlatformHostExistsUnbound info-level; CannotRedirect warning).

- SKILL.md Phase 4 inline summary: adds "Pipeline host: {url} ({status})"
  line, with "Will be ensured during setup-pipeline" variant when
  willEnsureDuringExecution: true.

- render-alm-plan.js: buildHostCardHtml emits three modes
  (host-card-ok green for AvailableUsing*, host-card-pending yellow
  for willEnsureDuringExecution, host-card-blocked red for
  CannotRedirect). buildHostChecklistSubBullet emits an indented
  substep under the Setup pipeline checklist item only when the host
  will be ensured at execution time. Both wired into the replacements
  map.

- alm-plan-template.html: __PIPELINES_HOST_CARD__ placeholder above
  the existing pipeline stages diagram; __HOST_CHECKLIST_SUBSTEP__
  placeholder after __CHECKLIST_HTML__. CSS for .host-card-ok /
  -pending / -blocked plus .checklist-substep-list / .checklist-substep
  / .substep-note.

- render-alm-plan.test.js: +7 tests covering all three card modes,
  MultipleUnboundCustomHosts candidates count, no-render when
  hostResolution is absent, substep render path, substep omission
  when host already established. 26 tests / 21 pass / 5 fail —
  same 5 baseline failures as HEAD (pre-existing, unrelated to
  this work; verified via git stash baseline run).

Spec ambiguities resolved (worth review):
- DetectionFailed not in spec's 9-status list but spec says use it
  on non-zero exit. Added to status enum.
- Renderer emits checklist items as <div class="checklist-item">,
  not <li> — substep wrapped in <ul class="checklist-substep-list">
  so it's valid HTML when slotted directly into the template.
- Used --high-border for pending color (--warning-border doesn't exist
  in the palette).

ensure-pipelines-host SKILL.md polish:

- Drops "Scripts to be Implemented" stale header → "Scripts" with all
  6 helpers as shipped (table now reflects --includeName, --firstHitWins,
  --maxEnvsToProbe defaults, ensure-pipelines-host-detect.js wrapper row).

- Integration section: setup-pipeline and plan-alm both marked ✅ wired
  with concrete invocation snippets and behavior summaries.

- Phase 1.0 cache fast-path probe corrected from
  deploymentpipelines?$top=0 → solutions?$filter=uniquename eq
  'msdyn_AppDeploymentAnchor'&$top=1 (cheap-probe correction validated
  against pascalepipelineshost 2026-04-28; deploymentpipelines?$top=0
  returns 400 even on a working host). Threat-model and progress-table
  rows updated to match.

- Validation script section: registration mechanism corrected from
  hooks/hooks.json to TRACKED_SKILLS in
  scripts/lib/powerpages-hook-utils.js (the actual mechanism after
  Slice B added the skill there).

Live-validated end-to-end:
  Smoke render with synthetic AvailableUnboundCustomHost data →
  host-card-pending div present (4 hits incl CSS), checklist-substep
  present (4 hits incl CSS), HTML well-formed.

Cumulative test count: 428 (423 pass + 5 pre-existing failures, all
in render-alm-plan.test.js, none introduced by this work).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ensure-pipelines-host: PAC-CLI shim for BAP — works in tenants where Az→BAP fails

Adds --source pac|bap|auto (default auto) to the detection scripts so they
work in tenants where BAP rejects Az-CLI-acquired tokens with 401
InvalidAuthenticationToken (verified 2026-04-28 against
D365DemoTSCE53051106 demo tenant — token claims show correct user/tenant/
audience but BAP still rejects; PAC CLI works for the same user because it
uses a different first-party client ID with implicit BAP grants).

New file:
- scripts/lib/pac-bap-shim.js — wraps `pac admin list --json` into
  BAP-shaped env list. Maps PAC fields to BAP fields (EnvironmentId →
  name, Type → environmentSku, EnvironmentUrl → instanceUrl, etc.).
  Derives instanceApiUrl by inserting ".api" before crmN.dynamics.com.
  Fields not provided by PAC (tenantId, lastModifiedTime, permissions,
  isManaged) returned as null — none are critical for host detection.
  PAC doesn't list Platform-sku envs, so PE detection still requires
  source=bap with a working BAP token.

Updated scripts:
- list-tenant-envs.js — adds --source flag. auto (default): try BAP first,
  fall back to PAC on 401/403. pac: skip BAP entirely. bap: existing
  behavior. Output adds sourceUsed + fallbackReason.
- resolve-env-by-id.js — same flag pattern. PAC mode filters the full
  list by EnvironmentId. On PAC-not-listed returns
  { found: false, reason: "not-in-pac-list" }.
- ensure-pipelines-host-detect.js — propagates --source through to
  the helpers. --bapToken now optional in auto/pac modes.

Tests:
- pac-bap-shim.test.js (NEW) — 20 tests covering field mapping, URL
  derivation, JSON parsing edge cases (header prose, malformed JSON,
  exec errors).
- Updated existing tests for the new auto-mode semantics: tests that
  previously expected "throws when bapToken missing" now use
  --source bap explicitly.
- New tests for source=pac, source=auto with 401 fallback to PAC,
  surfaces original error when both fail.

Live-validated against new tenant (admin@D365DemoTSCE53051106):
- --source pac: detected Stage-1 + Stage-2 as eligibleForAppInstall in
  14s, 0 BAP calls.
- --source auto with rejected BAP token: BAP returns 401, falls back to
  PAC, returns same result in 7.4s.

Cumulative test count: 186 (181 pass, 5 pre-existing render-alm-plan
failures unrelated to this work). +28 net new tests in this commit.

Auth strategy section added to SKILL.md documenting the PAC-first-with-
BAP-fallback approach. Provisioning (provision-custom-host.js for
D365_ProjectHost env-create) still requires BAP — that's the one path
where Az tokens are needed; documented explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* PP Pipelines helpers: fix unprefixed schema (msdyn_ format never shipped)

The two PP Pipelines API helpers used msdyn_-prefixed property names in
their POST bodies (e.g., msdyn_name, msdyn_url, msdyn_type, msdyn_pipelineid
@odata.bind, msdyn_targetenvironmentid@odata.bind). Live testing on
2026-04-28 against the shipped Pipelines version (msdyn_AppDeploymentAnchor
v9.1.2026034.260325188 in the D365DemoTSCE53051106 demo tenant) revealed
that the actual deploymentenvironment / deploymentpipeline / deploymentstage
entities use **unprefixed** field names — the msdyn_ prefix is rejected
("Invalid property 'msdyn_name'").

Per references/power-pipeline-skill-reference.md, the unprefixed schema is
the canonical format. The helpers had diverged from the spec — likely
authored from early-preview HARs that used msdyn_ before ship.

Fixed:
- create-deployment-environment.js: msdyn_name/msdyn_url/msdyn_type →
  name/environmentid/environmenttype. The …
* feat(security-review): add HTML report generation and supporting documentation

- Introduced a new HTML report template for security reviews, providing a structured overview of findings, scores, and recommendations.
- Created a script (`render-review.js`) to render the HTML report from JSON data files.
- Added documentation for the seven-step conversation flow used in the security review process.
- Included a glossary of technical terms relevant to the security review findings.
- Defined the data format for sections and findings to ensure consistency in report generation.

* feat(security-review): enhance security review flow and parallelize sub-skill execution

* Refactor manage-code-scan and manage-site-scan skills for clarity and consistency

- Updated terminology in SKILL.md files to replace "quick/deep" with "basic/advanced" for better understanding.
- Adjusted command parameters in commands.md to reflect new terminology.
- Removed get-security-score.js script as its functionality is no longer needed.
- Simplified start-deep-scan.js to eliminate unnecessary username/password parameters.
- Enhanced review-security skill to streamline user interactions and improve clarity in reporting.
- Updated HTML report to remove score display and adjust layout for better readability.
- Revised flow.md and section-data-format.md to align with the latest changes in skill behavior and reporting structure.

* fix(security-review): update terminology for clarity in security review steps

* refactor(manage-site-scan): update descriptions and remove quick scan script for clarity and consistency

* feat(security-review): add HTML report generation and supporting documentation

- Introduced a new HTML report template for the security review skill, providing a structured overview of findings, summaries, and next steps.
- Created a script to render the HTML report from JSON data, ensuring required keys are validated.
- Added documentation for the seven-step conversation flow used in the security review process.
- Included a glossary of technical terms relevant to the security review findings.
- Defined the data format for sections and findings to standardize report generation.

* Add scan-site skill and related scripts for Power Pages security scanning

- Implemented `scan-site` skill to perform security scans on deployed Power Pages sites.
- Created scripts for starting deep scans, polling scan status, and fetching latest reports.
- Added command reference documentation for the new scan-site functionality.
- Updated existing security review skill to integrate with the new scan-site capabilities.
- Introduced `run-trivy.js` script for scanning code dependencies and vulnerabilities.

* refactor(skills): remove progress tracking tables from manage-firewall, manage-headers, and scan-site skills for clarity

* refactor(manage-firewall, manage-headers, scan-code, scan-site, security-review): standardize JSON formatting and improve clarity in user prompts

* refactor(manage-firewall): enforce rule naming conventions and update examples for clarity

* Refactor firewall management scripts and documentation

- Deleted outdated rule cookbook documentation and replaced it with a new rule reference document that provides a comprehensive guide on creating and managing firewall rules.
- Updated scripts for deleting, enabling, disabling, and retrieving firewall rules to use the Power Platform API instead of the Admin API.
- Enhanced error handling and output formatting in the get-rules and get-status scripts.
- Modified set-rules script to accept inline JSON data for rule creation and updates, improving usability.
- Added timeout settings for rule deletion and creation to accommodate longer processing times.

* Refactor scan-site scripts to use Power Platform API; update command documentation and add scan reference schema

- Changed admin-api references to power-platform-api in scripts: get-latest-report.js, poll-deep-scan.js, start-deep-scan.js.
- Updated command documentation to reflect changes in output and usage.
- Added scan-reference.md for detailed schema of scan report and alert normalization.
- Improved error handling and response structures in get-latest-report.js and poll-deep-scan.js.

* Add headers reference documentation and remove obsolete scripts

- Created a new documentation file for headers reference detailing site settings and response headers in Power Pages.
- Removed the `inspect-headers.js` script as it is no longer needed.
- Removed the `scan-external-urls.js` script due to redundancy.
- Updated the `SKILL.md` and `commands.md` files to clarify rate limits and scan statuses.
- Adjusted severity mapping in `scan-reference.md` to align with API values.

* refactor(manage-headers): enhance security recommendations and clarify CSP directives

* refactor(manage-headers): update skill tracking instructions for clarity and brevity

* Refactor scan-code commands and tool installation documentation

- Simplified command descriptions and usage for `check-tools.js`, `run-opengrep.js`, and `run-trivy.js`.
- Updated output format to return JSON directly to stdout instead of writing to files.
- Removed the tool installation guide as a separate document; now users are instructed to install tools based on `check-tools.js` output.
- Enhanced error handling and timeout settings in `check-tools.js`, `run-opengrep.js`, and `run-trivy.js`.
- Adjusted the security review skill documentation to reflect changes in workflow and terminology.

* refactor(scan-code): update scan depth options and enhance trivy command flags

* refactor(security-review): improve clarity and consistency in documentation and remove obsolete glossary

* fix(scan-code): clarify description for Basic risk coverage in user prompts

* Refactor security review and scan report scripts

- Added a new script `render-review.js` to generate security review HTML reports from JSON data.
- Removed the obsolete `render-scan-report.js` script.
- Updated documentation in `manage-firewall` skill to reflect changes in JSON structure and usage.
- Standardized example usage across various scripts to replace `<guid>` with `<portal-id>`.
- Enhanced `run-opengrep.js` and `run-trivy.js` to output raw JSON directly, removing unnecessary parsing.
- Deleted the `section-data-format.md` reference file as it was no longer needed.
- Removed the old `render-review.js` script from the security review skill, consolidating functionality into the new script.

* Enhance scan-code and scan-site skills with unified JSON output and transformation scripts

- Updated scan-code skill to save raw JSON outputs from opengrep and trivy to temporary files, and introduced a new transform script to normalize findings into a unified format.
- Revised documentation for scan-code to reflect changes in output handling and normalization process.
- Added transform-report script for scan-site to convert deep-scan reports into a unified findings shape, including detailed severity mapping.
- Improved scan-site skill documentation to clarify the fetching and transformation of reports, including handling of empty report cases.
- Updated security-review skill to ensure findings are processed verbatim from transform scripts, with clear policies on severity and annotations for inventory sections.

* fix: update usage examples to replace <guid> with <portal-id> in scripts and documentation

* Refactor scan-code functionality: remove scripts and documentation

- Deleted the following scripts: check-tools.js, run-opengrep.js, run-trivy.js, transform-scan-code.js.
- Removed associated documentation from commands.md.
- Updated scan-site and security-review skills documentation to reflect changes in functionality and clarify usage.
- Adjusted the flow of the security-review skill to streamline the conversation steps and improve user experience.

* feat(security-review): integrate scan-code skill for local source and package vulnerability checks

- Added scan-code skill to scan local Power Pages projects for dependency vulnerabilities and code issues using npm audit and ESLint.
- Updated security-review skill to incorporate scan-code findings alongside live-site checks.
- Enhanced HTML report generation to include separate sections for package and code findings with verbatim severities.
- Modified existing styles and severity labels in the security review report template to accommodate new severities.
- Documented the scan-code skill with detailed usage instructions and workflow.

* Revert "feat(security-review): integrate scan-code skill for local source and package vulnerability checks"

This reverts commit 4c87aca.

* feat(security-review): enhance security review skill with improved flow and error handling

* Refactor scan-site and security-review skills for improved user experience and code clarity

- Updated SKILL.md for scan-site to streamline user options and remove unnecessary confirmation steps before scanning.
- Enhanced get-latest-report.js to improve error handling and modularize the main function.
- Refactored poll-deep-scan.js to introduce better timeout handling and improve readability.
- Simplified start-deep-scan.js by consolidating argument parsing and error handling.
- Transformed transform-report.js to improve report parsing and error messaging, ensuring better handling of malformed reports.
- Revised security-review SKILL.md to reduce phases from seven to six, clarifying workflow and user interactions.
- Updated flow.md to reflect changes in the security-review skill phases and improve clarity on user-facing beats.

* test(power-pages): address Copilot PR microsoft#151 review comments

Reconciles three doc inconsistencies and adds node:test coverage for
the four scripts Copilot flagged as untested.

Doc fixes:
- security-review SKILL.md § 3.1.2: skipped subagent placeholders
  are now rendered without a severity field, matching the no-severity
  policy in § 3.1.3. build-review-data.js's skippedSection() drops
  severity:'info' and the totals.info bump.
- scan-site SKILL.md § 5.1 + transform-report.js: the empty status
  now emits a single info finding (via new emptyReport() helper)
  rather than an empty findings array, so review-mode output never
  produces an empty section.
- security-review SKILL.md Constraints: the template reference now
  points at scripts/lib/templates/security-review-report.html (the
  real location) instead of the non-existent assets/.

New tests under scripts/tests/ (64 cases total, all passing):
- build-review-data.test.js: SECTION_MAP shape, SEVERITIES order,
  buildSections behaviour (skipped without severity, totals
  aggregation, unknown severities, empty dir, JSON-parse failures),
  TZ-pinned formatGeneratedAt, and CLI surface (--help, missing
  flags, --inputDir validation, --nextStepsFile folding).
- render-review.test.js: missing --output/--data exits, missing
  data file, unparseable JSON, parameterised required-key
  validation over all 6 keys (asserts stderr names the missing
  key), and end-to-end render that verifies REPORT_NAME/SITE_NAME/
  REVIEW_DATA-derived values reach the rendered HTML.
- power-platform-api.test.js: parseCliArgs, parseTimeoutMs (with
  parameterised rejection of 'abc'/'0'/'-1'/'-0.5'/'NaN'/''),
  hasErrorCode is status-agnostic, isFeatureUnsupported variants,
  pollUntil happy/timeout/error paths plus a stub-clock test that
  pins DEFAULT_POLL_INTERVAL_MS as the wired-up default and another
  that asserts a caller-supplied intervalMs flows through.
- website.test.js: nextSkipFrom and recordIdOf edge cases plus
  findWebsite pagination via @odata.nextLink, case-insensitive
  match, non-advancing-server guard, request/context errors, and
  empty-websiteId rejection. Uses require.cache injection to stub
  the api module — documented Node API, restored in finally.

Full plugin suite: 984 tests pass, 0 fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Ramachandran R <ramacr@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Document the latest Power Pages skills in README.md

- Add ALM/CI-CD and security skill descriptions

- Require future user-invocable skills to be documented

Co-authored-by: GitHub Copilot <copilot@github.com>
@Hjaf
Hjaf merged commit 405ec3c into main May 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants