diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 91e4a7e90..43bc23599 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -4,78 +4,29 @@ "name": "Microsoft" }, "metadata": { - "description": "Plugins for Power Platform development with Claude Code and GitHub Copilot" + "description": "Plugins for Power Platform development with Claude Code and GitHub Copilot", + "pluginRoot": "." }, "plugins": [ { "name": "power-pages", - "source": "./plugins/power-pages", - "description": "Power Pages development and management plugin for Claude Code and GitHub Copilot", - "category": "development", - "tags": [ - "power platform", - "power pages", - "microsoft power platform", - "microsoft" - ] + "source": "./plugins/power-pages" }, { "name": "model-apps", - "source": "./plugins/model-apps", - "description": "Build and deploy generative pages for Power Apps model-driven apps", - "category": "development", - "tags": [ - "power platform", - "power apps", - "model-driven apps", - "generative pages", - "genux", - "microsoft" - ] + "source": "./plugins/model-apps" }, { "name": "mcp-apps", - "source": "./plugins/mcp-apps", - "description": "Generate MCP App widgets for MCP tools", - "category": "development", - "tags": [ - "mcp", - "mcp-apps", - "widget", - "power platform", - "microsoft" - ] + "source": "./plugins/mcp-apps" }, { "name": "canvas-apps", - "source": "./plugins/canvas-apps", - "description": "Build Power Apps Canvas Apps using the Canvas Authoring MCP server", - "category": "development", - "tags": [ - "power platform", - "power apps", - "canvas apps", - "pa-yaml", - "msapp", - "connectors", - "microsoft" - ] + "source": "./plugins/canvas-apps" }, { "name": "code-apps-preview", - "source": "./plugins/code-apps", - "description": "Build and deploy Power Apps code apps using React, Vite, and Power Platform connectors", - "category": "development", - "tags": [ - "power platform", - "power apps", - "code apps", - "react", - "vite", - "dataverse", - "connectors", - "microsoft" - ] + "source": "./plugins/code-apps" } ] } diff --git a/.claude/settings.json b/.claude/settings.json index 7f667df63..87f30aed9 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -21,9 +21,7 @@ "Bash(dir:*)", "Read", "Write", - "Edit", - "mcp__playwright__*", - "mcp__*__browser_*" + "Edit" ] } } diff --git a/.github/workflows/power-pages-alm-lint.yml b/.github/workflows/power-pages-alm-lint.yml index e8ccbbbf8..824588005 100644 --- a/.github/workflows/power-pages-alm-lint.yml +++ b/.github/workflows/power-pages-alm-lint.yml @@ -1,3 +1,9 @@ +# Deliberately separate from `power-pages-script-tests.yml`. +# This workflow runs policy enforcement (gate markers, ALM-aware-by-default, +# discovery coverage) — pure-Node, OS-independent, completes in seconds, so +# it runs on a single OS to surface red checks fast. +# The sibling workflow runs functional unit tests across ubuntu × windows × macos. +# See PLUGIN_DEVELOPMENT_GUIDE.md and references/approval-gates.md. name: power-pages-alm-lint on: @@ -11,6 +17,12 @@ jobs: alm-lint: name: alm-lint runs-on: ubuntu-latest + # Consistency backstop: keep all power-pages CI opted out of telemetry + # transmission. This job runs only the linter today (no emission), so the + # var is inert here — it guarantees no transmission if an emitting step is + # ever added to this workflow. + env: + POWER_PLATFORM_SKILLS_TELEMETRY_POWER_PAGES_OPTOUT: "1" steps: - name: checkout uses: actions/checkout@v4 diff --git a/.github/workflows/power-pages-script-tests.yml b/.github/workflows/power-pages-script-tests.yml index 293b7db39..fd9700cb2 100644 --- a/.github/workflows/power-pages-script-tests.yml +++ b/.github/workflows/power-pages-script-tests.yml @@ -1,3 +1,8 @@ +# Deliberately separate from `power-pages-alm-lint.yml`. +# This workflow runs functional unit tests across ubuntu × windows × macos to +# catch OS-specific regressions in Node scripts. Slow (~30s on macOS). +# The sibling workflow runs fast policy enforcement on a single OS so red +# checks for gate-marker / catalog drift surface before this matrix finishes. name: power-pages-script-tests on: @@ -11,6 +16,14 @@ jobs: test-power-pages-scripts: name: test-power-pages-scripts (${{ matrix.os }}) runs-on: ${{ matrix.os }} + # Defense-in-depth: opt this CI job out of telemetry transmission so even + # a test that forgets to isolate emission (no FAKE_HTTPS probe / temp + # ikey) can never POST to the production collector. Honored end-to-end + # because emit-spawn forwards this var to the detached dispatcher. The two + # positive-emission tests clear it in their own spawn env so they still + # exercise the real emit path. + env: + POWER_PLATFORM_SKILLS_TELEMETRY_POWER_PAGES_OPTOUT: "1" strategy: fail-fast: false matrix: diff --git a/.github/workflows/validate-repository-metadata.yml b/.github/workflows/validate-repository-metadata.yml index 21a05b971..658ed741e 100644 --- a/.github/workflows/validate-repository-metadata.yml +++ b/.github/workflows/validate-repository-metadata.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: permissions: - contents: read + contents: read jobs: validate-repository-metadata: @@ -22,8 +22,11 @@ jobs: with: node-version: 20 - - name: validate-kebab-case-plugin-names + - name: validate-open-plugin-names run: node scripts/validate-plugin-names.js + - name: validate-legacy-compatibility + run: node scripts/validate-legacy-compatibility.js + - name: validate-skill-descriptions run: node scripts/validate-skill-descriptions.js diff --git a/.gitignore b/.gitignore index d5331ebf8..7ba5c95d5 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ Thumbs.db .playwright-mcp/ +# oh-my-claudecode scratch state +.omc/ + diff --git a/AGENTS.md b/AGENTS.md index 019a5029f..11de86ab6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,18 +4,21 @@ This file provides guidance to AI Agents when working with code in this reposito ## What This Repo Is -A **plugin marketplace** for Power Platform development by Microsoft. The marketplace manifest (`.claude-plugin/marketplace.json`) references individual plugins in `plugins/`. Each plugin has its own `AGENTS.md` with plugin-specific guidance. +A **plugin marketplace** for Power Platform development by Microsoft. The Open Plugins marketplace manifest (`marketplace.json`) references individual plugins in `plugins/`. Legacy `.claude-plugin` manifest mirrors are kept for existing subscriptions. Each plugin has its own `AGENTS.md` with plugin-specific guidance. ## Repository Structure -```text +``` power-platform-skills/ -├── .claude-plugin/ -│ └── marketplace.json # Marketplace manifest (lists all available plugins) +├── marketplace.json # Open Plugins marketplace manifest (lists all available plugins) +├── .claude-plugin/ # Legacy manifest mirrors for existing subscriptions +│ └── marketplace.json ├── plugins/ # Directory containing individual plugins │ └── / # Individual plugin (e.g., power-pages) +│ ├── .plugin/ +│ │ └── plugin.json # Open Plugins manifest │ ├── .claude-plugin/ -│ │ └── plugin.json # Plugin manifest +│ │ └── plugin.json # Legacy manifest mirror │ ├── AGENTS.md # Plugin-specific development guidelines │ ├── agents/ # Agent persona files │ ├── commands/ # Command entry points @@ -42,7 +45,8 @@ No root-level build, lint, or test commands exist. Build/test tooling lives insi Each plugin follows this structure: -- `.claude-plugin/plugin.json` — Plugin metadata (name, version, keywords) +- `.plugin/plugin.json` — Open Plugins metadata (name, version, keywords) +- `.claude-plugin/plugin.json` — legacy mirror of `.plugin/plugin.json` kept for existing subscriptions - `.mcp.json` — MCP server configuration (optional) - `agents/` — Agent definitions (`.md` files with YAML frontmatter) - `skills/` — Skill definitions, each in its own subdirectory with a `SKILL.md` @@ -59,20 +63,36 @@ Do not mark a plugin as ready for internal publication until it has a completed ## Cross-Plugin Shared Skills -Skills that apply to all plugins live in `shared/skills//`. The workflow logic is written once in a shared `.md` file, and each plugin has a thin `skills//SKILL.md` that contains only the YAML frontmatter and a reference to the shared workflow file. +Skills that apply to all plugins live in `shared/skills//`. The workflow logic is written once in a shared `.md` file, and each plugin has a thin `skills//SKILL.md` that contains only the YAML frontmatter and a reference to the workflow path bundled inside that plugin at install time. **Pattern:** - `shared/skills//.md` — Full workflow (phases, instructions, field definitions) - `shared/skills//SKILL.template.md` — Template SKILL.md (frontmatter + reference to workflow); supports `{{PLUGIN_NAME}}` placeholder - `plugins//skills//SKILL.md` — Per-plugin wrapper generated from the template above +- `plugins//skills//.md` — Bundled workflow file copied into the plugin so installs work without repo-root shared paths + +This keeps the skill discoverable in each plugin while preserving install-time portability. Marketplace installs copy only the plugin directory, so per-plugin wrappers must not reference repo-root `shared/` paths at runtime. Instead, point the wrapper at `${PLUGIN_ROOT}/skills//.md` and keep a physical copy of the shared workflow at that per-plugin path. Do not use Git symlinks for shared content; Windows and plugin-host installs can materialize them as plain link files. When updating a shared skill, edit the workflow file and/or `SKILL.template.md` in `shared/`, then refresh the per-plugin wrappers (frontmatter + bundled workflow reference, with `{{PLUGIN_NAME}}` substituted) and copy the workflow content into each adopting plugin. Commit the shared source and per-plugin copies together. + +## Legacy Marketplace Compatibility -This keeps the skill discoverable in each plugin while avoiding content duplication. When updating a shared skill, edit the workflow file and/or `SKILL.template.md` in `shared/`, then update the per-plugin wrappers (frontmatter + reference pointing to the shared workflow, with `{{PLUGIN_NAME}}` substituted) and commit them alongside the shared change. +Keep the root `.claude-plugin/marketplace.json` and each plugin's `.claude-plugin/plugin.json` as JSON mirrors of their Open Plugins counterparts. The shared root marketplace must stay dual-compatible while keeping per-plugin entries minimal: each plugin entry should include only the required `name` and repository-root-relative `source` fields. Keep marketplace-level `owner` and `metadata` because they describe the collection, but store per-plugin display/update metadata (description, version, license, keywords, and similar fields) in each `.plugin/plugin.json` instead of duplicating or overriding it in the marketplace index. Existing marketplace subscriptions may still resolve the legacy paths during auto-update, so removing or drifting these files can force users to reinstall. Because mirrors are committed files, update both source and legacy copies together, then run `node scripts/validate-legacy-compatibility.js` after metadata changes. ## Code Conventions **DRY (Don't Repeat Yourself):** Never duplicate logic across files. Each plugin has shared utilities (e.g., `scripts/lib/`) and shared reference docs (e.g., `references/`). Always check for and reuse existing helpers before writing new code. When adding shared logic, put it in the plugin's shared modules — not in individual skill directories. +### Code comments + +Most code in this repo is Node.js scripts and hooks that shell out to `pac`/`az`, call the Dataverse and Power Platform APIs, and parse loosely structured CLI output. The reasoning behind a line is rarely obvious from the line alone, so comments matter. + +- Err on the side of over-commenting when the reasoning is not obvious. Comments should explain **why** code is written a particular way. +- Comment non-obvious implementation details: concurrency hazards, lifecycle constraints, compatibility requirements, platform quirks, upstream PAC CLI or Dataverse workarounds, and intentional deviations from the obvious helper or API. +- When parsing strings, logs, CLI output, OData payloads, or other loosely structured data, include a comment with an example of the raw format being parsed. +- When code follows an external standard, protocol, or Power Platform convention, include links to the relevant documentation so future readers can verify the rule. +- When code touches auth tokens or other privacy/security-sensitive flows, explain the scope and fail-closed behavior. +- Do not add comments that only narrate clear code. + ## Maintaining This File When you add new plugins or change the repository-level structure, update this file. For plugin-specific changes, update the plugin's own `AGENTS.md` (e.g., `plugins/power-pages/AGENTS.md`). diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..c76401ade --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @microsoft/power-platform-skills-contributors diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..686e5e7a0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,10 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns +- Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) diff --git a/README.md b/README.md index 7299784b1..f8497a198 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ This repository is a **plugin marketplace** containing agent plugins for Power P **Primary platform:** GitHub Copilot (VS Code) via the `.github/` convention. **Also supported:** Claude Code via the plugin marketplace system. +The canonical Open Plugins marketplace manifest lives at `marketplace.json`. The root `.claude-plugin/marketplace.json` and each plugin's `.claude-plugin/plugin.json` remain as compatibility mirrors for existing marketplace subscriptions. + ## Equinor Fork This repository is a fork of [`microsoft/power-platform-skills`][upstream_github], maintained for Equinor internal use. It is not a neutral mirror — it applies Equinor governance, security standards, and the shared practices of the [Equinor IT professional network][equinor_varia] before any plugin is piloted or published internally. @@ -50,10 +52,9 @@ The table below summarises every category of change. The review records in [`doc | **`.github/instructions/`** | Markdown, documentation, and alignment instruction files | Enforces shared Equinor documentation conventions across all AI-assisted authoring | | **`.devcontainer/`** | Dev container with Equinor CA certificates and standard tooling | Ensures consistent, trusted development environments inside Equinor's network | | **`plugins/code-apps/`** | EDS integration guidance, mandatory deploy confirmation, updated development standards | Aligns generated code apps with Equinor Design System and prevents accidental production deploys | -| **`scripts/install.js`** | Extended to support GitHub Copilot project-scoped installation and Equinor fork URL | Lets teams install into `.github/` for shared team use, not only user-level Claude Code install | +| **`scripts/install.js`** | Extended to support GitHub Copilot project-scoped installation, Equinor fork URLs, and Open Plugins marketplace resolution | Lets teams install into `.github/` for shared team use while staying compatible with the upstream marketplace layout | | **`scripts/validate-plugin-reviews.js`** | New script | CI-validates review records against the JSON schema before any plugin state change merges | | **`SECURITY.md`** | Updated to Equinor responsible disclosure contacts | Replaces Microsoft-only disclosure path with Equinor contacts | -| **`CODE_OF_CONDUCT.md`, `SUPPORT.md`** | Removed | Superseded by Equinor's own conduct and support processes | ### Plugin publication status @@ -144,7 +145,8 @@ Inside a Claude Code session: ```bash /plugin install power-pages@power-platform-skills /plugin install model-apps@power-platform-skills - /plugin install code-apps@power-platform-skills + /plugin install mcp-apps@power-platform-skills + /plugin install code-apps-preview@power-platform-skills /plugin install canvas-apps@power-platform-skills ``` @@ -167,7 +169,8 @@ The marketplace registry (Claude Code) is stored at `~/.claude/plugins/known_mar # Inside a Claude Code session /plugin uninstall power-pages /plugin uninstall model-apps -/plugin uninstall code-apps +/plugin uninstall mcp-apps +/plugin uninstall code-apps-preview /plugin uninstall canvas-apps /plugin marketplace remove power-platform-skills ``` @@ -186,6 +189,12 @@ Build and deploy Power Apps generative pages for model-driven apps. **Stack**: React + TypeScript + Fluent, deployed via PAC CLI +### [MCP Apps](plugins/mcp-apps/README.md) (`plugins/mcp-apps`) + +Generate interactive MCP App widgets for MCP tools. + +**Stack**: HTML widgets using the MCP Apps protocol + ### [Code Apps](plugins/code-apps/AGENTS.md) (`plugins/code-apps`) Build and deploy Power Apps code apps connected to Power Platform via connectors. @@ -208,6 +217,7 @@ To develop and test plugins locally, follow these steps: ```bash claude --plugin-dir /path/to/power-platform-skills/plugins/power-pages claude --plugin-dir /path/to/power-platform-skills/plugins/model-apps + claude --plugin-dir /path/to/power-platform-skills/plugins/mcp-apps claude --plugin-dir /path/to/power-platform-skills/plugins/code-apps claude --plugin-dir /path/to/power-platform-skills/plugins/canvas-apps ``` @@ -278,31 +288,46 @@ See the [Copilot CLI docs][gh_copilot_cli_docs] for the full reference. ```text power-platform-skills/ +├── marketplace.json # Open Plugins marketplace manifest (lists all plugins) ├── .claude-plugin/ -│ └── marketplace.json # Marketplace manifest (lists all plugins) +│ └── marketplace.json # Legacy marketplace mirror for existing subscriptions ├── .claude/ │ └── settings.json # Auto-allowed tools (pac, node, dotnet, etc.) ├── plugins/ │ ├── power-pages/ # Power Pages plugin +│ │ ├── .plugin/ +│ │ │ └── plugin.json │ │ ├── .claude-plugin/ │ │ │ └── plugin.json │ │ ├── commands/ │ │ ├── shared/ │ │ └── skills/ │ ├── model-apps/ # Model Apps plugin +│ | ├── .plugin/ +│ │ └── plugin.json │ | ├── .claude-plugin/ │ │ └── plugin.json │ | ├── commands/ │ | ├── skills/ │ | ├── shared/ # Shared references + samples │ | └── github/ # GitHub Copilot instructions +│ ├── mcp-apps/ # MCP Apps widget generator plugin +│ │ ├── .plugin/ +│ │ │ └── plugin.json +│ │ ├── references/ +│ │ ├── samples/ +│ │ └── skills/ │ ├── code-apps/ # Code Apps plugin +│ │ ├── .plugin/ +│ │ │ └── plugin.json │ │ ├── .claude-plugin/ │ │ │ └── plugin.json │ │ ├── agents/ │ │ ├── skills/ │ │ └── shared/ # Shared instructions + references │ └── canvas-apps/ # Canvas Apps plugin +│ ├── .plugin/ +│ │ └── plugin.json │ ├── .claude-plugin/ │ │ └── plugin.json │ ├── references/ # Technical + design guides @@ -311,6 +336,8 @@ power-platform-skills/ └── README.md ``` +The `.claude-plugin` files are compatibility mirrors for users who subscribed before the Open Plugins migration. The shared marketplace keeps marketplace-level `owner` and `metadata`, while each plugin entry is intentionally just `name` plus repository-root-relative `source`. + ## Documentation - [Equinor Alignment Baseline](docs/equinor-alignment/README.md) diff --git a/SECURITY.md b/SECURITY.md index 74c3713e1..b4a05ca2c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,14 +1,10 @@ -If you discover a security vulnerability in this project, please follow the steps below to report it. +If you discover a security vulnerability in this project, do not report it through public GitHub issues. -### For "non-critical" issues +### Report the issue -- **Alternative A:** -Create a GitHub issue for the vulnerability. Avoid putting sensitive information in the issue. - -- **Alternative B:** Send an email to the project's maintainer at [Hjalmar Otto Fjøsne](mailto:hjaf@equinor.com) describing the issue. -### For "critical" and time sensitive issues +### Critical or time-sensitive issues Phone the Equinor helpdesk: diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 000000000..291d4d437 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,25 @@ +# TODO: The maintainer of this repo has not yet edited this file + +**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? + +- **No CSS support:** Fill out this template with information about how to file issues and get help. +- **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps. +- **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide. + +*Then remove this first heading from this SUPPORT.MD file before publishing your repo.* + +# Support + +## How to file issues and get help + +This project uses GitHub Issues to track bugs and feature requests. Please search the existing +issues before filing new issues to avoid duplicates. For new issues, file your bug or +feature request as a new Issue. + +For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE +FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER +CHANNEL. WHERE WILL YOU HELP PEOPLE?**. + +## Microsoft Support Policy + +Support for this **PROJECT or PRODUCT** is limited to the resources listed above. diff --git a/docs/equinor-alignment/reviews/canvas-apps.json b/docs/equinor-alignment/reviews/canvas-apps.json index 8a520b413..9223c3385 100644 --- a/docs/equinor-alignment/reviews/canvas-apps.json +++ b/docs/equinor-alignment/reviews/canvas-apps.json @@ -1,6 +1,6 @@ { "pluginName": "canvas-apps", - "reviewDate": "2026-06-09", + "reviewDate": "2026-06-30", "reviewer": "GitHub Copilot", "publicationStatus": "defer", "ownership": { @@ -109,9 +109,9 @@ }, "publication": { "targetRepository": "equinor/powerplatform recommended, not confirmed", - "copilotManifest": false, + "copilotManifest": true, "claudeManifest": true, - "versionsSynchronized": false, + "versionsSynchronized": true, "variaCatalogRequired": true, "techDocsRequired": true, "architectureContractLinkRequired": true, @@ -124,10 +124,14 @@ "MCP server is installed through dnx with prerelease package use and no explicit version pinning.", "Production-system interaction is possible by default through live coauthoring and authoring service access.", "Dependencies and environment prerequisites are not fully documented.", - "Technology Radar status is unknown for .NET in this context.", - "GitHub Copilot marketplace manifest is missing." + "Technology Radar status is unknown for .NET in this context." ], "evidence": [ + { + "type": "external-source", + "reference": "https://github.com/microsoft/power-platform-skills/compare/9258fd9..0188a8b", + "summary": "Re-reviewed after upstream sync 2026-06-30 (canvas-apps v2.1.0). Added Open Plugins metadata (.plugin/plugin.json + marketplace entry) and bundled report-issue workflow content. No new scripts, hooks, or production interaction. The fork keeps its existing flat .mcp.json form; Copilot manifest blocker cleared and remaining blockers unchanged." + }, { "type": "external-source", "reference": "https://github.com/microsoft/power-platform-skills/compare/2923950..9258fd9", @@ -144,5 +148,5 @@ "summary": "Registers Canvas Authoring MCP through dnx, prerelease package, and NuGet source." } ], - "notes": "Initial review record only. This record intentionally defers publication until detailed review evidence exists. Upstream sync 2026-06-09 added optional auth_flow/login_hint parameters to the Canvas Authoring MCP connect tool (configure-canvas-mcp v2.1.0); broker remains the default and browser sign-in is opt-in, so MCP governance posture is unchanged." + "notes": "Initial review record only. This record intentionally defers publication until detailed review evidence exists. Upstream sync 2026-06-09 added optional auth_flow/login_hint parameters to the Canvas Authoring MCP connect tool (configure-canvas-mcp v2.1.0); broker remains the default and browser sign-in is opt-in, so MCP governance posture is unchanged. Upstream sync 2026-06-30 added Open Plugins metadata and bundled report-issue workflow content with no new runtime behavior." } diff --git a/docs/equinor-alignment/reviews/code-apps-preview.json b/docs/equinor-alignment/reviews/code-apps-preview.json index 9b8e2c347..1f80d3bbb 100644 --- a/docs/equinor-alignment/reviews/code-apps-preview.json +++ b/docs/equinor-alignment/reviews/code-apps-preview.json @@ -1,6 +1,6 @@ { "pluginName": "code-apps-preview", - "reviewDate": "2026-05-28", + "reviewDate": "2026-06-30", "reviewer": "GitHub Copilot", "publicationStatus": "controlled-pilot", "ownership": { @@ -118,7 +118,7 @@ "targetRepository": "equinor/powerplatform recommended, not confirmed", "copilotManifest": true, "claudeManifest": true, - "versionsSynchronized": false, + "versionsSynchronized": true, "variaCatalogRequired": true, "techDocsRequired": true, "architectureContractLinkRequired": true, @@ -127,6 +127,11 @@ }, "blockers": [], "evidence": [ + { + "type": "external-source", + "reference": "https://github.com/microsoft/power-platform-skills/compare/9258fd9..0188a8b", + "summary": "Re-reviewed after upstream sync 2026-06-30 (code-apps-preview v1.0.0). Added Open Plugins metadata (.plugin/plugin.json + marketplace entry) and bundled report-issue workflow content. Upstream generic theming/documentation changes were not taken where they would remove Equinor EDS guidance; fork-specific EDS guardrails remain in README, shared instructions, and create/deploy skill docs. No new scripts, hooks, or production interaction." + }, { "type": "file", "reference": "plugins/code-apps/README.md", @@ -183,5 +188,5 @@ "summary": "Owner clarifications: (1) DLP mapping deferred — owner states DLP is somewhat irrelevant in this context; plugin review schema will be restructured later. Blocker removed. (2) GitHub Copilot CLI confirmed to work same way as Claude CLI — same manifest serves both. copilotManifest set to true. (3) Plugin name code-apps-preview to stay as-is (mirrors upstream). All blockers cleared. Plugin eligible for controlled-pilot with no remaining hard blockers." } ], - "notes": "Skills and agents reviewed 2026-05-22. Production interaction blocked (all deploys require confirmation — intentionally scoped to dev; test/prod promotion planned via a separate CI/CD skill or agent). EDS integrated into generation workflow. Re-reviewed 2026-05-23: owner assigned, zones clarified (Green/Yellow/Red, primarily Red), Copilot Studio excluded from scope, fork references updated. Upstream sync 2026-05-23: EDS-removing upstream changes deferred; Equinor guardrails preserved. Re-reviewed 2026-05-28: Vite now Adopt, all tech radar items resolved, status upgraded to controlled-pilot. Owner answers 2026-05-28: DLP deferred (schema restructure planned), Copilot CLI confirmed same mechanism as Claude CLI, plugin name stays code-apps-preview (mirrors upstream). All blockers cleared. Next step toward ready-for-internal-pilot: successful end-to-end install test using correct plugin name, and rollback documentation." + "notes": "Skills and agents reviewed 2026-05-22. Production interaction blocked (all deploys require confirmation — intentionally scoped to dev; test/prod promotion planned via a separate CI/CD skill or agent). EDS integrated into generation workflow. Re-reviewed 2026-05-23: owner assigned, zones clarified (Green/Yellow/Red, primarily Red), Copilot Studio excluded from scope, fork references updated. Upstream sync 2026-05-23: EDS-removing upstream changes deferred; Equinor guardrails preserved. Re-reviewed 2026-05-28: Vite now Adopt, all tech radar items resolved, status upgraded to controlled-pilot. Owner answers 2026-05-28: DLP deferred (schema restructure planned), Copilot CLI confirmed same mechanism as Claude CLI, plugin name stays code-apps-preview (mirrors upstream). Upstream sync 2026-06-30 added Open Plugins metadata and bundled report-issue workflow content; Equinor EDS guardrails remain intentionally preserved. All blockers cleared. Next step toward ready-for-internal-pilot: successful end-to-end install test using correct plugin name, and rollback documentation." } diff --git a/docs/equinor-alignment/reviews/mcp-apps.json b/docs/equinor-alignment/reviews/mcp-apps.json index 1e4808871..2af5ff516 100644 --- a/docs/equinor-alignment/reviews/mcp-apps.json +++ b/docs/equinor-alignment/reviews/mcp-apps.json @@ -1,6 +1,6 @@ { "pluginName": "mcp-apps", - "reviewDate": "2026-05-28", + "reviewDate": "2026-06-30", "reviewer": "GitHub Copilot", "publicationStatus": "defer", "ownership": { @@ -97,7 +97,7 @@ "targetRepository": "equinor/powerplatform (recommended if Power Platform scope retained)", "copilotManifest": true, "claudeManifest": true, - "versionsSynchronized": false, + "versionsSynchronized": true, "variaCatalogRequired": true, "techDocsRequired": true, "architectureContractLinkRequired": false, @@ -111,6 +111,11 @@ "Install and rollback not tested." ], "evidence": [ + { + "type": "external-source", + "reference": "https://github.com/microsoft/power-platform-skills/compare/9258fd9..0188a8b", + "summary": "Re-reviewed after upstream sync 2026-06-30 (mcp-apps v1.0.0). Added Open Plugins metadata (.plugin/plugin.json + marketplace entry) and bundled report-issue workflow content. No runtime behavior change, no new scripts, and no change to the external-call surface. Manifest mirrors now validate cleanly; remaining blockers unchanged." + }, { "type": "file", "reference": "plugins/mcp-apps/README.md", @@ -157,5 +162,5 @@ "summary": "Owner confirmed: @equinor/dcs-power-platform-team owns the plugin; support via #powerplatform Slack. EDS should be preferred — needs assessment whether EDS provides sufficient/equivalent components to replace Fluent UI Web Components. CDN dependency is contingent on EDS outcome; CSP rules need per-environment updates for all referenced CDNs. Copilot CLI and Claude CLI use the same manifest mechanism." } ], - "notes": "Full review completed 2026-05-28. Skills, samples, and references are reviewed and contain no secrets, no production interaction, and include XSS/accessibility protections. Owner assigned 2026-05-28 (@equinor/dcs-power-platform-team, #powerplatform). EDS preferred — assessment needed for component equivalence vs Fluent UI Web Components. CDN dependency requires per-environment CSP updates. Copilot CLI and Claude CLI use the same manifest (copilotManifest set true). Remaining blockers: EDS assessment, Fluent UI radar status (may become moot), CDN/CSP planning, install test. Plugin eligible for controlled-pilot once EDS assessment is completed and install is tested." + "notes": "Full review completed 2026-05-28. Skills, samples, and references are reviewed and contain no secrets, no production interaction, and include XSS/accessibility protections. Owner assigned 2026-05-28 (@equinor/dcs-power-platform-team, #powerplatform). EDS preferred — assessment needed for component equivalence vs Fluent UI Web Components. CDN dependency requires per-environment CSP updates. Copilot CLI and Claude CLI use the same manifest (copilotManifest set true). Upstream sync 2026-06-30 added Open Plugins metadata and bundled report-issue workflow content with no runtime behavior change. Remaining blockers: EDS assessment, Fluent UI radar status (may become moot), CDN/CSP planning, install test. Plugin eligible for controlled-pilot once EDS assessment is completed and install is tested." } diff --git a/docs/equinor-alignment/reviews/model-apps.json b/docs/equinor-alignment/reviews/model-apps.json index 4baee3f38..dc03bbd08 100644 --- a/docs/equinor-alignment/reviews/model-apps.json +++ b/docs/equinor-alignment/reviews/model-apps.json @@ -1,6 +1,6 @@ { "pluginName": "model-apps", - "reviewDate": "2026-06-09", + "reviewDate": "2026-06-30", "reviewer": "GitHub Copilot", "publicationStatus": "defer", "ownership": { @@ -133,9 +133,9 @@ }, "publication": { "targetRepository": "equinor/powerplatform recommended, not confirmed", - "copilotManifest": false, + "copilotManifest": true, "claudeManifest": true, - "versionsSynchronized": false, + "versionsSynchronized": true, "variaCatalogRequired": true, "techDocsRequired": true, "architectureContractLinkRequired": true, @@ -148,10 +148,14 @@ "Production-system interaction is possible by default through Dataverse schema creation, app creation, deployment, and browser verification workflows.", "Data classification ceiling is not documented.", "EDS position is unresolved because generated UI currently uses Fluent UI V9.", - "Technology Radar status is unknown for Fluent UI and Playwright.", - "GitHub Copilot marketplace manifest is missing." + "Technology Radar status is unknown for Fluent UI and Playwright." ], "evidence": [ + { + "type": "external-source", + "reference": "https://github.com/microsoft/power-platform-skills/compare/9258fd9..0188a8b", + "summary": "Re-reviewed after upstream sync 2026-06-30 (model-apps v2.2.0). Added Open Plugins metadata (.plugin/plugin.json + marketplace entry), bundled report-issue workflow content, and PLUGIN_ROOT-aligned helper text in local-dev/eval helpers. No new production interaction beyond the 2026-06-09 review. Copilot manifest blocker cleared; remaining blockers unchanged." + }, { "type": "external-source", "reference": "https://github.com/microsoft/power-platform-skills/compare/2923950..9258fd9", @@ -173,5 +177,5 @@ "summary": "Contains Node.js scripts that require security and behavior review before publication." } ], - "notes": "Initial review record only. This record intentionally defers publication until detailed review evidence exists. Upstream v2.2.0 synced 2026-06-09 adds an automated eval suite, two new local-dev scripts (capture-fixture.js, generate-page-manifest.js), supported-dependencies guidance, and dialog/overlay confinement rules. The new scripts are local dev/eval tooling with no new production interaction, but enlarge the script surface that still requires formal review before publication." + "notes": "Initial review record only. This record intentionally defers publication until detailed review evidence exists. Upstream v2.2.0 synced 2026-06-09 adds an automated eval suite, two new local-dev scripts (capture-fixture.js, generate-page-manifest.js), supported-dependencies guidance, and dialog/overlay confinement rules. The new scripts are local dev/eval tooling with no new production interaction, but enlarge the script surface that still requires formal review before publication. Upstream sync 2026-06-30 added Open Plugins metadata and bundled report-issue workflow content; the Copilot manifest blocker is now cleared." } diff --git a/docs/equinor-alignment/reviews/power-pages.json b/docs/equinor-alignment/reviews/power-pages.json index 2d450b214..e65fa33c0 100644 --- a/docs/equinor-alignment/reviews/power-pages.json +++ b/docs/equinor-alignment/reviews/power-pages.json @@ -1,13 +1,13 @@ { "pluginName": "power-pages", - "reviewDate": "2026-06-09", + "reviewDate": "2026-06-30", "reviewer": "GitHub Copilot", "publicationStatus": "defer", "ownership": { "owner": "not assigned", "supportChannel": "not assigned", "upstreamSource": "microsoft/power-platform-skills/plugins/power-pages", - "upstreamVersion": "2.0.0" + "upstreamVersion": "2.6.0" }, "scope": { "summary": "Creates, tests, activates, and deploys Power Pages code sites; can create Dataverse data models, sample data, web roles, site settings, table permissions, Web API integrations, server logic, and cloud flow integrations. Includes ALM workflows (plan-alm, setup-solution, setup-pipeline, export-solution, import-solution, deploy-pipeline, ensure-pipelines-host, force-link-environment, configure-env-variables, diagnose-deployment) and security skills (manage-firewall, manage-headers, scan-site, security-review).", @@ -169,9 +169,9 @@ }, "publication": { "targetRepository": "equinor/powerplatform recommended, not confirmed", - "copilotManifest": false, + "copilotManifest": true, "claudeManifest": true, - "versionsSynchronized": false, + "versionsSynchronized": true, "variaCatalogRequired": true, "techDocsRequired": true, "architectureContractLinkRequired": true, @@ -184,10 +184,14 @@ "Production-system interaction is possible by default through deploy, activate, Dataverse, Web API, permissions, and cloud flow workflows.", "Data classification ceiling is not documented.", "EDS guidance is not integrated into generated frontend workflows.", - "Technology Radar status is unknown for several framework and testing dependencies.", - "GitHub Copilot marketplace manifest is missing." + "Technology Radar status is unknown for several framework and testing dependencies." ], "evidence": [ + { + "type": "external-source", + "reference": "https://github.com/microsoft/power-platform-skills/compare/9258fd9..0188a8b", + "summary": "Re-reviewed after upstream sync 2026-06-30 (power-pages v2.6.0). Synced AI Web API scaffolding, additional ALM planning/discovery improvements, check-activation-status and plan-refresh enhancements, Open Plugins metadata, and non-telemetry Power Pages script updates. The new upstream telemetry stack was intentionally excluded from the Equinor sync branch pending separate governance review. Copilot manifest blocker cleared; remaining blockers unchanged." + }, { "type": "external-source", "reference": "https://github.com/microsoft/power-platform-skills/compare/2923950..9258fd9", @@ -214,5 +218,5 @@ "summary": "Contains many Node.js scripts that require security and behavior review before publication." } ], - "notes": "Initial review record only. Upstream v2.0.0 synced 2026-05-27 adds significant ALM and security capabilities (10 new ALM skills, 4 security skills, 45 lib helpers, 6 reference docs). The 2026-06-09 sync (commit b4c46e9) expands setup-auth to multi-provider authentication and SPA flows and adds a Dataverse dev-environment solution check and a local auth-report renderer. This record intentionally defers publication until detailed review evidence exists. The expanded surface area — especially ALM pipeline management, deployment automation, firewall rules, security scanning, and now multi-provider auth — increases the review scope considerably." + "notes": "Initial review record only. Upstream v2.0.0 synced 2026-05-27 adds significant ALM and security capabilities (10 new ALM skills, 4 security skills, 45 lib helpers, 6 reference docs). The 2026-06-09 sync (commit b4c46e9) expands setup-auth to multi-provider authentication and SPA flows and adds a Dataverse dev-environment solution check and a local auth-report renderer. The 2026-06-30 sync raises upstreamVersion to 2.6.0 and adds AI Web API scaffolding plus further ALM discovery and plan-refresh updates; the upstream telemetry stack was intentionally excluded pending separate governance review. This record intentionally defers publication until detailed review evidence exists. The expanded surface area — especially ALM pipeline management, deployment automation, firewall rules, security scanning, multi-provider auth, and now AI Web API scaffolding — increases the review scope considerably." } diff --git a/evals/model-apps/genpage/evals.json b/evals/model-apps/genpage/evals.json index 850cc1cb7..1e64908f1 100644 --- a/evals/model-apps/genpage/evals.json +++ b/evals/model-apps/genpage/evals.json @@ -59,7 +59,7 @@ "Phase 2: Entity-builder is SKIPPED (no entities to create)", "Phase 4: pac model genpage generate-types --data-sources 'account' --output-file /RuntimeTypes.ts is run", "Phase 5b (single-page fast path): Plan has 1 page so orchestrator inlines the build — NO Task subagent dispatched for the page-builder", - "Phase 5b: Orchestrator reads ${CLAUDE_PLUGIN_ROOT}/references/verified-icons.txt before writing the .tsx", + "Phase 5b: Orchestrator reads ${PLUGIN_ROOT}/references/verified-icons.txt before writing the .tsx", "Phase 5b: A relevant sample file is read (e.g., 7-responsive-cards.tsx for card layout)", "Phase 5b: Generated .tsx uses only column names verified from RuntimeTypes.ts — no guessed names", "Phase 5b: After writing, orchestrator greps the .tsx for `from \"@fluentui/react-icons\"` imports and verifies each named import against verified-icons.txt — rewrites if any are missing", diff --git a/evals/model-apps/genpage/fixtures/1-account-card-gallery/workflow-log.md b/evals/model-apps/genpage/fixtures/1-account-card-gallery/workflow-log.md index 2c99c01f6..c771abb92 100644 --- a/evals/model-apps/genpage/fixtures/1-account-card-gallery/workflow-log.md +++ b/evals/model-apps/genpage/fixtures/1-account-card-gallery/workflow-log.md @@ -50,7 +50,7 @@ - Plan has 1 page → fast path taken (inlined build, no Task subagent dispatched for the page-builder) - Data mode: dataverse - Read sample: plugins/model-apps/samples/7-responsive-cards.tsx (closest match for card layout) -- Read ${CLAUDE_PLUGIN_ROOT}/references/verified-icons.txt to source icon names +- Read ${PLUGIN_ROOT}/references/verified-icons.txt to source icon names - Wrote page.tsx - Post-write icon verification: grep `from "@fluentui/react-icons"` in page.tsx; verified `BuildingRegular`, `MailRegular`, `PhoneRegular`, `GlobeRegular` against verified-icons.txt — all present diff --git a/evals/model-apps/genpage/fixtures/13-contact-localization/workflow-log.md b/evals/model-apps/genpage/fixtures/13-contact-localization/workflow-log.md index 887b1372a..53e4eefe6 100644 --- a/evals/model-apps/genpage/fixtures/13-contact-localization/workflow-log.md +++ b/evals/model-apps/genpage/fixtures/13-contact-localization/workflow-log.md @@ -52,8 +52,8 @@ - Plan has 1 page → fast path taken (inlined build, no Task subagent dispatched) - Data mode: dataverse - Read sample: plugins/model-apps/samples/9-list-with-caching.tsx (Dataverse list pattern) -- Read ${CLAUDE_PLUGIN_ROOT}/references/localization.md (multi-language + RTL pattern) -- Read ${CLAUDE_PLUGIN_ROOT}/references/verified-icons.txt +- Read ${PLUGIN_ROOT}/references/localization.md (multi-language + RTL pattern) +- Read ${PLUGIN_ROOT}/references/verified-icons.txt - Wrote page.tsx with: - Xrm.Utility.getGlobalContext().userSettings.languageId for language detection - LOCALE_MAP for 1033/1025/1036 → BCP-47 + isRtl diff --git a/evals/model-apps/genpage/fixtures/4-case-wizard/workflow-log.md b/evals/model-apps/genpage/fixtures/4-case-wizard/workflow-log.md index 522c49891..8d5d467f8 100644 --- a/evals/model-apps/genpage/fixtures/4-case-wizard/workflow-log.md +++ b/evals/model-apps/genpage/fixtures/4-case-wizard/workflow-log.md @@ -48,7 +48,7 @@ - Plan has 1 page → fast path taken (inlined build, no Task subagent dispatched) - Data mode: dataverse - Read sample: plugins/model-apps/samples/2-wizard-multi-step.tsx (wizard pattern reference) -- Read ${CLAUDE_PLUGIN_ROOT}/references/verified-icons.txt to source icon names +- Read ${PLUGIN_ROOT}/references/verified-icons.txt to source icon names - Wrote page.tsx (~7 KB) - Post-write icon verification: grep `from "@fluentui/react-icons"` in page.tsx; verified `PersonRegular`, `DocumentRegular`, `CheckmarkCircleRegular` against verified-icons.txt — all present diff --git a/evals/model-apps/genpage/fixtures/7-job-candidates-new-entities/workflow-log.md b/evals/model-apps/genpage/fixtures/7-job-candidates-new-entities/workflow-log.md index b6db82a9f..2036fdc45 100644 --- a/evals/model-apps/genpage/fixtures/7-job-candidates-new-entities/workflow-log.md +++ b/evals/model-apps/genpage/fixtures/7-job-candidates-new-entities/workflow-log.md @@ -41,7 +41,7 @@ - ## Entity Creation Required has 2 entities with suffix-only names (jobrequisition, candidate) ## Phase 2a — Pre-flight auth check -- node ${CLAUDE_PLUGIN_ROOT}/scripts/check-auth.js → returned `{ ok: true, ... }` +- node ${PLUGIN_ROOT}/scripts/check-auth.js → returned `{ ok: true, ... }` - Identity match between pac and az verified ## Phase 2b — Entity Builder (genpage-entity-builder agent invoked via Task) @@ -76,7 +76,7 @@ - Plan has 1 page → fast path taken (inlined build, no Task subagent dispatched) - Data mode: dataverse - Read sample: plugins/model-apps/samples/9-list-with-caching.tsx (Dataverse list + window cache) -- Read ${CLAUDE_PLUGIN_ROOT}/references/verified-icons.txt +- Read ${PLUGIN_ROOT}/references/verified-icons.txt - Wrote page.tsx - Post-write icon verification: grep `from "@fluentui/react-icons"` in page.tsx; verified `PeopleRegular`, `BriefcaseRegular` against verified-icons.txt — all present diff --git a/evals/model-apps/genpage/lib/assertions-layer-1.js b/evals/model-apps/genpage/lib/assertions-layer-1.js index e65884f3d..cb049475f 100644 --- a/evals/model-apps/genpage/lib/assertions-layer-1.js +++ b/evals/model-apps/genpage/lib/assertions-layer-1.js @@ -500,7 +500,7 @@ PHASE_EXPECTATIONS.set( ); PHASE_EXPECTATIONS.set( - 'Phase 5b: Orchestrator reads ${CLAUDE_PLUGIN_ROOT}/references/verified-icons.txt before writing the .tsx', + 'Phase 5b: Orchestrator reads ${PLUGIN_ROOT}/references/verified-icons.txt before writing the .tsx', ({ fixture }) => { const log = fixture.workflowLog; if (!log) return fail('no workflow-log.md'); diff --git a/evals/power-pages/add-ai-webapi/evals.json b/evals/power-pages/add-ai-webapi/evals.json new file mode 100644 index 000000000..075180caf --- /dev/null +++ b/evals/power-pages/add-ai-webapi/evals.json @@ -0,0 +1,27 @@ +{ + "skill_name": "add-ai-webapi", + "notes": "Dry-run decision-trace evals: each prompt asks the subagent to analyse a Power Pages fixture project using the skill and output the decisions it would make (integration manifest, Phase 5 agent-invocation prompts, Phase 6 settings plan). Sub-skill/agent invocations are mocked — the subagent prints what it would send without actually spawning agents. This isolates the skill's classification and routing logic from the broader Power Pages environment.", + "evals": [ + { + "id": 1, + "name": "list-only-workorders", + "prompt": "You have read access to a Power Pages Single Page Application (SPA) site fixture at . The site has a WorkOrderList.tsx page that fetches and iterates over a collection of cr363_workorder records (it's a list page). The user has just invoked the /add-ai-webapi skill on this project and wants to add an AI summary to this list page.\n\nLoad the skill from . Execute Phases 1-3 (site check, explore, plan review) and draft what Phase 5 and Phase 6 WOULD do — but DO NOT actually invoke any sub-skills, sub-agents, or run any scripts. Instead, produce a single output file at /decision-trace.md containing:\n\n1. ## Phase 2 integration manifest — the compiled manifest table, one row per target, with the new Target kind column.\n2. ## Phase 3 AskUserQuestion prompts — the exact question(s) you would ask the user (integration selection + per-target follow-ups), with the full option list per question.\n3. ## Phase 5 agent-invocation prompt — for every data/case target, the exact prompt string you would send to the ai-webapi-integration agent, with all placeholders filled in.\n4. ## Phase 6 settings-architect invocation — the exact prompt string you would send to the ai-webapi-settings-architect agent.\n5. ## Projected settings plan — the settings the architect would propose (name + value + write-path script-or-manual for each), based on the prompt-pattern and URL-form rules in the architect's Step 3/5.\n\nFor any AskUserQuestion, assume the user picks the first/Recommended option. Do not actually modify any files in the fixture. Do not invoke Task, Skill, or Bash to run scripts.", + "files": [], + "fixture": "list-only-workorders" + }, + { + "id": 2, + "name": "single-record-case-regression", + "prompt": "You have read access to a Power Pages Single Page Application (SPA) site fixture at . The site has a CaseDetail.tsx page that reads a case id from the URL querystring and displays a single incident record. The user has just invoked the /add-ai-webapi skill on this project and wants to add the canonical case-page Copilot preset.\n\nLoad the skill from . Execute Phases 1-3 and draft what Phase 5 and Phase 6 WOULD do — but DO NOT actually invoke any sub-skills, sub-agents, or run any scripts. Produce /decision-trace.md with the same 5 sections as eval 1.\n\nFor any AskUserQuestion, assume the user picks the first/Recommended option. Do not actually modify any files in the fixture. Do not invoke Task, Skill, or Bash to run scripts.", + "files": [], + "fixture": "single-record-case" + }, + { + "id": 3, + "name": "collision-search-vs-list-results", + "prompt": "You have read access to a Power Pages Single Page Application (SPA) site fixture at . The site has TWO pages: SearchResults.tsx (a pure search-results page that calls /_api/search/v1.0/query) AND WorkOrderResults.tsx (which iterates over a collection of cr363_workorder records — despite the `Results` suffix, it's a Dataverse list). The user wants to add AI summaries to both pages.\n\nLoad the skill from . Execute Phases 1-3 and draft what Phase 5 and Phase 6 WOULD do — but DO NOT actually invoke any sub-skills, sub-agents, or run any scripts. Produce /decision-trace.md with the same 5 sections as eval 1.\n\nFor any AskUserQuestion, assume the user picks the first/Recommended option. Do not actually modify any files in the fixture. Do not invoke Task, Skill, or Bash to run scripts.", + "files": [], + "fixture": "collision-search-vs-results" + } + ] +} diff --git a/evals/power-pages/create-site/evals.json b/evals/power-pages/create-site/evals.json index d7ee5a0de..3a14c3c7f 100644 --- a/evals/power-pages/create-site/evals.json +++ b/evals/power-pages/create-site/evals.json @@ -16,7 +16,7 @@ "Dev server is started in background via 'npm run dev' and the localhost URL is shared with the user", "Playwright browser_navigate and browser_snapshot are used to verify the scaffold loads (no screenshots taken)", "Component planning asks for features (context-aware options for an external company portal), aesthetic direction (Bold & Vibrant selected), and mood", - "Design aesthetics reference is read from ${CLAUDE_PLUGIN_ROOT}/skills/create-site/references/design-aesthetics.md", + "Design aesthetics reference is read from ${PLUGIN_ROOT}/skills/create-site/references/design-aesthetics.md", "A component plan table is presented showing pages, shared components, design elements, and routes", "Color palette is determined with 6 CSS variables (--color-primary, --color-secondary, --color-bg, --color-surface, --color-text, --color-text-muted)", "Implementation plan is presented with Section A (Design & Pages) and Section B (Review & Deployment) and user approval is requested", diff --git a/evals/power-pages/setup-auth/evals.json b/evals/power-pages/setup-auth/evals.json index 822227857..14aa45145 100644 --- a/evals/power-pages/setup-auth/evals.json +++ b/evals/power-pages/setup-auth/evals.json @@ -541,7 +541,7 @@ "expectations": [ "Detects the site as a React framework project", "Asks the optional features question in Phase 2.1 and the user selects Terms and Conditions", - "Runs `node \"${CLAUDE_PLUGIN_ROOT}/scripts/check-solution-installed.js\" --solutionName \"msdynce_PortalPrivacyExtensions\"` BEFORE asking the GDPR prerequisite question", + "Runs `node \"${PLUGIN_ROOT}/scripts/check-solution-installed.js\" --solutionName \"msdynce_PortalPrivacyExtensions\"` BEFORE asking the GDPR prerequisite question", "Parses the script's stdout JSON result and branches based on the `installed` boolean", "When the script returns installed: true, skips the manual prerequisite confirmation entirely and tells the user the solution is detected (including the version from the JSON) before proceeding to collect Terms content", "When the script returns installed: false, tells the user clearly that Terms and Conditions will NOT be enforced by the server until msdynce_PortalPrivacyExtensions is installed (Authentication/Registration/TermsAgreementEnabled is silently ignored without it), then asks via AskUserQuestion whether to continue scaffolding anyway or skip Terms entirely (two options, not three)", diff --git a/marketplace.json b/marketplace.json new file mode 100644 index 000000000..43bc23599 --- /dev/null +++ b/marketplace.json @@ -0,0 +1,32 @@ +{ + "name": "power-platform-skills", + "owner": { + "name": "Microsoft" + }, + "metadata": { + "description": "Plugins for Power Platform development with Claude Code and GitHub Copilot", + "pluginRoot": "." + }, + "plugins": [ + { + "name": "power-pages", + "source": "./plugins/power-pages" + }, + { + "name": "model-apps", + "source": "./plugins/model-apps" + }, + { + "name": "mcp-apps", + "source": "./plugins/mcp-apps" + }, + { + "name": "canvas-apps", + "source": "./plugins/canvas-apps" + }, + { + "name": "code-apps-preview", + "source": "./plugins/code-apps" + } + ] +} diff --git a/plugins/canvas-apps/.claude-plugin/plugin.json b/plugins/canvas-apps/.claude-plugin/plugin.json index 142a9e794..89d5a3eef 100644 --- a/plugins/canvas-apps/.claude-plugin/plugin.json +++ b/plugins/canvas-apps/.claude-plugin/plugin.json @@ -6,8 +6,8 @@ "name": "Microsoft", "url": "https://www.microsoft.com" }, - "homepage": "https://github.com/microsoft/power-platform-skills/", - "repository": "https://github.com/microsoft/power-platform-skills/", + "homepage": "https://github.com/equinor/power-platform-skills/", + "repository": "https://github.com/equinor/power-platform-skills/", "license": "MIT", "keywords": [ "canvas-apps", diff --git a/plugins/canvas-apps/.plugin/plugin.json b/plugins/canvas-apps/.plugin/plugin.json new file mode 100644 index 000000000..89d5a3eef --- /dev/null +++ b/plugins/canvas-apps/.plugin/plugin.json @@ -0,0 +1,19 @@ +{ + "name": "canvas-apps", + "version": "2.1.0", + "description": "Build Power Apps Canvas Apps using the Canvas Authoring MCP server.", + "author": { + "name": "Microsoft", + "url": "https://www.microsoft.com" + }, + "homepage": "https://github.com/equinor/power-platform-skills/", + "repository": "https://github.com/equinor/power-platform-skills/", + "license": "MIT", + "keywords": [ + "canvas-apps", + "power-apps", + "canvas", + "pa-yaml", + "msapp" + ] +} diff --git a/plugins/canvas-apps/AGENTS.md b/plugins/canvas-apps/AGENTS.md index 3ee0a9c10..f6d57d459 100644 --- a/plugins/canvas-apps/AGENTS.md +++ b/plugins/canvas-apps/AGENTS.md @@ -19,7 +19,7 @@ claude --plugin-dir /path/to/plugins/canvas-apps ## Architecture ``` -.claude-plugin/plugin.json ← Plugin metadata (name, version, keywords) +.plugin/plugin.json ← Open Plugins metadata (name, version, keywords) .mcp.json ← MCP server config (canvas-authoring, auto-registered) AGENTS.md ← Plugin guidance for AI agents (this file) CLAUDE.md ← Symlink → AGENTS.md diff --git a/plugins/canvas-apps/README.md b/plugins/canvas-apps/README.md index 9405994b0..3c0cc278b 100644 --- a/plugins/canvas-apps/README.md +++ b/plugins/canvas-apps/README.md @@ -1,81 +1,81 @@ -# Canvas Apps Plugin - -Build Power Apps Canvas Apps with your coding agent as coauthor. This plugin connects AI coding assistants to the Power Apps authoring service over stdio, enabling them to validate .pa.yaml files, browse available controls and their properties, discover APIs/connectors and data sources, and sync app state from live coauthoring sessions. - -> **Preview:** This plugin is currently in [preview](https://www.microsoft.com/en-us/business-applications/legal/supp-powerplatform-preview/). These features are available before official release for customers to provide feedback. - -## Prerequisites - -- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) - -## Installation - -### From the marketplace - -```bash -/plugin marketplace add microsoft/power-platform-skills -/plugin install canvas-apps@power-platform-skills -``` - -### From a local clone - -```bash -claude --plugin-dir /path/to/power-platform-skills/plugins/canvas-apps -``` - -## Skills - -### `/canvas-app` - -Create or edit a Canvas App. Automatically detects whether to generate a new app from scratch -or edit an existing one based on the current app state. - -**Usage:** Invoke directly with `/canvas-app`, or use natural language to trigger it: - -- `Create a Canvas App for managing inventory` -- `I need a Canvas App for tracking employee time off` -- `Modify the form in my existing Canvas App to include validation` -- `Edit my Canvas App to add a new screen for reports` - -### `/configure-canvas-mcp` - -Configure the Canvas Authoring MCP server for the current coauthoring session. The MCP server is auto-registered by the plugin — this skill connects it to a specific app. - -**Usage:** Invoke directly with `/configure-canvas-mcp`, or use any of the keywords below to trigger it automatically: - -- `Configure MCP for Canvas Apps` -- `Set up the Canvas Authoring MCP server` -- `Connect Canvas Apps MCP` - -## MCP Tools - -The `canvas-authoring` MCP server exposes the following tools: - -| Tool | Description | -|------|-------------| -| `configure` | Configures the MCP server for a specific coauthoring session (environment ID, app ID, cluster category) | -| `compile_canvas` | Validates canvas app YAML files in a directory using the Power Apps authoring service | -| `describe_api` | Gets detailed information about a specific API (connector) including its operations and parameters | -| `describe_control` | Gets detailed information about a specific Power Apps control including properties, variants, and metadata | -| `get_data_source_schema` | Gets the schema (columns and their Power Fx types) for a specific data source in the current authoring session | -| `list_apis` | Lists all available APIs (connectors) in the current authoring session | -| `list_controls` | Lists all available Power Apps controls in the current authoring session | -| `list_data_sources` | Lists all available data sources in the current authoring session | -| `sync_canvas` | Syncs the current coauthoring session state from the server to a local directory, writing all YAML files | - -## Security - -Your credentials are always handled securely through the official Azure Identity SDK - we never store or manage tokens directly. - -MCP is a new and developing standard. As with all new technology standards, you should review the security of any systems that integrate with MCP servers, such as MCP hosts, clients, agents, AI applications, and models and confirm that they comply with system requirements, standards, and expectations. You should follow Microsoft security guidance for MCP servers, including enabling Entra ID authentication, secure token management, and network isolation. Refer to Microsoft Security Documentation for details. - - -## Support - -If you face issues with: - -- **Using the MCP Plugin:** Report your issue here: [aka.ms/power-skills-canvas-issues](https://aka.ms/power-skills-canvas-issues). (Microsoft Support won't help you with issues related to this MCP Plugin, but they will help with related, underlying platform and feature issues.) -- **The core features in Microsoft Power Apps Canvas Apps:** Use your standard channel to contact Microsoft Support. - -## License +# Canvas Apps Plugin + +Build Power Apps Canvas Apps with your coding agent as coauthor. This plugin connects AI coding assistants to the Power Apps authoring service over stdio, enabling them to validate .pa.yaml files, browse available controls and their properties, discover APIs/connectors and data sources, and sync app state from live coauthoring sessions. + +> **Preview:** This plugin is currently in [preview](https://www.microsoft.com/en-us/business-applications/legal/supp-powerplatform-preview/). These features are available before official release for customers to provide feedback. + +## Prerequisites + +- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) + +## Installation + +### From the marketplace + +```bash +/plugin marketplace add equinor/power-platform-skills +/plugin install canvas-apps@power-platform-skills +``` + +### From a local clone + +```bash +claude --plugin-dir /path/to/power-platform-skills/plugins/canvas-apps +``` + +## Skills + +### `/canvas-app` + +Create or edit a Canvas App. Automatically detects whether to generate a new app from scratch +or edit an existing one based on the current app state. + +**Usage:** Invoke directly with `/canvas-app`, or use natural language to trigger it: + +- `Create a Canvas App for managing inventory` +- `I need a Canvas App for tracking employee time off` +- `Modify the form in my existing Canvas App to include validation` +- `Edit my Canvas App to add a new screen for reports` + +### `/configure-canvas-mcp` + +Configure the Canvas Authoring MCP server for the current coauthoring session. The MCP server is auto-registered by the plugin — this skill connects it to a specific app. + +**Usage:** Invoke directly with `/configure-canvas-mcp`, or use any of the keywords below to trigger it automatically: + +- `Configure MCP for Canvas Apps` +- `Set up the Canvas Authoring MCP server` +- `Connect Canvas Apps MCP` + +## MCP Tools + +The `canvas-authoring` MCP server exposes the following tools: + +| Tool | Description | +|------|-------------| +| `configure` | Configures the MCP server for a specific coauthoring session (environment ID, app ID, cluster category) | +| `compile_canvas` | Validates canvas app YAML files in a directory using the Power Apps authoring service | +| `describe_api` | Gets detailed information about a specific API (connector) including its operations and parameters | +| `describe_control` | Gets detailed information about a specific Power Apps control including properties, variants, and metadata | +| `get_data_source_schema` | Gets the schema (columns and their Power Fx types) for a specific data source in the current authoring session | +| `list_apis` | Lists all available APIs (connectors) in the current authoring session | +| `list_controls` | Lists all available Power Apps controls in the current authoring session | +| `list_data_sources` | Lists all available data sources in the current authoring session | +| `sync_canvas` | Syncs the current coauthoring session state from the server to a local directory, writing all YAML files | + +## Security + +Your credentials are always handled securely through the official Azure Identity SDK - we never store or manage tokens directly. + +MCP is a new and developing standard. As with all new technology standards, you should review the security of any systems that integrate with MCP servers, such as MCP hosts, clients, agents, AI applications, and models and confirm that they comply with system requirements, standards, and expectations. You should follow Microsoft security guidance for MCP servers, including enabling Entra ID authentication, secure token management, and network isolation. Refer to Microsoft Security Documentation for details. + + +## Support + +If you face issues with: + +- **Using the MCP Plugin:** Report your issue here: [aka.ms/power-skills-canvas-issues](https://aka.ms/power-skills-canvas-issues). (Microsoft Support won't help you with issues related to this MCP Plugin, but they will help with related, underlying platform and feature issues.) +- **The core features in Microsoft Power Apps Canvas Apps:** Use your standard channel to contact Microsoft Support. + +## License See the [LICENSE](../../LICENSE) file for license information. \ No newline at end of file diff --git a/plugins/canvas-apps/agents/canvas-app-planner.md b/plugins/canvas-apps/agents/canvas-app-planner.md index 44e400615..2d801ba01 100644 --- a/plugins/canvas-apps/agents/canvas-app-planner.md +++ b/plugins/canvas-apps/agents/canvas-app-planner.md @@ -35,7 +35,7 @@ Your prompt includes: - The user's requirements - The **approved plan** (screens, aesthetic direction / approach, data strategy) - The working directory where files should be written -- The plugin root directory (`${CLAUDE_PLUGIN_ROOT}`) +- The plugin root directory (`${PLUGIN_ROOT}`) - **CREATE-specific context:** user preferences (target users, aesthetic, features) - **EDIT-specific context:** current app state (palette, variables, layout), synced file list @@ -45,8 +45,8 @@ Your prompt includes: Read both reference documents before writing anything: -- `${CLAUDE_PLUGIN_ROOT}/references/TechnicalGuide.md` -- `${CLAUDE_PLUGIN_ROOT}/references/DesignGuide.md` +- `${PLUGIN_ROOT}/references/TechnicalGuide.md` +- `${PLUGIN_ROOT}/references/DesignGuide.md` Internalize both. These govern every YAML syntax and design decision. @@ -142,7 +142,7 @@ Write `canvas-app-plan.md` to the working directory. This document is the **sing truth** for all `canvas-screen-builder` agents — each agent will only `Read` this file and will not call MCP tools. The document must be fully self-contained. -Read `${CLAUDE_PLUGIN_ROOT}/references/PlanTemplates.md` for the mode-appropriate document +Read `${PLUGIN_ROOT}/references/PlanTemplates.md` for the mode-appropriate document structure (CREATE or EDIT). Follow the template exactly — fill in every section with real content from the approved plan, discovery results, and control definitions. Do not omit sections unless the template says to. diff --git a/plugins/canvas-apps/agents/canvas-screen-builder.md b/plugins/canvas-apps/agents/canvas-screen-builder.md index 61d0676bd..4cba9273a 100644 --- a/plugins/canvas-apps/agents/canvas-screen-builder.md +++ b/plugins/canvas-apps/agents/canvas-screen-builder.md @@ -103,7 +103,7 @@ reserve your reasoning for logic correctness that the compiler cannot catch. After writing or editing the file, run the runtime-anti-pattern checks that `compile_canvas` does not catch. -1. Read `${CLAUDE_PLUGIN_ROOT}/references/QAChecks.md` +1. Read `${PLUGIN_ROOT}/references/QAChecks.md` 2. Re-read the `.pa.yaml` file you just wrote or edited 3. Apply each check in order; for every issue found, fix it inline using `Edit` 4. Track the count and a one-line description of every fix applied diff --git a/plugins/canvas-apps/skills/canvas-app/SKILL.md b/plugins/canvas-apps/skills/canvas-app/SKILL.md index 8f8108842..0d7217827 100644 --- a/plugins/canvas-apps/skills/canvas-app/SKILL.md +++ b/plugins/canvas-apps/skills/canvas-app/SKILL.md @@ -1,407 +1,407 @@ ---- -name: canvas-app -version: 2.0.0 -description: Creates or edits a Power Apps Canvas App through the Canvas Authoring MCP coauthoring session. Handles new app generation from requirements, simple inline edits, and complex multi-screen changes with parallel screen builders. Triggers on requests to create, build, generate, modify, update, change, or edit a Canvas App or .pa.yaml files. -author: Microsoft Corporation -user-invocable: true -allowed-tools: Read, Write, Edit, Bash, AskUserQuestion, Task, TaskCreate, TaskUpdate, TaskList, EnterPlanMode, ExitPlanMode, mcp__canvas-authoring__sync_canvas, mcp__canvas-authoring__compile_canvas ---- - -# Create or Edit a Canvas App - -Create or edit a Power Apps canvas app for the following requirements: - -$ARGUMENTS - -## Overview - -This skill handles both **creating** and **editing** canvas apps through a unified workflow. -It syncs the current app state to detect whether the app has existing content, then routes -accordingly: - -- **CREATE mode** — the app is empty or has no meaningful content; a new app is generated - from scratch using a preferences wizard and parallel screen builders. -- **EDIT mode (simple)** — the app has existing content and the requested changes are small; - edits are applied inline without planning agents. -- **EDIT mode (complex)** — the app has existing content and the requested changes are - substantial; a planner designs the changes and parallel screen builders execute them. - -Two specialist agents are used for planned work: - -1. **`canvas-app-planner`** — discovers available controls, APIs, and data sources; gathers - control property definitions; and writes the shared plan document (`canvas-app-plan.md`) - and `App.pa.yaml`. Receives the approved plan from the skill. -2. **`canvas-screen-builder`** — writes or modifies exactly one screen's YAML; multiple - builders run in parallel after the plan is approved - -You (the skill) coordinate the agents, detect mode, design and present the plan for user -approval, and own the compilation + error-fixing loop after all screens are written. - ---- - -## Phase 0 — Create App Folder - -Before syncing or editing, create a subfolder to contain the app's YAML files: - -1. Extract the app name or a 2–4 word summary from `$ARGUMENTS` -2. Convert to kebab-case (e.g., "Expense Tracker" → `expense-tracker`, "my travel planner" → - `my-travel-planner`) -3. Create the folder using `Bash`: `mkdir -p ` -4. Resolve its absolute path — this is the **working directory** for all subsequent phases - -Pass this absolute path as the working directory in every agent prompt below. - ---- - -## Phase 1 — Sync - -Call the `sync_canvas` MCP tool targeting the working directory. This pulls the current app -state from the coauthoring session into local `.pa.yaml` files. Only proceed after -`sync_canvas` completes successfully. - ---- - -## Phase 2 — Detect Mode - -After `sync_canvas` completes, read the synced `.pa.yaml` files and check whether the app -has meaningful content. An app is considered **empty** if: - -- No `.pa.yaml` files were written, or -- The only files present contain no screens, or -- Every screen present has no controls (only bare screen-level YAML with no children), or -- Every screen's controls consist solely of containers (e.g., `GroupContainer`) with no - leaf controls inside them - -**If the app is empty → CREATE mode.** Proceed to Phase 3. - -**If the app has meaningful content → EDIT mode.** Skip Phase 3 and proceed to Phase 4. - ---- - -## Phase 3 — Gather Preferences (CREATE mode only) - -Use `AskUserQuestion` to collect design preferences that cannot be reliably inferred from -`$ARGUMENTS`. **Parse `$ARGUMENTS` first** to determine which questions to skip — but a -short request like "visitor check-in app" or "expense tracker" leaves most preferences -unspecified and you MUST ask. - -Call `AskUserQuestion` with the applicable questions from the table below (include only the -ones that need answers): - -| Question | Header | When to Ask | Options | -|----------|--------|-------------|---------| -| Who will primarily use this app, and on what device? | Target Users & Device | Only if not clear from `$ARGUMENTS` | *(3–4 dynamically inferred options that combine the user role with their likely device, e.g., for "visitor check-in": Front desk staff on desktop/tablet, Security team on tablet, Self-service kiosk on tablet, Visitors on their phone)* | -| Do you have a screenshot or mockup for reference? (paste an image or provide a file path) | Reference | Only if user has NOT already attached/pasted an image with their request | Yes I'll share one now, No just pick a direction for me | -| What aesthetic direction? | Aesthetic | Only if not clear from `$ARGUMENTS` (skip if user already described a visual direction like "dark themed", "minimal", "corporate style", or provided a reference image) | Clean & Professional (Recommended), Bold & High-Contrast, Soft & Approachable, Dense & Utilitarian | -| Which features do you need? (multi-select) | Features | Only if `$ARGUMENTS` is vague on features | *(3–4 dynamically inferred options based on app purpose + target users)* | - -**Rules:** - -1. If the user provides a screenshot (either attached with their original request or via the - wizard), examine it to extract structural cues (layout, navigation pattern) and visual cues - (color palette, density, typography). Use these to inform the aesthetic direction — do not - ask the aesthetic question separately. -2. **If all questions are already answered by `$ARGUMENTS` and any attached images, skip the - wizard entirely** and proceed directly to Phase 5. -3. Ask all applicable questions in a single `AskUserQuestion` call — do not ask them one at a time. -4. Store all answers for use in the planner prompt below. - -**Target users & device influence design decisions:** -- **Desktop users** → data-dense layouts, tables, keyboard-friendly, multi-column. ManualLayout acceptable for pixel-perfect dashboards. -- **Tablet users** → touch-friendly targets, medium density, AutoLayout (responsive) so the app adapts to landscape/portrait. -- **Phone users** → large touch targets, single-column, simplified navigation, AutoLayout (responsive), minimal typing. -- **Multi-device / unknown** → AutoLayout (responsive) required. - -After collecting preferences, proceed to Phase 5 (Plan). - ---- - -## Phase 4 — Assess Complexity (EDIT mode only) - -Read all synced `.pa.yaml` files. Based on `$ARGUMENTS` and the current app state, determine -whether this is a **simple** or **complex** edit: - -**Simple** — all of the following are true: -- Changes affect ≤ 2 controls or properties -- Changes are confined to ≤ 1 screen -- No new screens are being added -- No new data sources or connectors are needed -- No structural layout changes (e.g., not changing ManualLayout to AutoLayout) - -Examples: change a button color, update label text, fix a formula, adjust a control size. - -**Complex** — any of the following are true: -- Changes span multiple screens -- One or more new screens need to be created -- New data sources or connectors are required -- Structural layout changes are involved -- Significant visual redesign of a screen - -Examples: add a settings screen, redesign the home screen layout, integrate a new connector, -change the navigation flow across the app. - -- If **simple**: proceed to Phase 4a. -- If **complex**: proceed to Phase 5 (Plan). - -### Phase 4a — Simple: Direct Edit - -Read `${CLAUDE_PLUGIN_ROOT}/references/TechnicalGuide.md` before making changes. - -Apply the changes directly: - -1. **Edit** the relevant `.pa.yaml` files with the required changes, following conventions - from TechnicalGuide.md. - -2. **Validate** by calling `compile_canvas` on the working directory after making changes. - On failure, read the errors, fix with `Edit`, and re-compile. Iterate until clean. - -3. Present a brief summary: - > **Edit complete.** [1-2 sentence description of what was changed.] Compiled clean after [N] pass(es). - -**Stop here.** The simple edit path is complete — do not continue to Phase 5 or beyond. - ---- - -## Phase 5 — Plan - -You (the skill) own plan design and user approval. After approval, invoke the -`canvas-app-planner` agent to discover resources, gather control definitions, and write the -plan document. - -### Step 5.1 — Read Reference Documents - -Read both reference documents before designing the plan: - -- `${CLAUDE_PLUGIN_ROOT}/references/TechnicalGuide.md` -- `${CLAUDE_PLUGIN_ROOT}/references/DesignGuide.md` - -Internalize both. These govern every design decision you will make. - -### Step 5.2 — Design the Plan - -#### CREATE mode - -Based on the user preferences from Phase 3 and the user's requirements, reason through: - -- How many screens are needed and what each does -- Which controls will drive each screen's layout -- What aesthetic direction fits the app's purpose -- How data will flow (data sources, collections, or mock data) -- **Layout strategy** — follow the layout decision rules in TechnicalGuide.md - -#### EDIT mode - -Read all `.pa.yaml` files in the working directory (you may have already read them in -Phase 4). Based on the current app state and the user's edit requirements, reason through: - -- Which screens need to be modified and what specific changes are needed -- Whether any new screens need to be created -- How changes can be made while preserving the existing app's aesthetic and layout consistency -- Any new controls, data sources, or variables required - -### Step 5.3 — Present Plan for Approval - -Enter plan mode (`EnterPlanMode`) and present the plan. - -#### CREATE mode - -``` -## Canvas App Plan - -### Screens ([N] total) - -| Screen | File | Purpose | Key Controls | -|--------|------|---------|--------------| -| [Name] | [Name].pa.yaml | [one-line description] | [2-3 controls] | - -### Data Strategy -[How data will be loaded — data sources used, or "collections/mock data"] - -### Aesthetic Direction -[e.g., "Bold & editorial — high-contrast dark background, accent RGBA(255,90,60,1), card-based layout, strong typographic hierarchy"] -``` - -#### EDIT mode - -``` -## Canvas Edit Plan - -### Screens to Modify ([N] total) - -| Screen | File | Summary of Changes | -|--------|------|--------------------| -| [Name] | [Name].pa.yaml | [one-line description of changes] | - -### Screens to Add ([N] total, if any) - -| Screen | File | Purpose | -|--------|------|---------| -| [Name] | [Name].pa.yaml | [one-line description] | - -### Approach -[e.g., "Preserving existing dark theme — updating button palette on Home screen and adding a -new Settings screen with consistent RGBA values extracted from existing files"] -``` - -#### Both modes - -Then call `ExitPlanMode` to request user approval. - -- If approved: proceed to Step 5.4. -- If changes requested: revise the plan and re-enter plan mode with the updated version. - -### Step 5.4 — Invoke Planner Agent - -After approval, invoke the `canvas-app-planner` agent using the `Task` tool. The agent -will discover available resources, gather control property definitions, write `App.pa.yaml` -(CREATE only), and write `canvas-app-plan.md`. - -Pass a prompt that includes the **approved plan**. The agent does NOT redesign the plan or -interact with the user — it discovers resources, enriches the plan with control definitions, -and writes the output files. - -#### CREATE mode - -Example prompt: - -> You are the canvas-app-planner agent. Write the plan document for a Canvas App. -> -> Mode: CREATE -> -> Requirements: [paste $ARGUMENTS here] -> -> Approved plan: -> [paste the full plan you presented in Step 5.3 — screens, data strategy, aesthetic -> direction, all RGBA values] -> -> User preferences (from wizard): -> - Target users & device: [answer] -> - Aesthetic direction: [answer] -> - Features: [answer] -> - Reference image: [observations, or "none provided"] -> -> Working directory: [absolute path from Phase 0] -> Plugin root: ${CLAUDE_PLUGIN_ROOT} -> -> Follow the instructions in your agent file. Discover resources, gather control -> definitions, write App.pa.yaml and canvas-app-plan.md to the working directory. Return -> the screen list and plan document path when complete. - -#### EDIT mode (complex) - -Example prompt: - -> You are the canvas-app-planner agent. Write the plan document for edits to a Canvas App. -> -> Mode: EDIT -> -> Edit requirements: [paste $ARGUMENTS here] -> -> Approved plan: -> [paste the full plan you presented in Step 5.3 — screens to modify/add, approach, -> all RGBA values] -> -> Current app state: -> - Palette: [exact RGBA values extracted from existing files] -> - Variables: [variable names found in existing files] -> - Layout strategy: [AutoLayout / ManualLayout as found in existing files] -> - Screens: [list of existing screens and their key controls] -> -> Working directory: [absolute working directory path] -> Plugin root: ${CLAUDE_PLUGIN_ROOT} -> Synced files: [list of .pa.yaml filenames] -> -> Follow the instructions in your agent file. Discover resources for new controls, gather -> control definitions, write canvas-app-plan.md to the working directory. Return the list -> of screens and the plan document path when complete. - -**Wait for the planner to finish.** Do not proceed to Phase 6 until the planner task -completes successfully. - ---- - -## Phase 6 — Build / Edit - -After the planner completes, read `canvas-app-plan.md` from the working directory. - -Extract the screen list from the `## Screens` table — collect each screen name, its target -file name, and its action (Create or Modify). - -Invoke one `canvas-screen-builder` agent per screen. **Fire all invocations in a single -message** (parallel execution) — do not wait for one screen to finish before starting the -next. - -For each screen, pass a prompt that includes: - -- Screen name (e.g., "Home") -- Target file name (e.g., "Home.pa.yaml") -- Action: "Create" (new screen) or "Modify" (existing screen being edited) -- Absolute path to `canvas-app-plan.md` -- Working directory - -Example prompt per screen: - -> You are the canvas-screen-builder agent. [Create / Modify] the **[Screen Name]** screen. -> -> - Action: [Create / Modify] -> - Target file: [ScreenName].pa.yaml -> - Plan document: [absolute path to canvas-app-plan.md] -> - Working directory: [absolute path from Phase 0] -> -> Follow the instructions in your agent file. [Write / Edit] [ScreenName].pa.yaml and return -> your result when done. Do not call compile_canvas — validation is handled by the skill. - -Wait for all screen-builder tasks to complete before proceeding. - ---- - -## Phase 7 — Validate and Fix - -After all screen-builders have finished writing their files, call `compile_canvas` on the -working directory. - -**On success:** Proceed to Phase 8. - -**On failure:** Read every error in the output. Errors will reference specific files and -line numbers. For each error: - -1. `Read` the referenced `.pa.yaml` file -2. Fix the error using `Edit` -3. After fixing all errors from this pass, call `compile_canvas` again - -Repeat until `compile_canvas` reports no errors. Do not give up after a single fix attempt — -iterate until the entire directory compiles clean. - -Track how many `compile_canvas` passes were needed. - ---- - -## Phase 8 — Summary - -Delete `canvas-app-plan.md` from the working directory using `Bash`: -`rm /canvas-app-plan.md` - -Present a final summary based on the mode: - -**CREATE mode:** - -> **App generation complete.** -> -> | Screen | File | Status | -> |--------|------|--------| -> | [Screen Name] | [filename].pa.yaml | Created | -> -> **Compiled clean** after [N] pass(es). | **Screens:** [N] | **Data:** [source or collections] - -**EDIT mode (complex):** - -> **Edit complete.** -> -> | Action | Screen | File | Status | -> |--------|--------|------|--------| -> | [Create / Modify] | [Screen Name] | [filename].pa.yaml | Done | -> -> **Compiled clean** after [N] pass(es). - -If any errors remain after exhausting fixes, report them explicitly so the user knows what -needs manual attention. +--- +name: canvas-app +version: 2.0.0 +description: Creates or edits a Power Apps Canvas App through the Canvas Authoring MCP coauthoring session. Handles new app generation from requirements, simple inline edits, and complex multi-screen changes with parallel screen builders. Triggers on requests to create, build, generate, modify, update, change, or edit a Canvas App or .pa.yaml files. +author: Microsoft Corporation +user-invocable: true +allowed-tools: Read, Write, Edit, Bash, AskUserQuestion, Task, TaskCreate, TaskUpdate, TaskList, EnterPlanMode, ExitPlanMode, mcp__canvas-authoring__sync_canvas, mcp__canvas-authoring__compile_canvas +--- + +# Create or Edit a Canvas App + +Create or edit a Power Apps canvas app for the following requirements: + +$ARGUMENTS + +## Overview + +This skill handles both **creating** and **editing** canvas apps through a unified workflow. +It syncs the current app state to detect whether the app has existing content, then routes +accordingly: + +- **CREATE mode** — the app is empty or has no meaningful content; a new app is generated + from scratch using a preferences wizard and parallel screen builders. +- **EDIT mode (simple)** — the app has existing content and the requested changes are small; + edits are applied inline without planning agents. +- **EDIT mode (complex)** — the app has existing content and the requested changes are + substantial; a planner designs the changes and parallel screen builders execute them. + +Two specialist agents are used for planned work: + +1. **`canvas-app-planner`** — discovers available controls, APIs, and data sources; gathers + control property definitions; and writes the shared plan document (`canvas-app-plan.md`) + and `App.pa.yaml`. Receives the approved plan from the skill. +2. **`canvas-screen-builder`** — writes or modifies exactly one screen's YAML; multiple + builders run in parallel after the plan is approved + +You (the skill) coordinate the agents, detect mode, design and present the plan for user +approval, and own the compilation + error-fixing loop after all screens are written. + +--- + +## Phase 0 — Create App Folder + +Before syncing or editing, create a subfolder to contain the app's YAML files: + +1. Extract the app name or a 2–4 word summary from `$ARGUMENTS` +2. Convert to kebab-case (e.g., "Expense Tracker" → `expense-tracker`, "my travel planner" → + `my-travel-planner`) +3. Create the folder using `Bash`: `mkdir -p ` +4. Resolve its absolute path — this is the **working directory** for all subsequent phases + +Pass this absolute path as the working directory in every agent prompt below. + +--- + +## Phase 1 — Sync + +Call the `sync_canvas` MCP tool targeting the working directory. This pulls the current app +state from the coauthoring session into local `.pa.yaml` files. Only proceed after +`sync_canvas` completes successfully. + +--- + +## Phase 2 — Detect Mode + +After `sync_canvas` completes, read the synced `.pa.yaml` files and check whether the app +has meaningful content. An app is considered **empty** if: + +- No `.pa.yaml` files were written, or +- The only files present contain no screens, or +- Every screen present has no controls (only bare screen-level YAML with no children), or +- Every screen's controls consist solely of containers (e.g., `GroupContainer`) with no + leaf controls inside them + +**If the app is empty → CREATE mode.** Proceed to Phase 3. + +**If the app has meaningful content → EDIT mode.** Skip Phase 3 and proceed to Phase 4. + +--- + +## Phase 3 — Gather Preferences (CREATE mode only) + +Use `AskUserQuestion` to collect design preferences that cannot be reliably inferred from +`$ARGUMENTS`. **Parse `$ARGUMENTS` first** to determine which questions to skip — but a +short request like "visitor check-in app" or "expense tracker" leaves most preferences +unspecified and you MUST ask. + +Call `AskUserQuestion` with the applicable questions from the table below (include only the +ones that need answers): + +| Question | Header | When to Ask | Options | +|----------|--------|-------------|---------| +| Who will primarily use this app, and on what device? | Target Users & Device | Only if not clear from `$ARGUMENTS` | *(3–4 dynamically inferred options that combine the user role with their likely device, e.g., for "visitor check-in": Front desk staff on desktop/tablet, Security team on tablet, Self-service kiosk on tablet, Visitors on their phone)* | +| Do you have a screenshot or mockup for reference? (paste an image or provide a file path) | Reference | Only if user has NOT already attached/pasted an image with their request | Yes I'll share one now, No just pick a direction for me | +| What aesthetic direction? | Aesthetic | Only if not clear from `$ARGUMENTS` (skip if user already described a visual direction like "dark themed", "minimal", "corporate style", or provided a reference image) | Clean & Professional (Recommended), Bold & High-Contrast, Soft & Approachable, Dense & Utilitarian | +| Which features do you need? (multi-select) | Features | Only if `$ARGUMENTS` is vague on features | *(3–4 dynamically inferred options based on app purpose + target users)* | + +**Rules:** + +1. If the user provides a screenshot (either attached with their original request or via the + wizard), examine it to extract structural cues (layout, navigation pattern) and visual cues + (color palette, density, typography). Use these to inform the aesthetic direction — do not + ask the aesthetic question separately. +2. **If all questions are already answered by `$ARGUMENTS` and any attached images, skip the + wizard entirely** and proceed directly to Phase 5. +3. Ask all applicable questions in a single `AskUserQuestion` call — do not ask them one at a time. +4. Store all answers for use in the planner prompt below. + +**Target users & device influence design decisions:** +- **Desktop users** → data-dense layouts, tables, keyboard-friendly, multi-column. ManualLayout acceptable for pixel-perfect dashboards. +- **Tablet users** → touch-friendly targets, medium density, AutoLayout (responsive) so the app adapts to landscape/portrait. +- **Phone users** → large touch targets, single-column, simplified navigation, AutoLayout (responsive), minimal typing. +- **Multi-device / unknown** → AutoLayout (responsive) required. + +After collecting preferences, proceed to Phase 5 (Plan). + +--- + +## Phase 4 — Assess Complexity (EDIT mode only) + +Read all synced `.pa.yaml` files. Based on `$ARGUMENTS` and the current app state, determine +whether this is a **simple** or **complex** edit: + +**Simple** — all of the following are true: +- Changes affect ≤ 2 controls or properties +- Changes are confined to ≤ 1 screen +- No new screens are being added +- No new data sources or connectors are needed +- No structural layout changes (e.g., not changing ManualLayout to AutoLayout) + +Examples: change a button color, update label text, fix a formula, adjust a control size. + +**Complex** — any of the following are true: +- Changes span multiple screens +- One or more new screens need to be created +- New data sources or connectors are required +- Structural layout changes are involved +- Significant visual redesign of a screen + +Examples: add a settings screen, redesign the home screen layout, integrate a new connector, +change the navigation flow across the app. + +- If **simple**: proceed to Phase 4a. +- If **complex**: proceed to Phase 5 (Plan). + +### Phase 4a — Simple: Direct Edit + +Read `${PLUGIN_ROOT}/references/TechnicalGuide.md` before making changes. + +Apply the changes directly: + +1. **Edit** the relevant `.pa.yaml` files with the required changes, following conventions + from TechnicalGuide.md. + +2. **Validate** by calling `compile_canvas` on the working directory after making changes. + On failure, read the errors, fix with `Edit`, and re-compile. Iterate until clean. + +3. Present a brief summary: + > **Edit complete.** [1-2 sentence description of what was changed.] Compiled clean after [N] pass(es). + +**Stop here.** The simple edit path is complete — do not continue to Phase 5 or beyond. + +--- + +## Phase 5 — Plan + +You (the skill) own plan design and user approval. After approval, invoke the +`canvas-app-planner` agent to discover resources, gather control definitions, and write the +plan document. + +### Step 5.1 — Read Reference Documents + +Read both reference documents before designing the plan: + +- `${PLUGIN_ROOT}/references/TechnicalGuide.md` +- `${PLUGIN_ROOT}/references/DesignGuide.md` + +Internalize both. These govern every design decision you will make. + +### Step 5.2 — Design the Plan + +#### CREATE mode + +Based on the user preferences from Phase 3 and the user's requirements, reason through: + +- How many screens are needed and what each does +- Which controls will drive each screen's layout +- What aesthetic direction fits the app's purpose +- How data will flow (data sources, collections, or mock data) +- **Layout strategy** — follow the layout decision rules in TechnicalGuide.md + +#### EDIT mode + +Read all `.pa.yaml` files in the working directory (you may have already read them in +Phase 4). Based on the current app state and the user's edit requirements, reason through: + +- Which screens need to be modified and what specific changes are needed +- Whether any new screens need to be created +- How changes can be made while preserving the existing app's aesthetic and layout consistency +- Any new controls, data sources, or variables required + +### Step 5.3 — Present Plan for Approval + +Enter plan mode (`EnterPlanMode`) and present the plan. + +#### CREATE mode + +``` +## Canvas App Plan + +### Screens ([N] total) + +| Screen | File | Purpose | Key Controls | +|--------|------|---------|--------------| +| [Name] | [Name].pa.yaml | [one-line description] | [2-3 controls] | + +### Data Strategy +[How data will be loaded — data sources used, or "collections/mock data"] + +### Aesthetic Direction +[e.g., "Bold & editorial — high-contrast dark background, accent RGBA(255,90,60,1), card-based layout, strong typographic hierarchy"] +``` + +#### EDIT mode + +``` +## Canvas Edit Plan + +### Screens to Modify ([N] total) + +| Screen | File | Summary of Changes | +|--------|------|--------------------| +| [Name] | [Name].pa.yaml | [one-line description of changes] | + +### Screens to Add ([N] total, if any) + +| Screen | File | Purpose | +|--------|------|---------| +| [Name] | [Name].pa.yaml | [one-line description] | + +### Approach +[e.g., "Preserving existing dark theme — updating button palette on Home screen and adding a +new Settings screen with consistent RGBA values extracted from existing files"] +``` + +#### Both modes + +Then call `ExitPlanMode` to request user approval. + +- If approved: proceed to Step 5.4. +- If changes requested: revise the plan and re-enter plan mode with the updated version. + +### Step 5.4 — Invoke Planner Agent + +After approval, invoke the `canvas-app-planner` agent using the `Task` tool. The agent +will discover available resources, gather control property definitions, write `App.pa.yaml` +(CREATE only), and write `canvas-app-plan.md`. + +Pass a prompt that includes the **approved plan**. The agent does NOT redesign the plan or +interact with the user — it discovers resources, enriches the plan with control definitions, +and writes the output files. + +#### CREATE mode + +Example prompt: + +> You are the canvas-app-planner agent. Write the plan document for a Canvas App. +> +> Mode: CREATE +> +> Requirements: [paste $ARGUMENTS here] +> +> Approved plan: +> [paste the full plan you presented in Step 5.3 — screens, data strategy, aesthetic +> direction, all RGBA values] +> +> User preferences (from wizard): +> - Target users & device: [answer] +> - Aesthetic direction: [answer] +> - Features: [answer] +> - Reference image: [observations, or "none provided"] +> +> Working directory: [absolute path from Phase 0] +> Plugin root: ${PLUGIN_ROOT} +> +> Follow the instructions in your agent file. Discover resources, gather control +> definitions, write App.pa.yaml and canvas-app-plan.md to the working directory. Return +> the screen list and plan document path when complete. + +#### EDIT mode (complex) + +Example prompt: + +> You are the canvas-app-planner agent. Write the plan document for edits to a Canvas App. +> +> Mode: EDIT +> +> Edit requirements: [paste $ARGUMENTS here] +> +> Approved plan: +> [paste the full plan you presented in Step 5.3 — screens to modify/add, approach, +> all RGBA values] +> +> Current app state: +> - Palette: [exact RGBA values extracted from existing files] +> - Variables: [variable names found in existing files] +> - Layout strategy: [AutoLayout / ManualLayout as found in existing files] +> - Screens: [list of existing screens and their key controls] +> +> Working directory: [absolute working directory path] +> Plugin root: ${PLUGIN_ROOT} +> Synced files: [list of .pa.yaml filenames] +> +> Follow the instructions in your agent file. Discover resources for new controls, gather +> control definitions, write canvas-app-plan.md to the working directory. Return the list +> of screens and the plan document path when complete. + +**Wait for the planner to finish.** Do not proceed to Phase 6 until the planner task +completes successfully. + +--- + +## Phase 6 — Build / Edit + +After the planner completes, read `canvas-app-plan.md` from the working directory. + +Extract the screen list from the `## Screens` table — collect each screen name, its target +file name, and its action (Create or Modify). + +Invoke one `canvas-screen-builder` agent per screen. **Fire all invocations in a single +message** (parallel execution) — do not wait for one screen to finish before starting the +next. + +For each screen, pass a prompt that includes: + +- Screen name (e.g., "Home") +- Target file name (e.g., "Home.pa.yaml") +- Action: "Create" (new screen) or "Modify" (existing screen being edited) +- Absolute path to `canvas-app-plan.md` +- Working directory + +Example prompt per screen: + +> You are the canvas-screen-builder agent. [Create / Modify] the **[Screen Name]** screen. +> +> - Action: [Create / Modify] +> - Target file: [ScreenName].pa.yaml +> - Plan document: [absolute path to canvas-app-plan.md] +> - Working directory: [absolute path from Phase 0] +> +> Follow the instructions in your agent file. [Write / Edit] [ScreenName].pa.yaml and return +> your result when done. Do not call compile_canvas — validation is handled by the skill. + +Wait for all screen-builder tasks to complete before proceeding. + +--- + +## Phase 7 — Validate and Fix + +After all screen-builders have finished writing their files, call `compile_canvas` on the +working directory. + +**On success:** Proceed to Phase 8. + +**On failure:** Read every error in the output. Errors will reference specific files and +line numbers. For each error: + +1. `Read` the referenced `.pa.yaml` file +2. Fix the error using `Edit` +3. After fixing all errors from this pass, call `compile_canvas` again + +Repeat until `compile_canvas` reports no errors. Do not give up after a single fix attempt — +iterate until the entire directory compiles clean. + +Track how many `compile_canvas` passes were needed. + +--- + +## Phase 8 — Summary + +Delete `canvas-app-plan.md` from the working directory using `Bash`: +`rm /canvas-app-plan.md` + +Present a final summary based on the mode: + +**CREATE mode:** + +> **App generation complete.** +> +> | Screen | File | Status | +> |--------|------|--------| +> | [Screen Name] | [filename].pa.yaml | Created | +> +> **Compiled clean** after [N] pass(es). | **Screens:** [N] | **Data:** [source or collections] + +**EDIT mode (complex):** + +> **Edit complete.** +> +> | Action | Screen | File | Status | +> |--------|--------|------|--------| +> | [Create / Modify] | [Screen Name] | [filename].pa.yaml | Done | +> +> **Compiled clean** after [N] pass(es). + +If any errors remain after exhausting fixes, report them explicitly so the user knows what +needs manual attention. diff --git a/plugins/canvas-apps/skills/report-issue/SKILL.md b/plugins/canvas-apps/skills/report-issue/SKILL.md index 3f2a12658..bfa7db3af 100644 --- a/plugins/canvas-apps/skills/report-issue/SKILL.md +++ b/plugins/canvas-apps/skills/report-issue/SKILL.md @@ -10,4 +10,4 @@ allowed-tools: Read, Bash, Glob, Grep, AskUserQuestion, TaskCreate, TaskUpdate, model: sonnet --- -**Shared workflow: [report-issue-workflow.md](${CLAUDE_PLUGIN_ROOT}/../../shared/skills/report-issue/report-issue-workflow.md)** — Read and follow all phases defined in that file. +**Workflow: [report-issue-workflow.md](${PLUGIN_ROOT}/skills/report-issue/report-issue-workflow.md)** — Read and follow all phases defined in that bundled file. diff --git a/plugins/canvas-apps/skills/report-issue/report-issue-workflow.md b/plugins/canvas-apps/skills/report-issue/report-issue-workflow.md new file mode 100644 index 000000000..709963825 --- /dev/null +++ b/plugins/canvas-apps/skills/report-issue/report-issue-workflow.md @@ -0,0 +1,230 @@ +# Report Issue + +File a bug report on the [power-platform-skills](https://github.com/equinor/power-platform-skills) GitHub repository. This workflow gathers the required information, presents a full preview, and creates the issue on the user's behalf. + +**Initial request:** $ARGUMENTS + +> **WARNING:** Before proceeding, inform the user: "This will create an issue on the **public** GitHub repository [equinor/power-platform-skills](https://github.com/equinor/power-platform-skills). **Do not include any sensitive information** such as credentials, internal URLs, tenant IDs, customer data, or proprietary code in the bug report." + +--- + +## Phase 1: Identify Plugin & Version + +**Goal**: Determine which plugin the bug is for and its version. + +**Actions**: + +1. Create a task list with all 5 phases (see [Progress Tracking](#progress-tracking) table). +2. **Auto-detect the plugin name** from the current plugin context by reading `${PLUGIN_ROOT}/.plugin/plugin.json`. Extract the `name` field. +3. **Auto-detect the plugin version** from the same `plugin.json` — extract the `version` field. +4. If the plugin name or version cannot be determined, use `AskUserQuestion` to ask the user. + +**Output**: Plugin name and version confirmed. + +--- + +## Phase 2: Gather Bug Details + +**Goal**: Collect all required and optional fields for the bug report. + +**Actions**: + +### 2.1 Check Arguments + +If `$ARGUMENTS` contains a description of the bug, extract as much information as possible from it to pre-fill the fields below. Still confirm with the user. + +### 2.2 Ask for Bug Details + +Use `AskUserQuestion` to gather bug details. `AskUserQuestion` supports 1-4 questions per call, but each question requires 2-4 predefined options. Since bug report fields are free-text, use options as prompt hints and let the user type their actual response via "Other". Skip any field the user already provided in `$ARGUMENTS`. + +**Call 1** — Skill and bug description (2 questions): + +1. **"Which skill or command were you using?"** (header: "Skill") — Provide the plugin's known skills as options (e.g., `deploy-site`, `create-site`). This is optional — the user can skip it. +2. **"Please describe the bug clearly and concisely."** (header: "Bug") — Provide short prompt-hint options like "Skill failed with error", "Unexpected behavior", "Skill hangs or times out". The user will typically select "Other" to type their description. + +**Call 2** — Repro steps and expected vs actual (3 questions): + +1. **"What steps reproduce the bug?"** (header: "Repro steps") — Provide prompt-hint options like "Ran the skill with default settings", "Ran the skill with custom arguments". The user will typically select "Other" to type their steps. +2. **"What did you expect to happen?"** (header: "Expected") — Provide prompt-hint options like "Skill should have succeeded", "Different output expected". The user will typically select "Other" to type their answer. +3. **"What actually happened?"** (header: "Actual") — Provide prompt-hint options like "Got an error message", "Wrong output produced", "Skill hung or timed out". The user will typically select "Other" to type their answer. + +**Call 3** — Environment details: + +Use `AskUserQuestion` with a single question: **"We recommend including environment details (OS, Claude Code version, PAC CLI version) to help maintainers reproduce and debug the issue faster. Include them?"** (header: "Environment") — Options: "Yes, auto-collect them" (recommended), "No, skip". + +- If **yes**: Auto-collect the following by running shell commands: + - **OS**: Detect the user's OS name and version using an appropriate command for their platform. + - **Claude Code version**: Run `claude --version` + - **PAC CLI version**: Run `pac help` and extract the version from the output + - Present the collected details to the user. +- If **no**: Skip environment fields. + +**Call 4** — Logs: + +Use `AskUserQuestion` with a single question: **"Do you want to include any relevant logs or error messages?"** (header: "Logs") — Options: "Yes, I'll paste them", "No, skip". + +- If **yes**: Use `AskUserQuestion` to ask: **"Please paste any relevant logs or error messages."** (header: "Paste logs") — Provide prompt-hint options like "Error message from terminal", "Skill output log". The user will typically select "Other" to paste their content. +- If **no**: Skip this field. + +**Output**: All bug report fields gathered. + +--- + +## Phase 3: Preview Issue + +**Goal**: Present the complete issue to the user for review before creating it. + +**Actions**: + +Remind the user: **"This issue will be created on the public GitHub repository. Please review and make sure it does not contain any sensitive information (credentials, internal URLs, tenant IDs, customer data, or proprietary code)."** + +Format and display the full issue exactly as it will be filed: + +``` +===== ISSUE PREVIEW ===== + +Title: [Bug] + +Plugin: +Plugin Version: +Skill / Command: + +Bug Description: + + +Steps to Reproduce: + + +Expected Behavior: + + +Actual Behavior: + + +Relevant Logs / Screenshots: + + +Environment: + + +Labels: bug, + +--- +🤖 This issue was created using the /report-issue skill. + +===== END PREVIEW ===== +``` + +Use `AskUserQuestion` to confirm: + +> "Here's the issue that will be created. Would you like to proceed, or would you like to make any changes?" + +- If the user wants changes, go back and update the relevant fields, then preview again. +- If the user approves, proceed to Phase 4. + +**Output**: User-approved issue ready for creation. + +--- + +## Phase 4: Create Issue + +**Goal**: Create the GitHub issue using the `gh` CLI. + +**Actions**: + +1. Build the issue body from the gathered fields, matching the bug report template structure: + + ``` + ### Plugin + + + + ### Plugin Version + + + + ### Skill / Command + + + + ### Bug Description + + + + ### Steps to Reproduce + + + + ### Expected Behavior + + + + ### Actual Behavior + + + + ### Relevant Logs / Screenshots + + + + ### Environment + + + + --- + 🤖 *This issue was created using the `/report-issue` skill.* + ``` + +2. Create the issue via `gh`: + + ```bash + gh issue create --repo equinor/power-platform-skills --title "[Bug] " --label "bug" --label "" --body "" + ``` + + Use a HEREDOC for the body to preserve formatting. + +3. Capture and display the issue URL to the user. + +**Output**: Issue created, URL shared with user. + +--- + +## Phase 5: Summarize + +**Goal**: Confirm success and suggest next steps. + +**Actions**: + +1. Display the created issue URL. +2. Let the user know they can track the issue on GitHub. + +**Output**: Issue URL and next steps presented. + +--- + +## Important Notes + +### Throughout All Phases + +- **Use TaskCreate/TaskUpdate** to track progress at every phase. +- **Always use AskUserQuestion** before collecting any environment information — never run commands to gather system details without explicit user consent. +- **Always preview the full issue** before creating it — the user must approve the content. +- **Use the `gh` CLI** to create the issue — confirm it is authenticated before attempting. + +### Progress Tracking + +Before starting Phase 1, create a task list with all phases using `TaskCreate`: + +| Task subject | activeForm | Description | +|-------------|------------|-------------| +| Identify plugin and version | Identifying plugin | Auto-detect plugin name and version from plugin.json | +| Gather bug details | Gathering details | Collect bug description, repro steps, expected/actual behavior | +| Preview issue | Previewing issue | Show complete issue for user review and approval | +| Create issue | Creating issue | File the bug report on GitHub via gh CLI | +| Summarize | Summarizing | Confirm success and share issue URL | + +Mark each task `in_progress` when starting it and `completed` when done via `TaskUpdate`. + +--- + +**Begin with Phase 1: Identify Plugin & Version** diff --git a/plugins/code-apps/.plugin/plugin.json b/plugins/code-apps/.plugin/plugin.json new file mode 100644 index 000000000..acb6047a6 --- /dev/null +++ b/plugins/code-apps/.plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "code-apps-preview", + "version": "1.0.0", + "description": "Build and deploy Power Apps code apps using React, Vite, and Power Platform connectors.", + "author": { + "name": "Microsoft", + "url": "https://www.microsoft.com" + }, + "homepage": "https://github.com/equinor/power-platform-skills/", + "repository": "https://github.com/equinor/power-platform-skills/", + "license": "MIT", + "keywords": [ + "power-apps", + "code-apps", + "react", + "vite", + "dataverse", + "connectors", + "pac-cli", + "power-platform" + ] +} diff --git a/plugins/code-apps/AGENTS.md b/plugins/code-apps/AGENTS.md index 85a0a93a0..977e3dc56 100644 --- a/plugins/code-apps/AGENTS.md +++ b/plugins/code-apps/AGENTS.md @@ -17,7 +17,7 @@ claude --plugin-dir /path/to/plugins/power-apps ## Architecture ``` -.claude-plugin/plugin.json <- Plugin metadata (name, version, keywords) +.plugin/plugin.json <- Open Plugins metadata (name, version, keywords) AGENTS.md <- Plugin guidance for AI agents (this file) agents/ code-app-architect.md <- Agent persona for architecture decisions diff --git a/plugins/code-apps/README.md b/plugins/code-apps/README.md index 38222f1c2..f14f82e57 100644 --- a/plugins/code-apps/README.md +++ b/plugins/code-apps/README.md @@ -11,7 +11,7 @@ Copilot plugin for building Power Apps code apps with React and Vite. Works with ## Install -The plugin marketplace is hosted in the `plugin` folder of the [microsoft/PowerAppsCodeApps](https://github.com/microsoft/PowerAppsCodeApps) repository. +The plugin marketplace for this fork is hosted in this repository. Open Claude Code or GitHub Copilot in any folder and run the following commands: @@ -22,7 +22,7 @@ Open Claude Code or GitHub Copilot in any folder and run the following commands: 2. Install the plugin: ``` - "/plugin install code-apps@power-platform-skills" + /plugin install code-apps-preview@power-platform-skills ``` ## Available Commands @@ -45,7 +45,7 @@ Start with `/create-code-app` — it walks you through everything. ## Uninstall ``` -/plugin uninstall code-apps +/plugin uninstall code-apps-preview ``` ## Documentation diff --git a/plugins/code-apps/agents/code-app-architect.md b/plugins/code-apps/agents/code-app-architect.md index e557c9e0a..521326df5 100644 --- a/plugins/code-apps/agents/code-app-architect.md +++ b/plugins/code-apps/agents/code-app-architect.md @@ -3,7 +3,7 @@ name: code-app-architect description: Power Apps Code App Architect specializing in React/Vite architecture, Dataverse integration, connector patterns, and Power Platform deployment. Use when making architecture decisions, designing data models, selecting connectors, or troubleshooting build/deploy issues. --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns (Windows CLI, environment, planning, memory bank, execution style). +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns (Windows CLI, environment, planning, memory bank, execution style). # Code App Architect @@ -98,7 +98,7 @@ npx power-apps init -n '{app-name}' -e - **Lookup fields** expose `_fieldname_value` (GUID, read-only) for reading and `@odata.bind` for writing. - **Formatted values** can be requested via `Prefer: odata.include-annotations` header for server-side date/currency/label formatting. - **useState with enums**: Explicitly type picklist state fields as `number` to avoid TypeScript literal type inference. -- **File/Image columns**: Use the generated `upload`, `downloadFile`, `downloadImage`, and `deleteFileOrImage` service methods — never raw fetch. The model exports table-prefixed union types for type-safe column references (for example, `AccountsFileColumnName`, `AccountsImageColumnName`, and `AccountsUploadColumnName`, depending on the table). See [dataverse-reference.md](${CLAUDE_PLUGIN_ROOT}/skills/add-dataverse/references/dataverse-reference.md) for full patterns. +- **File/Image columns**: Use the generated `upload`, `downloadFile`, `downloadImage`, and `deleteFileOrImage` service methods — never raw fetch. The model exports table-prefixed union types for type-safe column references (for example, `AccountsFileColumnName`, `AccountsImageColumnName`, and `AccountsUploadColumnName`, depending on the table). See [dataverse-reference.md](${PLUGIN_ROOT}/skills/add-dataverse/references/dataverse-reference.md) for full patterns. ### Connector Workarounds diff --git a/plugins/code-apps/shared/shared-instructions.md b/plugins/code-apps/shared/shared-instructions.md index f6e37979e..4c42e6303 100644 --- a/plugins/code-apps/shared/shared-instructions.md +++ b/plugins/code-apps/shared/shared-instructions.md @@ -22,7 +22,7 @@ Run at the start of every skill execution (at most once per day). Notifies the u Exception: the baseline deploy in `create-code-app` Step 7 is pre-approved as part of the scaffold flow. The final deploy in Step 10 still requires confirmation. - **Confirm before any global install**: Before running `npm install -g ...` or `winget install ...`, ask: _"This will install [tool] globally on your machine. OK to proceed?"_ Wait for explicit user confirmation. This applies even when the install is a documented prerequisite. - **Confirm before writing outside project root**: Before writing, editing, or deleting any file that is not inside the current project directory, ask the user for confirmation. - Exception: writing `~/.claude/.power-apps-last-version-check` as part of the version check is pre-approved and does not require confirmation. + Exception: writing `~/.claude/.code-apps-preview-last-version-check` as part of the version check is pre-approved and does not require confirmation. ### MUST NOT diff --git a/plugins/code-apps/shared/version-check.md b/plugins/code-apps/shared/version-check.md index a47d721a9..b565bbc6e 100644 --- a/plugins/code-apps/shared/version-check.md +++ b/plugins/code-apps/shared/version-check.md @@ -6,10 +6,10 @@ ### Step 1: Check if a version check is needed -Look for the file `~/.claude/.power-apps-last-version-check`. Use Bash to read it if it exists: +Look for the file `~/.claude/.code-apps-preview-last-version-check`. Use Bash to read it if it exists: ```bash -cat ~/.claude/.power-apps-last-version-check 2>/dev/null +cat ~/.claude/.code-apps-preview-last-version-check 2>/dev/null ``` - If the file exists and contains today's date (YYYY-MM-DD format), **skip the entire check** and continue with the skill silently. @@ -17,17 +17,17 @@ cat ~/.claude/.power-apps-last-version-check 2>/dev/null ### Step 2: Read the local version -Read `${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json` and extract the `version` field. +Read `${PLUGIN_ROOT}/.plugin/plugin.json` and extract the `name` and `version` fields. ### Step 3: Fetch the latest version from the marketplace Fetch the remote marketplace manifest using Bash: ```bash -curl -fsSL https://raw.githubusercontent.com/equinor/power-platform-skills/main/.claude-plugin/marketplace.json 2>/dev/null +curl -fsSL https://raw.githubusercontent.com/equinor/power-platform-skills/main/marketplace.json 2>/dev/null ``` -Parse the `version` field from the first plugin in the `plugins` array. +Find the plugin entry whose `name` matches the local plugin name, read its `source`, then fetch the matching remote plugin manifest at `/.plugin/plugin.json` and parse its `version` field. ### Step 4: Compare versions @@ -37,10 +37,10 @@ Compare the local version against the remote version using semver rules — spli **If a newer version is available**, print this banner **before any other output**: -``` +```text ╔══════════════════════════════════════════════════════════════╗ ║ UPDATE AVAILABLE: v{local} → v{remote} ║ -║ Run: claude plugin update power-apps ║ +║ Run: claude plugin update code-apps-preview ║ ╚══════════════════════════════════════════════════════════════╝ ``` @@ -48,10 +48,10 @@ Compare the local version against the remote version using semver rules — spli ### Step 6: Update the timestamp -Write today's date (YYYY-MM-DD) to `~/.claude/.power-apps-last-version-check` using Bash: +Write today's date (YYYY-MM-DD) to `~/.claude/.code-apps-preview-last-version-check` using Bash: ```bash -mkdir -p ~/.claude && echo "YYYY-MM-DD" > ~/.claude/.power-apps-last-version-check +mkdir -p ~/.claude && echo "YYYY-MM-DD" > ~/.claude/.code-apps-preview-last-version-check ``` > **Note:** Writing to `~/.claude/` is a pre-approved exception to the "confirm before writing outside project root" guardrail. This file is a shared cache and does not affect the user's project. diff --git a/plugins/code-apps/skills/add-azuredevops/SKILL.md b/plugins/code-apps/skills/add-azuredevops/SKILL.md index d80e79340..d15475867 100644 --- a/plugins/code-apps/skills/add-azuredevops/SKILL.md +++ b/plugins/code-apps/skills/add-azuredevops/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: sonnet --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. # Add Azure DevOps @@ -18,11 +18,11 @@ model: sonnet ### Step 1: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Step 2: Add Connector -**First, find the connection ID** (see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md)): +**First, find the connection ID** (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): Run the `/list-connections` skill. Find the Azure DevOps connection in the output. If none exists, direct the user to create one using the environment-specific Connections URL — construct it from the active environment ID in context (from `power.config.json` or a prior step): `https://make.powerapps.com/environments//connections` → **+ New connection** → search for the connector → Create. @@ -94,7 +94,7 @@ await AzureDevOpsService.HttpRequest({ Docs: [Azure DevOps REST API](https://learn.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-7.2) -Use `Grep` to find specific methods in `src/generated/services/AzureDevOpsService.ts` (generated files can be very large -- see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). +Use `Grep` to find specific methods in `src/generated/services/AzureDevOpsService.ts` (generated files can be very large -- see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). ### Step 5: Build diff --git a/plugins/code-apps/skills/add-connector/SKILL.md b/plugins/code-apps/skills/add-connector/SKILL.md index f591f7d71..cb5da75fc 100644 --- a/plugins/code-apps/skills/add-connector/SKILL.md +++ b/plugins/code-apps/skills/add-connector/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: sonnet --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. # Add Connector (Generic) @@ -28,7 +28,7 @@ Fallback skill for any connector not covered by a specific `/add-*` skill. For c ### Step 1: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Step 2: Identify Connector @@ -59,7 +59,7 @@ Common connector API names: ### Step 3: Add Connector -**First, find the connection ID** (see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md)): +**First, find the connection ID** (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): Run the `/list-connections` skill. Find the connector in the output. If none exists, direct the user to create one using the environment-specific Connections URL — construct it from the active environment ID in context (from `power.config.json` or a prior step): `https://make.powerapps.com/environments//connections` → **+ New connection** → search for the connector → Create. diff --git a/plugins/code-apps/skills/add-datasource/SKILL.md b/plugins/code-apps/skills/add-datasource/SKILL.md index f5f137e8e..c98324d5f 100644 --- a/plugins/code-apps/skills/add-datasource/SKILL.md +++ b/plugins/code-apps/skills/add-datasource/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: sonnet --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. # Add Data Source @@ -16,7 +16,7 @@ Router skill that understands the user's goal and connects them to the right dat ### Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Understand the Goal diff --git a/plugins/code-apps/skills/add-dataverse/SKILL.md b/plugins/code-apps/skills/add-dataverse/SKILL.md index ae0d26a63..1b021c219 100644 --- a/plugins/code-apps/skills/add-dataverse/SKILL.md +++ b/plugins/code-apps/skills/add-dataverse/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: opus --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. **References:** diff --git a/plugins/code-apps/skills/add-excel/SKILL.md b/plugins/code-apps/skills/add-excel/SKILL.md index 431303f6c..4f033153c 100644 --- a/plugins/code-apps/skills/add-excel/SKILL.md +++ b/plugins/code-apps/skills/add-excel/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: sonnet --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. # Add Excel Online @@ -18,7 +18,7 @@ model: sonnet ### Step 1: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Step 2: Gather @@ -30,7 +30,7 @@ Ask the user: ### Step 3: Add Connector -**First, find the connection ID** (see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md)): +**First, find the connection ID** (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): Run the `/list-connections` skill. Find the Excel Online (Business) connection in the output. If none exists, direct the user to create one using the environment-specific Connections URL — construct it from the active environment ID in context (from `power.config.json` or a prior step): `https://make.powerapps.com/environments//connections` → **+ New connection** → search for the connector → Create. @@ -76,7 +76,7 @@ await ExcelOnlineBusinessService.AddRowIntoTable({ - For SharePoint, use the site path and drive ID - The `body` is a flat key-value object matching column headers -- do NOT wrap in `{ items: ... }` -Use `Grep` to find specific methods in `src/generated/services/ExcelOnlineBusinessService.ts` (generated files can be very large -- see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). +Use `Grep` to find specific methods in `src/generated/services/ExcelOnlineBusinessService.ts` (generated files can be very large -- see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). ### Step 5: Build diff --git a/plugins/code-apps/skills/add-mcscopilot/SKILL.md b/plugins/code-apps/skills/add-mcscopilot/SKILL.md index 211b57052..6a727eaa0 100644 --- a/plugins/code-apps/skills/add-mcscopilot/SKILL.md +++ b/plugins/code-apps/skills/add-mcscopilot/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: sonnet --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. # Add Microsoft Copilot Studio @@ -18,11 +18,11 @@ model: sonnet ### Step 1: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Step 2: Add Connector -**First, find the connection ID** (see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md)): +**First, find the connection ID** (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): Run the `/list-connections` skill. Find the Microsoft Copilot Studio connection in the output. If none exists, direct the user to create one using the environment-specific Connections URL — construct it from the active environment ID in context (from `power.config.json` or a prior step): `https://make.powerapps.com/environments//connections` → **+ New connection** → search for the connector → Create. @@ -66,7 +66,7 @@ if (agentResponse) { } ``` -Use `Grep` to find specific methods in the generated service file (generated files can be very large — see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). +Use `Grep` to find specific methods in the generated service file (generated files can be very large — see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). #### Known Issues diff --git a/plugins/code-apps/skills/add-office365/SKILL.md b/plugins/code-apps/skills/add-office365/SKILL.md index 46483f58f..598bba451 100644 --- a/plugins/code-apps/skills/add-office365/SKILL.md +++ b/plugins/code-apps/skills/add-office365/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: sonnet --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. # Add Office 365 Outlook @@ -18,11 +18,11 @@ model: sonnet ### Step 1: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Step 2: Add Connector -**First, find the connection ID** (see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md)): +**First, find the connection ID** (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): Run the `/list-connections` skill. Find the Office 365 Outlook connection in the output (API name contains `office365`). If none exists, direct the user to create one using the environment-specific Connections URL — construct it from the active environment ID in context (from `power.config.json` or a prior step): `https://make.powerapps.com/environments//connections` → **+ New connection** → search for the connector → Create. diff --git a/plugins/code-apps/skills/add-onedrive/SKILL.md b/plugins/code-apps/skills/add-onedrive/SKILL.md index 6be5fb9ae..cd451913c 100644 --- a/plugins/code-apps/skills/add-onedrive/SKILL.md +++ b/plugins/code-apps/skills/add-onedrive/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: sonnet --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. # Add OneDrive for Business @@ -18,11 +18,11 @@ model: sonnet ### Step 1: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Step 2: Add Connector -**First, find the connection ID** (see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md)): +**First, find the connection ID** (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): Run the `/list-connections` skill. Find the OneDrive for Business connection in the output. If none exists, direct the user to create one using the environment-specific Connections URL — construct it from the active environment ID in context (from `power.config.json` or a prior step): `https://make.powerapps.com/environments//connections` → **+ New connection** → search for the connector → Create. @@ -66,7 +66,7 @@ await OneDriveForBusinessService.CreateFile({ - Use `folderPath` for creating files by path, `id` for accessing existing files - Binary file content may need base64 encoding/decoding depending on the operation -Use `Grep` to find specific methods in `src/generated/services/OneDriveForBusinessService.ts` (generated files can be very large -- see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). +Use `Grep` to find specific methods in `src/generated/services/OneDriveForBusinessService.ts` (generated files can be very large -- see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). ### Step 4: Build diff --git a/plugins/code-apps/skills/add-sharepoint/SKILL.md b/plugins/code-apps/skills/add-sharepoint/SKILL.md index b8b3a3152..14f4533df 100644 --- a/plugins/code-apps/skills/add-sharepoint/SKILL.md +++ b/plugins/code-apps/skills/add-sharepoint/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: opus --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. **References:** @@ -26,7 +26,7 @@ Two paths: **existing lists** (skip to Step 6) or **new lists** (full workflow). ### Step 1: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Step 2: Plan @@ -84,7 +84,7 @@ Get explicit confirmation before creating. Use safe functions from [list-managem ### Step 6: Get Connection ID -Find the SharePoint Online connection ID (see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md)): +Find the SharePoint Online connection ID (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): Run the `/list-connections` skill. Find the SharePoint Online connection in the output. If none exists, direct the user to create one using the environment-specific Connections URL — construct it from the active environment ID in context (from `power.config.json` or a prior step): `https://make.powerapps.com/environments//connections` → **+ New connection** → search for the connector → Create. @@ -173,7 +173,7 @@ const content = await SharePointOnlineService.GetFileContent({ - Document library operations use folder/file IDs or server-relative URLs - Choice columns use **string values**, not integer picklist codes (unlike Dataverse) -Use `Grep` to find specific methods in `src/generated/services/SharePointOnlineService.ts` (generated files can be very large -- see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). +Use `Grep` to find specific methods in `src/generated/services/SharePointOnlineService.ts` (generated files can be very large -- see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). ### Step 11: Build diff --git a/plugins/code-apps/skills/add-sharepoint/references/sharepoint-reference.md b/plugins/code-apps/skills/add-sharepoint/references/sharepoint-reference.md index 51b1044b2..0835ce9b7 100644 --- a/plugins/code-apps/skills/add-sharepoint/references/sharepoint-reference.md +++ b/plugins/code-apps/skills/add-sharepoint/references/sharepoint-reference.md @@ -169,4 +169,4 @@ await SharePointOnlineService.DeleteItem({ }); ``` -Use `Grep` to find specific methods in `src/generated/services/SharePointOnlineService.ts` (generated files can be very large -- see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). +Use `Grep` to find specific methods in `src/generated/services/SharePointOnlineService.ts` (generated files can be very large -- see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). diff --git a/plugins/code-apps/skills/add-teams/SKILL.md b/plugins/code-apps/skills/add-teams/SKILL.md index e197950d9..9c8d28c71 100644 --- a/plugins/code-apps/skills/add-teams/SKILL.md +++ b/plugins/code-apps/skills/add-teams/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: sonnet --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. # Add Teams @@ -18,11 +18,11 @@ model: sonnet ### Step 1: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). ### Step 2: Add Connector -**First, find the connection ID** (see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md)): +**First, find the connection ID** (see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md)): Run the `/list-connections` skill. Find the Teams connection in the output. If none exists, direct the user to create one using the environment-specific Connections URL — construct it from the active environment ID in context (from `power.config.json` or a prior step): `https://make.powerapps.com/environments//connections` → **+ New connection** → search for the connector → Create. @@ -49,7 +49,7 @@ await TeamsService.PostMessageToConversation({ }); ``` -Use `Grep` to find specific methods in `src/generated/services/TeamsService.ts` (generated files can be very large -- see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). +Use `Grep` to find specific methods in `src/generated/services/TeamsService.ts` (generated files can be very large -- see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). ### Step 4: Build diff --git a/plugins/code-apps/skills/create-code-app/SKILL.md b/plugins/code-apps/skills/create-code-app/SKILL.md index a9e579d36..3aabd91e6 100644 --- a/plugins/code-apps/skills/create-code-app/SKILL.md +++ b/plugins/code-apps/skills/create-code-app/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash, LSP, TaskCreate, TaskUpdate, model: opus --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. **References:** @@ -23,7 +23,7 @@ model: opus ### Step 0: Check Memory Bank -Check for `memory-bank.md` per [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md). Skip completed steps. +Check for `memory-bank.md` per [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md). Skip completed steps. ### Step 1: Validate Prerequisites @@ -123,7 +123,7 @@ npx power-apps init -n '{user-provided-app-name}' -e **Authentication:** On first run, a browser window opens for Microsoft sign-in. Complete the login and the command continues. No separate auth setup is needed. -See [preferred-environment.md](${CLAUDE_PLUGIN_ROOT}/shared/preferred-environment.md) for environment selection details. +See [preferred-environment.md](${PLUGIN_ROOT}/shared/preferred-environment.md) for environment selection details. **`npx power-apps init` failure:** @@ -185,7 +185,7 @@ Each `/add-*` skill runs `npm run build` to catch errors. Do NOT deploy yet. **This is the core step.** Build the actual app features described in the plan from Step 3. -1. **Review generated services**: Use `Grep` to find methods in generated service files (they can be very large -- see [connector-reference.md](${CLAUDE_PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). Do NOT read entire generated files. +1. **Review generated services**: Use `Grep` to find methods in generated service files (they can be very large -- see [connector-reference.md](${PLUGIN_ROOT}/shared/connector-reference.md#inspecting-large-generated-files)). Do NOT read entire generated files. 2. **Build components**: Create React components for each screen/feature in the plan using EDS components (`@equinor/eds-core-react`) where available 3. **Connect data**: Wire components to generated services (use `*Service.getAll()`, `*Service.create()`, etc.) 4. **Apply theme**: Use EDS tokens for theming (see development standards). Respect user overrides if specified. @@ -194,7 +194,7 @@ Each `/add-*` skill runs `npm run build` to catch errors. Do NOT deploy yet. **Key rules:** - Use generated services for all data access -- never use fetch/axios directly -- Read [dataverse-reference.md](${CLAUDE_PLUGIN_ROOT}/skills/add-dataverse/references/dataverse-reference.md) if working with Dataverse (picklist fields, virtual fields, lookups have critical gotchas) +- Read [dataverse-reference.md](${PLUGIN_ROOT}/skills/add-dataverse/references/dataverse-reference.md) if working with Dataverse (picklist fields, virtual fields, lookups have critical gotchas) - Remove unused imports before building (TS6133 strict mode) - Don't edit files in `src/generated/` unless fixing known issues diff --git a/plugins/code-apps/skills/deploy/SKILL.md b/plugins/code-apps/skills/deploy/SKILL.md index 690da2974..b8591e7f0 100644 --- a/plugins/code-apps/skills/deploy/SKILL.md +++ b/plugins/code-apps/skills/deploy/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Read, Edit, Write, Grep, Glob, Bash model: sonnet --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns. # Deploy diff --git a/plugins/code-apps/skills/list-connections/SKILL.md b/plugins/code-apps/skills/list-connections/SKILL.md index a4875f737..65998ec4b 100644 --- a/plugins/code-apps/skills/list-connections/SKILL.md +++ b/plugins/code-apps/skills/list-connections/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Bash model: haiku --- -**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns (Windows CLI compatibility, memory bank, etc.). +**📋 Shared Instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** - Cross-cutting concerns (Windows CLI compatibility, memory bank, etc.). # List Connections diff --git a/plugins/code-apps/skills/report-issue/SKILL.md b/plugins/code-apps/skills/report-issue/SKILL.md index f9c15eefc..2e63306e6 100644 --- a/plugins/code-apps/skills/report-issue/SKILL.md +++ b/plugins/code-apps/skills/report-issue/SKILL.md @@ -10,4 +10,4 @@ allowed-tools: Read, Bash, Glob, Grep, AskUserQuestion, TaskCreate, TaskUpdate, model: sonnet --- -**Shared workflow: [report-issue-workflow.md](${CLAUDE_PLUGIN_ROOT}/../../shared/skills/report-issue/report-issue-workflow.md)** — Read and follow all phases defined in that file. +**Workflow: [report-issue-workflow.md](${PLUGIN_ROOT}/skills/report-issue/report-issue-workflow.md)** — Read and follow all phases defined in that bundled file. diff --git a/plugins/code-apps/skills/report-issue/report-issue-workflow.md b/plugins/code-apps/skills/report-issue/report-issue-workflow.md new file mode 100644 index 000000000..709963825 --- /dev/null +++ b/plugins/code-apps/skills/report-issue/report-issue-workflow.md @@ -0,0 +1,230 @@ +# Report Issue + +File a bug report on the [power-platform-skills](https://github.com/equinor/power-platform-skills) GitHub repository. This workflow gathers the required information, presents a full preview, and creates the issue on the user's behalf. + +**Initial request:** $ARGUMENTS + +> **WARNING:** Before proceeding, inform the user: "This will create an issue on the **public** GitHub repository [equinor/power-platform-skills](https://github.com/equinor/power-platform-skills). **Do not include any sensitive information** such as credentials, internal URLs, tenant IDs, customer data, or proprietary code in the bug report." + +--- + +## Phase 1: Identify Plugin & Version + +**Goal**: Determine which plugin the bug is for and its version. + +**Actions**: + +1. Create a task list with all 5 phases (see [Progress Tracking](#progress-tracking) table). +2. **Auto-detect the plugin name** from the current plugin context by reading `${PLUGIN_ROOT}/.plugin/plugin.json`. Extract the `name` field. +3. **Auto-detect the plugin version** from the same `plugin.json` — extract the `version` field. +4. If the plugin name or version cannot be determined, use `AskUserQuestion` to ask the user. + +**Output**: Plugin name and version confirmed. + +--- + +## Phase 2: Gather Bug Details + +**Goal**: Collect all required and optional fields for the bug report. + +**Actions**: + +### 2.1 Check Arguments + +If `$ARGUMENTS` contains a description of the bug, extract as much information as possible from it to pre-fill the fields below. Still confirm with the user. + +### 2.2 Ask for Bug Details + +Use `AskUserQuestion` to gather bug details. `AskUserQuestion` supports 1-4 questions per call, but each question requires 2-4 predefined options. Since bug report fields are free-text, use options as prompt hints and let the user type their actual response via "Other". Skip any field the user already provided in `$ARGUMENTS`. + +**Call 1** — Skill and bug description (2 questions): + +1. **"Which skill or command were you using?"** (header: "Skill") — Provide the plugin's known skills as options (e.g., `deploy-site`, `create-site`). This is optional — the user can skip it. +2. **"Please describe the bug clearly and concisely."** (header: "Bug") — Provide short prompt-hint options like "Skill failed with error", "Unexpected behavior", "Skill hangs or times out". The user will typically select "Other" to type their description. + +**Call 2** — Repro steps and expected vs actual (3 questions): + +1. **"What steps reproduce the bug?"** (header: "Repro steps") — Provide prompt-hint options like "Ran the skill with default settings", "Ran the skill with custom arguments". The user will typically select "Other" to type their steps. +2. **"What did you expect to happen?"** (header: "Expected") — Provide prompt-hint options like "Skill should have succeeded", "Different output expected". The user will typically select "Other" to type their answer. +3. **"What actually happened?"** (header: "Actual") — Provide prompt-hint options like "Got an error message", "Wrong output produced", "Skill hung or timed out". The user will typically select "Other" to type their answer. + +**Call 3** — Environment details: + +Use `AskUserQuestion` with a single question: **"We recommend including environment details (OS, Claude Code version, PAC CLI version) to help maintainers reproduce and debug the issue faster. Include them?"** (header: "Environment") — Options: "Yes, auto-collect them" (recommended), "No, skip". + +- If **yes**: Auto-collect the following by running shell commands: + - **OS**: Detect the user's OS name and version using an appropriate command for their platform. + - **Claude Code version**: Run `claude --version` + - **PAC CLI version**: Run `pac help` and extract the version from the output + - Present the collected details to the user. +- If **no**: Skip environment fields. + +**Call 4** — Logs: + +Use `AskUserQuestion` with a single question: **"Do you want to include any relevant logs or error messages?"** (header: "Logs") — Options: "Yes, I'll paste them", "No, skip". + +- If **yes**: Use `AskUserQuestion` to ask: **"Please paste any relevant logs or error messages."** (header: "Paste logs") — Provide prompt-hint options like "Error message from terminal", "Skill output log". The user will typically select "Other" to paste their content. +- If **no**: Skip this field. + +**Output**: All bug report fields gathered. + +--- + +## Phase 3: Preview Issue + +**Goal**: Present the complete issue to the user for review before creating it. + +**Actions**: + +Remind the user: **"This issue will be created on the public GitHub repository. Please review and make sure it does not contain any sensitive information (credentials, internal URLs, tenant IDs, customer data, or proprietary code)."** + +Format and display the full issue exactly as it will be filed: + +``` +===== ISSUE PREVIEW ===== + +Title: [Bug] + +Plugin: +Plugin Version: +Skill / Command: + +Bug Description: + + +Steps to Reproduce: + + +Expected Behavior: + + +Actual Behavior: + + +Relevant Logs / Screenshots: + + +Environment: + + +Labels: bug, + +--- +🤖 This issue was created using the /report-issue skill. + +===== END PREVIEW ===== +``` + +Use `AskUserQuestion` to confirm: + +> "Here's the issue that will be created. Would you like to proceed, or would you like to make any changes?" + +- If the user wants changes, go back and update the relevant fields, then preview again. +- If the user approves, proceed to Phase 4. + +**Output**: User-approved issue ready for creation. + +--- + +## Phase 4: Create Issue + +**Goal**: Create the GitHub issue using the `gh` CLI. + +**Actions**: + +1. Build the issue body from the gathered fields, matching the bug report template structure: + + ``` + ### Plugin + + + + ### Plugin Version + + + + ### Skill / Command + + + + ### Bug Description + + + + ### Steps to Reproduce + + + + ### Expected Behavior + + + + ### Actual Behavior + + + + ### Relevant Logs / Screenshots + + + + ### Environment + + + + --- + 🤖 *This issue was created using the `/report-issue` skill.* + ``` + +2. Create the issue via `gh`: + + ```bash + gh issue create --repo equinor/power-platform-skills --title "[Bug] " --label "bug" --label "" --body "" + ``` + + Use a HEREDOC for the body to preserve formatting. + +3. Capture and display the issue URL to the user. + +**Output**: Issue created, URL shared with user. + +--- + +## Phase 5: Summarize + +**Goal**: Confirm success and suggest next steps. + +**Actions**: + +1. Display the created issue URL. +2. Let the user know they can track the issue on GitHub. + +**Output**: Issue URL and next steps presented. + +--- + +## Important Notes + +### Throughout All Phases + +- **Use TaskCreate/TaskUpdate** to track progress at every phase. +- **Always use AskUserQuestion** before collecting any environment information — never run commands to gather system details without explicit user consent. +- **Always preview the full issue** before creating it — the user must approve the content. +- **Use the `gh` CLI** to create the issue — confirm it is authenticated before attempting. + +### Progress Tracking + +Before starting Phase 1, create a task list with all phases using `TaskCreate`: + +| Task subject | activeForm | Description | +|-------------|------------|-------------| +| Identify plugin and version | Identifying plugin | Auto-detect plugin name and version from plugin.json | +| Gather bug details | Gathering details | Collect bug description, repro steps, expected/actual behavior | +| Preview issue | Previewing issue | Show complete issue for user review and approval | +| Create issue | Creating issue | File the bug report on GitHub via gh CLI | +| Summarize | Summarizing | Confirm success and share issue URL | + +Mark each task `in_progress` when starting it and `completed` when done via `TaskUpdate`. + +--- + +**Begin with Phase 1: Identify Plugin & Version** diff --git a/plugins/mcp-apps/.claude-plugin/plugin.json b/plugins/mcp-apps/.claude-plugin/plugin.json index 3356cafc2..77f1c03cc 100644 --- a/plugins/mcp-apps/.claude-plugin/plugin.json +++ b/plugins/mcp-apps/.claude-plugin/plugin.json @@ -6,8 +6,8 @@ "name": "Microsoft", "url": "https://www.microsoft.com" }, - "homepage": "https://github.com/microsoft/power-platform-skills/", - "repository": "https://github.com/microsoft/power-platform-skills/", + "homepage": "https://github.com/equinor/power-platform-skills/", + "repository": "https://github.com/equinor/power-platform-skills/", "license": "MIT", "keywords": [ "mcp-apps", diff --git a/plugins/mcp-apps/.plugin/plugin.json b/plugins/mcp-apps/.plugin/plugin.json new file mode 100644 index 000000000..77f1c03cc --- /dev/null +++ b/plugins/mcp-apps/.plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "mcp-apps", + "version": "1.0.0", + "description": "Generate MCP App widgets for MCP tools. Produces self-contained HTML widgets using the MCP Apps protocol.", + "author": { + "name": "Microsoft", + "url": "https://www.microsoft.com" + }, + "homepage": "https://github.com/equinor/power-platform-skills/", + "repository": "https://github.com/equinor/power-platform-skills/", + "license": "MIT", + "keywords": [ + "mcp-apps", + "mcp", + "widget", + "ext-apps", + "fluent-ui", + "html", + "tool-visualization" + ] +} diff --git a/plugins/mcp-apps/README.md b/plugins/mcp-apps/README.md index 32fc55d2f..bd3409731 100644 --- a/plugins/mcp-apps/README.md +++ b/plugins/mcp-apps/README.md @@ -7,7 +7,7 @@ Generate interactive MCP App widgets for MCP tools using Claude Code or Visual S ### From the marketplace ```bash -/plugin marketplace add microsoft/power-platform-skills +/plugin marketplace add equinor/power-platform-skills /plugin install mcp-apps@power-platform-skills ``` diff --git a/plugins/mcp-apps/skills/report-issue/SKILL.md b/plugins/mcp-apps/skills/report-issue/SKILL.md index 6d0b8d352..6c01fbfb0 100644 --- a/plugins/mcp-apps/skills/report-issue/SKILL.md +++ b/plugins/mcp-apps/skills/report-issue/SKILL.md @@ -10,4 +10,4 @@ allowed-tools: Read, Bash, Glob, Grep, AskUserQuestion, TaskCreate, TaskUpdate, model: sonnet --- -**Shared workflow: [report-issue-workflow.md](${CLAUDE_PLUGIN_ROOT}/../../shared/skills/report-issue/report-issue-workflow.md)** — Read and follow all phases defined in that file. +**Workflow: [report-issue-workflow.md](${PLUGIN_ROOT}/skills/report-issue/report-issue-workflow.md)** — Read and follow all phases defined in that bundled file. diff --git a/plugins/mcp-apps/skills/report-issue/report-issue-workflow.md b/plugins/mcp-apps/skills/report-issue/report-issue-workflow.md new file mode 100644 index 000000000..709963825 --- /dev/null +++ b/plugins/mcp-apps/skills/report-issue/report-issue-workflow.md @@ -0,0 +1,230 @@ +# Report Issue + +File a bug report on the [power-platform-skills](https://github.com/equinor/power-platform-skills) GitHub repository. This workflow gathers the required information, presents a full preview, and creates the issue on the user's behalf. + +**Initial request:** $ARGUMENTS + +> **WARNING:** Before proceeding, inform the user: "This will create an issue on the **public** GitHub repository [equinor/power-platform-skills](https://github.com/equinor/power-platform-skills). **Do not include any sensitive information** such as credentials, internal URLs, tenant IDs, customer data, or proprietary code in the bug report." + +--- + +## Phase 1: Identify Plugin & Version + +**Goal**: Determine which plugin the bug is for and its version. + +**Actions**: + +1. Create a task list with all 5 phases (see [Progress Tracking](#progress-tracking) table). +2. **Auto-detect the plugin name** from the current plugin context by reading `${PLUGIN_ROOT}/.plugin/plugin.json`. Extract the `name` field. +3. **Auto-detect the plugin version** from the same `plugin.json` — extract the `version` field. +4. If the plugin name or version cannot be determined, use `AskUserQuestion` to ask the user. + +**Output**: Plugin name and version confirmed. + +--- + +## Phase 2: Gather Bug Details + +**Goal**: Collect all required and optional fields for the bug report. + +**Actions**: + +### 2.1 Check Arguments + +If `$ARGUMENTS` contains a description of the bug, extract as much information as possible from it to pre-fill the fields below. Still confirm with the user. + +### 2.2 Ask for Bug Details + +Use `AskUserQuestion` to gather bug details. `AskUserQuestion` supports 1-4 questions per call, but each question requires 2-4 predefined options. Since bug report fields are free-text, use options as prompt hints and let the user type their actual response via "Other". Skip any field the user already provided in `$ARGUMENTS`. + +**Call 1** — Skill and bug description (2 questions): + +1. **"Which skill or command were you using?"** (header: "Skill") — Provide the plugin's known skills as options (e.g., `deploy-site`, `create-site`). This is optional — the user can skip it. +2. **"Please describe the bug clearly and concisely."** (header: "Bug") — Provide short prompt-hint options like "Skill failed with error", "Unexpected behavior", "Skill hangs or times out". The user will typically select "Other" to type their description. + +**Call 2** — Repro steps and expected vs actual (3 questions): + +1. **"What steps reproduce the bug?"** (header: "Repro steps") — Provide prompt-hint options like "Ran the skill with default settings", "Ran the skill with custom arguments". The user will typically select "Other" to type their steps. +2. **"What did you expect to happen?"** (header: "Expected") — Provide prompt-hint options like "Skill should have succeeded", "Different output expected". The user will typically select "Other" to type their answer. +3. **"What actually happened?"** (header: "Actual") — Provide prompt-hint options like "Got an error message", "Wrong output produced", "Skill hung or timed out". The user will typically select "Other" to type their answer. + +**Call 3** — Environment details: + +Use `AskUserQuestion` with a single question: **"We recommend including environment details (OS, Claude Code version, PAC CLI version) to help maintainers reproduce and debug the issue faster. Include them?"** (header: "Environment") — Options: "Yes, auto-collect them" (recommended), "No, skip". + +- If **yes**: Auto-collect the following by running shell commands: + - **OS**: Detect the user's OS name and version using an appropriate command for their platform. + - **Claude Code version**: Run `claude --version` + - **PAC CLI version**: Run `pac help` and extract the version from the output + - Present the collected details to the user. +- If **no**: Skip environment fields. + +**Call 4** — Logs: + +Use `AskUserQuestion` with a single question: **"Do you want to include any relevant logs or error messages?"** (header: "Logs") — Options: "Yes, I'll paste them", "No, skip". + +- If **yes**: Use `AskUserQuestion` to ask: **"Please paste any relevant logs or error messages."** (header: "Paste logs") — Provide prompt-hint options like "Error message from terminal", "Skill output log". The user will typically select "Other" to paste their content. +- If **no**: Skip this field. + +**Output**: All bug report fields gathered. + +--- + +## Phase 3: Preview Issue + +**Goal**: Present the complete issue to the user for review before creating it. + +**Actions**: + +Remind the user: **"This issue will be created on the public GitHub repository. Please review and make sure it does not contain any sensitive information (credentials, internal URLs, tenant IDs, customer data, or proprietary code)."** + +Format and display the full issue exactly as it will be filed: + +``` +===== ISSUE PREVIEW ===== + +Title: [Bug] + +Plugin: +Plugin Version: +Skill / Command: + +Bug Description: + + +Steps to Reproduce: + + +Expected Behavior: + + +Actual Behavior: + + +Relevant Logs / Screenshots: + + +Environment: + + +Labels: bug, + +--- +🤖 This issue was created using the /report-issue skill. + +===== END PREVIEW ===== +``` + +Use `AskUserQuestion` to confirm: + +> "Here's the issue that will be created. Would you like to proceed, or would you like to make any changes?" + +- If the user wants changes, go back and update the relevant fields, then preview again. +- If the user approves, proceed to Phase 4. + +**Output**: User-approved issue ready for creation. + +--- + +## Phase 4: Create Issue + +**Goal**: Create the GitHub issue using the `gh` CLI. + +**Actions**: + +1. Build the issue body from the gathered fields, matching the bug report template structure: + + ``` + ### Plugin + + + + ### Plugin Version + + + + ### Skill / Command + + + + ### Bug Description + + + + ### Steps to Reproduce + + + + ### Expected Behavior + + + + ### Actual Behavior + + + + ### Relevant Logs / Screenshots + + + + ### Environment + + + + --- + 🤖 *This issue was created using the `/report-issue` skill.* + ``` + +2. Create the issue via `gh`: + + ```bash + gh issue create --repo equinor/power-platform-skills --title "[Bug] " --label "bug" --label "" --body "" + ``` + + Use a HEREDOC for the body to preserve formatting. + +3. Capture and display the issue URL to the user. + +**Output**: Issue created, URL shared with user. + +--- + +## Phase 5: Summarize + +**Goal**: Confirm success and suggest next steps. + +**Actions**: + +1. Display the created issue URL. +2. Let the user know they can track the issue on GitHub. + +**Output**: Issue URL and next steps presented. + +--- + +## Important Notes + +### Throughout All Phases + +- **Use TaskCreate/TaskUpdate** to track progress at every phase. +- **Always use AskUserQuestion** before collecting any environment information — never run commands to gather system details without explicit user consent. +- **Always preview the full issue** before creating it — the user must approve the content. +- **Use the `gh` CLI** to create the issue — confirm it is authenticated before attempting. + +### Progress Tracking + +Before starting Phase 1, create a task list with all phases using `TaskCreate`: + +| Task subject | activeForm | Description | +|-------------|------------|-------------| +| Identify plugin and version | Identifying plugin | Auto-detect plugin name and version from plugin.json | +| Gather bug details | Gathering details | Collect bug description, repro steps, expected/actual behavior | +| Preview issue | Previewing issue | Show complete issue for user review and approval | +| Create issue | Creating issue | File the bug report on GitHub via gh CLI | +| Summarize | Summarizing | Confirm success and share issue URL | + +Mark each task `in_progress` when starting it and `completed` when done via `TaskUpdate`. + +--- + +**Begin with Phase 1: Identify Plugin & Version** diff --git a/plugins/model-apps/.claude-plugin/plugin.json b/plugins/model-apps/.claude-plugin/plugin.json index cd83184dc..41615628c 100644 --- a/plugins/model-apps/.claude-plugin/plugin.json +++ b/plugins/model-apps/.claude-plugin/plugin.json @@ -1,13 +1,13 @@ { "name": "model-apps", "version": "2.2.0", - "description": "Build and deploy Power Apps generative pages for model-driven apps with specialist agents for planning, entity creation, and parallel code generation. Requires PAC CLI >= 2.7.0 and Azure CLI (`az`). See CHANGELOG.md for v1.x → v2.x migration.", + "description": "Build and deploy Power Apps generative pages for model-driven apps with specialist agents for planning, entity creation, and parallel code generation. Requires PAC CLI >= 2.7.0 and Azure CLI (`az`). See CHANGELOG.md for v1.x -> v2.x migration.", "author": { "name": "Microsoft", "url": "https://www.microsoft.com" }, - "homepage": "https://github.com/microsoft/power-platform-skills/", - "repository": "https://github.com/microsoft/power-platform-skills/", + "homepage": "https://github.com/equinor/power-platform-skills/", + "repository": "https://github.com/equinor/power-platform-skills/", "license": "MIT", "keywords": [ "model-apps", diff --git a/plugins/model-apps/.plugin/plugin.json b/plugins/model-apps/.plugin/plugin.json new file mode 100644 index 000000000..41615628c --- /dev/null +++ b/plugins/model-apps/.plugin/plugin.json @@ -0,0 +1,23 @@ +{ + "name": "model-apps", + "version": "2.2.0", + "description": "Build and deploy Power Apps generative pages for model-driven apps with specialist agents for planning, entity creation, and parallel code generation. Requires PAC CLI >= 2.7.0 and Azure CLI (`az`). See CHANGELOG.md for v1.x -> v2.x migration.", + "author": { + "name": "Microsoft", + "url": "https://www.microsoft.com" + }, + "homepage": "https://github.com/equinor/power-platform-skills/", + "repository": "https://github.com/equinor/power-platform-skills/", + "license": "MIT", + "keywords": [ + "model-apps", + "generative-pages", + "genux", + "genpage", + "react", + "fluent-ui", + "dataverse", + "pac-cli", + "model-driven" + ] +} diff --git a/plugins/model-apps/AGENTS.md b/plugins/model-apps/AGENTS.md index 1ec6f47d5..b64d7e3d7 100644 --- a/plugins/model-apps/AGENTS.md +++ b/plugins/model-apps/AGENTS.md @@ -25,7 +25,7 @@ claude --plugin-dir /path/to/plugins/model-apps ## Architecture ``` -.claude-plugin/plugin.json ← Plugin metadata (name, version, keywords) +.plugin/plugin.json ← Open Plugins metadata (name, version, keywords) .mcp.json ← MCP server config (Playwright for browser verification) AGENTS.md ← Plugin guidance for AI agents (this file) CLAUDE.md ← Symlink → AGENTS.md diff --git a/plugins/model-apps/README.md b/plugins/model-apps/README.md index 930976bcb..082c8dce9 100644 --- a/plugins/model-apps/README.md +++ b/plugins/model-apps/README.md @@ -7,7 +7,7 @@ Build and deploy generative pages (genux) for Power Apps model-driven apps. This ### From the marketplace ```bash -/plugin marketplace add microsoft/power-platform-skills +/plugin marketplace add equinor/power-platform-skills /plugin install model-apps@power-platform-skills ``` diff --git a/plugins/model-apps/agents/genpage-edit-planner.md b/plugins/model-apps/agents/genpage-edit-planner.md index 00459bc07..d937af785 100644 --- a/plugins/model-apps/agents/genpage-edit-planner.md +++ b/plugins/model-apps/agents/genpage-edit-planner.md @@ -30,7 +30,7 @@ You will be invoked by the `/genpage` skill with a prompt that includes: - The user's edit intent: `$ARGUMENTS` - The working directory (absolute path) -- The plugin root directory (`${CLAUDE_PLUGIN_ROOT}`) +- The plugin root directory (`${PLUGIN_ROOT}`) - The app-id and page-id of the page being edited - The download directory: `//` @@ -196,7 +196,7 @@ Write `genpage-edit-plan.md` to the working directory root (NOT inside the when applying the changes.> ## Relevant Samples - | Purpose | Sample | diff --git a/plugins/model-apps/agents/genpage-entity-builder.md b/plugins/model-apps/agents/genpage-entity-builder.md index fd71d1494..22ee2ceed 100644 --- a/plugins/model-apps/agents/genpage-entity-builder.md +++ b/plugins/model-apps/agents/genpage-entity-builder.md @@ -26,7 +26,7 @@ You will be invoked by the `/genpage` skill with a prompt that includes: - Path to `genpage-plan.md` - The working directory (where to write logs and intermediate JSON) -- The plugin root (`${CLAUDE_PLUGIN_ROOT}`) — where the JS scripts live +- The plugin root (`${PLUGIN_ROOT}`) — where the JS scripts live - The Dataverse environment URL (e.g. `https://aurorabapenv4ab3f.crmtest.dynamics.com`) The **Solution unique name** and **Publisher Prefix** are read directly from the @@ -38,7 +38,7 @@ and `create-relationship.js` call. `Default` is a valid value — it lands new components in the env's built-in Default Solution. There is no "omit" branch. You operate entirely through the Web API via the plugin's scripts under -`${CLAUDE_PLUGIN_ROOT}/scripts/`. **There is no MCP server. There is no Python. There +`${PLUGIN_ROOT}/scripts/`. **There is no MCP server. There is no Python. There is no Dataverse Skills plugin dependency.** --- @@ -48,7 +48,7 @@ is no Dataverse Skills plugin dependency.** Read `genpage-plan.md` at the path provided in your invocation prompt. The plan document follows a strict schema. See -`${CLAUDE_PLUGIN_ROOT}/references/plan-schema.md` for the full contract, +`${PLUGIN_ROOT}/references/plan-schema.md` for the full contract, especially the `## Entity Creation Required` section. Extract from the **`## Environment`** section: @@ -108,7 +108,7 @@ You still re-probe defensively in case the orchestrator's check went stale (e.g., the user revoked auth mid-run): ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/check-auth.js" +node "${PLUGIN_ROOT}/scripts/check-auth.js" ``` Parse the JSON output. If `ok: false`, **abort and surface the `message` field @@ -169,7 +169,7 @@ contract above. Pass it on every command. #### 4a. Create the table ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-table.js" \ +node "${PLUGIN_ROOT}/scripts/create-table.js" \ "$ENV_URL" \ "${PREFIX}_" \ "" \ @@ -194,7 +194,7 @@ omit `--solution "$SOLUTION"` for brevity but **every call must include it**. **String:** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ +node "${PLUGIN_ROOT}/scripts/add-column.js" \ "$ENV_URL" "" "${PREFIX}_email" "Email" string \ --max-length 200 --format Email \ --required-level None \ @@ -203,7 +203,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ **Memo (long text):** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ +node "${PLUGIN_ROOT}/scripts/add-column.js" \ "$ENV_URL" "" "${PREFIX}_notes" "Notes" memo \ --max-length 4000 --format TextArea \ --solution "$SOLUTION" @@ -211,25 +211,25 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ **Integer / Decimal / Money:** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ +node "${PLUGIN_ROOT}/scripts/add-column.js" \ "$ENV_URL" "" "${PREFIX}_count" "Count" integer \ --min 0 --max 10000 --solution "$SOLUTION" -node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ +node "${PLUGIN_ROOT}/scripts/add-column.js" \ "$ENV_URL" "" "${PREFIX}_amount" "Amount" money \ --precision 2 --max 1000000 --solution "$SOLUTION" ``` **DateTime:** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ +node "${PLUGIN_ROOT}/scripts/add-column.js" \ "$ENV_URL" "" "${PREFIX}_startdate" "Start Date" datetime \ --format DateOnly --behavior UserLocal --solution "$SOLUTION" ``` **Boolean:** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ +node "${PLUGIN_ROOT}/scripts/add-column.js" \ "$ENV_URL" "" "${PREFIX}_isactive" "Active" boolean \ --true-label "Active" --false-label "Inactive" --default true \ --solution "$SOLUTION" @@ -237,7 +237,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ **Picklist (choice column) — options are inline JSON or @file:** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/add-column.js" \ +node "${PLUGIN_ROOT}/scripts/add-column.js" \ "$ENV_URL" "" "${PREFIX}_status" "Status" picklist \ --options '[{"value":100000000,"label":"Active"},{"value":100000001,"label":"Inactive"},{"value":100000002,"label":"OnHold"}]' \ --solution "$SOLUTION" @@ -254,7 +254,7 @@ Append a row to the log for each successful add. Once both the referenced and referencing tables exist: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-relationship.js" 1n \ +node "${PLUGIN_ROOT}/scripts/create-relationship.js" 1n \ "$ENV_URL" \ "${PREFIX}__${PREFIX}_" \ "${PREFIX}_" \ @@ -275,7 +275,7 @@ properties on the child table — the navigation property name (e.g. #### 4d. Add N:N relationships ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-relationship.js" nn \ +node "${PLUGIN_ROOT}/scripts/create-relationship.js" nn \ "$ENV_URL" \ "${PREFIX}__${PREFIX}_" \ "${PREFIX}_" \ @@ -294,7 +294,7 @@ Use `add-to-solution.js` only when you need to add an **existing** component (e.g., a system table you didn't create) to a solution: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/add-to-solution.js" \ +node "${PLUGIN_ROOT}/scripts/add-to-solution.js" \ 1 ``` @@ -308,7 +308,7 @@ are in place. After all tables, columns, and relationships are created, run a verification query: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" \ GET \ "EntityDefinitions(LogicalName='_')?\$select=LogicalName,SchemaName,PrimaryNameAttribute" ``` @@ -354,7 +354,7 @@ If the user says yes: 3. Create parent records first (no @odata.bind references): ```bash - node "${CLAUDE_PLUGIN_ROOT}/scripts/create-record.js" \ + node "${PLUGIN_ROOT}/scripts/create-record.js" \ "_" --body @/-records.json ``` @@ -369,7 +369,7 @@ If the user says yes: ] ``` ```bash - node "${CLAUDE_PLUGIN_ROOT}/scripts/create-record.js" \ + node "${PLUGIN_ROOT}/scripts/create-record.js" \ "_" --body @/-records.json ``` @@ -403,7 +403,7 @@ Ready for RuntimeTypes generation. ## Critical Constraints -- **All Dataverse operations go through the JS scripts in `${CLAUDE_PLUGIN_ROOT}/scripts/`.** +- **All Dataverse operations go through the JS scripts in `${PLUGIN_ROOT}/scripts/`.** Do NOT call `pac` for entity create/update (PAC's metadata commands are limited). Do NOT write Python. Do NOT call MCP tools. - **One script invocation per logical operation.** Each script is idempotent in the diff --git a/plugins/model-apps/agents/genpage-page-builder.md b/plugins/model-apps/agents/genpage-page-builder.md index 0c3b74045..9fef97445 100644 --- a/plugins/model-apps/agents/genpage-page-builder.md +++ b/plugins/model-apps/agents/genpage-page-builder.md @@ -29,7 +29,7 @@ You will be invoked with a prompt that includes: - **Data mode** — either `dataverse` or `mock` - **RuntimeTypes path** — absolute path to `RuntimeTypes.ts` (present only when Data mode is `dataverse`) - **Working directory** — where to write the `.tsx` file -- **Plugin root** — `${CLAUDE_PLUGIN_ROOT}` for reading references and samples +- **Plugin root** — `${PLUGIN_ROOT}` for reading references and samples The **Data mode** flag is authoritative — use it to decide whether to perform Step 2 (read RuntimeTypes.ts) or skip it. Do not infer data mode from the plan document. @@ -39,7 +39,7 @@ The **Data mode** flag is authoritative — use it to decide whether to perform Read `genpage-plan.md` at the path provided in your invocation prompt. The plan document follows a strict schema. See -`${CLAUDE_PLUGIN_ROOT}/references/plan-schema.md` for the full contract. +`${PLUGIN_ROOT}/references/plan-schema.md` for the full contract. Locate and extract: @@ -70,7 +70,7 @@ For **mock data pages:** Skip this step. Generate realistic sample data inline. ## Step 2.5 — Icon-name validation (Grep-based) The plugin ships a verified icon list at -`${CLAUDE_PLUGIN_ROOT}/references/verified-icons.txt` (~5000 names from +`${PLUGIN_ROOT}/references/verified-icons.txt` (~5000 names from `@fluentui/react-icons`). **Do NOT load the full file into context** — it's ~26K tokens of dead weight. Instead, use `Grep` to validate names on demand. @@ -96,13 +96,13 @@ import has been Grep-validated against the verified list. Read the code generation rules reference: ``` -${CLAUDE_PLUGIN_ROOT}/references/rules.md +${PLUGIN_ROOT}/references/rules.md ``` Read the relevant sample file identified in the plan: ``` -${CLAUDE_PLUGIN_ROOT}/samples/[sample-name].tsx +${PLUGIN_ROOT}/samples/[sample-name].tsx ``` If **Data mode** is `dataverse` AND the page fits the "list / detail / pages @@ -110,7 +110,7 @@ the user navigates back to" profile (per the plan's Per-Page Specification), also read the data caching reference: ``` -${CLAUDE_PLUGIN_ROOT}/references/data-caching.md +${PLUGIN_ROOT}/references/data-caching.md ``` Skip the caching reference for forms, single-visit dashboards, mock-data pages, @@ -250,7 +250,7 @@ pattern (translation dictionary, RTL support, formatting helpers, usersettings fetch): ``` -${CLAUDE_PLUGIN_ROOT}/references/localization.md +${PLUGIN_ROOT}/references/localization.md ``` For English-only environments, skip this entirely — do not load the reference diff --git a/plugins/model-apps/agents/genpage-planner.md b/plugins/model-apps/agents/genpage-planner.md index 023ce6b8a..ea3cbea42 100644 --- a/plugins/model-apps/agents/genpage-planner.md +++ b/plugins/model-apps/agents/genpage-planner.md @@ -30,7 +30,7 @@ You will be invoked by the `/genpage` skill with a prompt that includes: - The user's requirements (`$ARGUMENTS`) - The working directory (absolute path where artifacts should be written) -- The plugin root directory (`${CLAUDE_PLUGIN_ROOT}`) +- The plugin root directory (`${PLUGIN_ROOT}`) --- @@ -197,7 +197,7 @@ If any entities need creating, note that entity creation requires: - A target solution (the planner asks you to pick one in the next step) Detection uses `pac model list-tables` natively; creation runs through the -plugin's own Web API scripts under `${CLAUDE_PLUGIN_ROOT}/scripts/`. +plugin's own Web API scripts under `${PLUGIN_ROOT}/scripts/`. ### App Detection @@ -241,7 +241,7 @@ Query the env for non-managed solutions (excluding the always-present "Default" "Active"): ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" "$ENV_URL" GET \ +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "$ENV_URL" GET \ "solutions?\$select=uniquename,friendlyname&\$expand=publisherid(\$select=customizationprefix)&\$filter=ismanaged eq false and uniquename ne 'Default' and uniquename ne 'Active' and isvisible eq true&\$top=10" ``` @@ -289,9 +289,9 @@ in the plan's `## Environment`. Specifics: - **Existing solution** → use it directly; capture its prefix from the Step 2 query. - **Create new under publisher ``** → resolve publisher uniquename, then create: ```bash - PUB=$(node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" "$ENV_URL" GET \ + PUB=$(node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "$ENV_URL" GET \ "publishers?\$select=uniquename&\$filter=customizationprefix eq ''&\$top=1") - node "${CLAUDE_PLUGIN_ROOT}/scripts/create-solution.js" "$ENV_URL" \ + node "${PLUGIN_ROOT}/scripts/create-solution.js" "$ENV_URL" \ "" "" --publisher "" ``` Omit `--publisher` to use the env's Default Publisher (prefix `new`). @@ -357,7 +357,7 @@ of truth** for all downstream agents. It must be fully self-contained. downstream agents parse by name. See: ``` -${CLAUDE_PLUGIN_ROOT}/references/plan-schema.md +${PLUGIN_ROOT}/references/plan-schema.md ``` Read that file before writing the plan. Every required section must be present with @@ -391,7 +391,7 @@ single-visit dashboards, or mock-data pages. The page-builder reads this field to decide whether to load `references/data-caching.md`. For the `## Relevant Samples` section: pick the most structurally relevant sample -from `${CLAUDE_PLUGIN_ROOT}/samples/` (e.g., 7-responsive-cards.tsx for card +from `${PLUGIN_ROOT}/samples/` (e.g., 7-responsive-cards.tsx for card layouts, 2-wizard-multi-step.tsx for wizards, 12-dialog-form-overlay.tsx for any page with a modal/dialog — create/edit forms, confirm-delete, detail-in-a-dialog). Do NOT list reference docs as samples — only files under `samples/`. diff --git a/plugins/model-apps/docs/architecture.md b/plugins/model-apps/docs/architecture.md index 396e7d723..a4340f269 100644 --- a/plugins/model-apps/docs/architecture.md +++ b/plugins/model-apps/docs/architecture.md @@ -111,7 +111,7 @@ full skill. ``` plugins/model-apps/ -├── .claude-plugin/plugin.json <-- version, name, keywords +├── .plugin/plugin.json <-- version, name, keywords ├── AGENTS.md / CLAUDE.md <-- agent guidance (this is the source) ├── README.md <-- user-facing intro ├── CHANGELOG.md <-- Keep-a-Changelog diff --git a/plugins/model-apps/references/plan-schema.md b/plugins/model-apps/references/plan-schema.md index 2ab6c76aa..b1246ce00 100644 --- a/plugins/model-apps/references/plan-schema.md +++ b/plugins/model-apps/references/plan-schema.md @@ -147,7 +147,7 @@ validation rejects values that look like `crb2b_playername` or even | `## Entity Creation Required` | Entity-builder | Exact literal "No entity creation required..." when empty, else per-entity subsections | | `## Existing Entities` | Orchestrator (for `pac model genpage generate-types --data-sources`) | Comma-separated logical names | | `## Design Preferences` | Page-builder | Prose, free-form | -| `## Relevant Samples` | Page-builder (for Read path resolution) | Sample filename must match a file in `${CLAUDE_PLUGIN_ROOT}/samples/` | +| `## Relevant Samples` | Page-builder (for Read path resolution) | Sample filename must match a file in `${PLUGIN_ROOT}/samples/` | | `## Per-Page Specifications` | Page-builder | Each page gets one `### [Page Name]` subsection matching the Pages table | --- diff --git a/plugins/model-apps/references/rules.md b/plugins/model-apps/references/rules.md index 1039b2424..fe8d0398d 100644 --- a/plugins/model-apps/references/rules.md +++ b/plugins/model-apps/references/rules.md @@ -14,7 +14,7 @@ Comprehensive rules for generating generative page code. Read this file during c 6. **Entity Logical Names**: Use singular lowercase (e.g., `"account"` not `"accounts"`) 7. **Styling**: Use `makeStyles` with tokens; avoid inline styles except for dynamic values 8. **Responsive Design**: Use flexbox and relative units; NEVER use `100vh`/`100vw` -9. **Icons — verified names only**: Import from `@fluentui/react-icons`; use unsized variants only (e.g., `AddRegular` not `Add24Regular`). Icon names are frequently hallucinated — names like `MedicalRegular`, `PawRegular`, `AnimalRabbitRegular`, `BirdRegular` do not exist. **Always Read `${CLAUDE_PLUGIN_ROOT}/references/verified-icons.txt`** (~5000 names) and cross-check every icon import against that list. After writing, Grep your own output for `from "@fluentui/react-icons"` and verify each named import. If an icon you want is not in the list, pick the closest semantic substitute that is. Never guess a name. +9. **Icons — verified names only**: Import from `@fluentui/react-icons`; use unsized variants only (e.g., `AddRegular` not `Add24Regular`). Icon names are frequently hallucinated — names like `MedicalRegular`, `PawRegular`, `AnimalRabbitRegular`, `BirdRegular` do not exist. **Always Read `${PLUGIN_ROOT}/references/verified-icons.txt`** (~5000 names) and cross-check every icon import against that list. After writing, Grep your own output for `from "@fluentui/react-icons"` and verify each named import. If an icon you want is not in the list, pick the closest semantic substitute that is. Never guess a name. 10. **No External Libraries**: No routing libraries (React Router) or assumptions of implicit dependencies 11. **No FluentProvider**: Already provided at root — adding another causes a double-render flicker in React 17. For dark mode/theme overrides, use the `themeToVars` two-div pattern in **Special Patterns > Dark Mode Toggle**. 12. **Forbidden Functions**: Don't use `createTheme`, `mergeThemes`, `useTheme` (don't exist in Fluent UI V9) @@ -170,7 +170,7 @@ Localization guidance has been moved to a separate reference that is loaded configured languages OR any non-English language. For English-only environments, skip this entirely. -See: `${CLAUDE_PLUGIN_ROOT}/references/localization.md` +See: `${PLUGIN_ROOT}/references/localization.md` --- diff --git a/plugins/model-apps/references/supported-dependencies.md b/plugins/model-apps/references/supported-dependencies.md index 818e9a422..ac43c6b10 100644 --- a/plugins/model-apps/references/supported-dependencies.md +++ b/plugins/model-apps/references/supported-dependencies.md @@ -66,5 +66,5 @@ When upstream confirms versions or a runtime upgrade lands: 1. Edit `scripts/lib/supported-dependencies.js` — change the `version` field and (if applicable) flip `confidence` from `compatible` to `pinned`. 2. Re-run `node --test plugins/model-apps/scripts/tests/generate-page-manifest.test.js` — the tests assert the generated manifest's dependency map, feature-flag handling, and `--force` behavior. 3. Update this doc to reflect the new versions (the table is hand-maintained from the JS module — a small sync script could automate it later). -4. Bump the plugin minor or patch version in `.claude-plugin/plugin.json`. +4. Bump the plugin minor or patch version in `.plugin/plugin.json`. 5. CHANGELOG entry under the appropriate version section. diff --git a/plugins/model-apps/scripts/generate-page-manifest.js b/plugins/model-apps/scripts/generate-page-manifest.js index 3f4c053e7..d5077c4be 100644 --- a/plugins/model-apps/scripts/generate-page-manifest.js +++ b/plugins/model-apps/scripts/generate-page-manifest.js @@ -110,7 +110,7 @@ function buildAmbientDeclarations() { // \`(window as any).\` casts. // // Regenerate by running: -// node \${CLAUDE_PLUGIN_ROOT}/scripts/generate-page-manifest.js --force +// node \${PLUGIN_ROOT}/scripts/generate-page-manifest.js --force export {}; diff --git a/plugins/model-apps/skills/genpage/SKILL.md b/plugins/model-apps/skills/genpage/SKILL.md index 4d1ee6218..c915f4cd9 100644 --- a/plugins/model-apps/skills/genpage/SKILL.md +++ b/plugins/model-apps/skills/genpage/SKILL.md @@ -76,7 +76,7 @@ definition" in their editor. Versions come from `scripts/lib/supported-dependencies.js`). ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/generate-page-manifest.js" +node "${PLUGIN_ROOT}/scripts/generate-page-manifest.js" ``` - `` is the same slug used for the working directory. @@ -129,7 +129,7 @@ Pass a prompt that includes: - The user's requirements: `$ARGUMENTS` - The working directory (absolute path from Phase 0) -- The plugin root path: `${CLAUDE_PLUGIN_ROOT}` +- The plugin root path: `${PLUGIN_ROOT}` Example: @@ -138,7 +138,7 @@ Example: > [paste $ARGUMENTS here verbatim, or "no arguments provided — gather from user"] > > Working directory: [absolute path from Phase 0] -> Plugin root: ${CLAUDE_PLUGIN_ROOT} +> Plugin root: ${PLUGIN_ROOT} > > Follow the instructions in your agent file. Validate prereqs, confirm auth, ask > the new/edit question via AskUserQuestion, then proceed accordingly. Write @@ -162,7 +162,7 @@ auth, and the `az` and `pac` identities should normally match. Run the consolidated pre-flight: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/check-auth.js" +node "${PLUGIN_ROOT}/scripts/check-auth.js" ``` It returns a single JSON object: @@ -195,7 +195,7 @@ Capture `envUrl` from the result — Phase 2b passes it to the entity-builder. Invoke the `genpage-entity-builder` agent via the `Task` tool. Pass in the prompt: - Path to `genpage-plan.md` - Working directory (absolute path) -- Plugin root: `${CLAUDE_PLUGIN_ROOT}` +- Plugin root: `${PLUGIN_ROOT}` - Dataverse env URL (from `pac org who`) The entity-builder reads `Solution` and `Publisher Prefix` directly from the @@ -260,24 +260,24 @@ Before invoking any builders, verify: rewrite the plan appending `-1`, `-2`, etc. before dispatch. Duplicate filenames cause silent last-writer-wins data loss under parallel execution. -See `${CLAUDE_PLUGIN_ROOT}/references/plan-schema.md` for the full contract. +See `${PLUGIN_ROOT}/references/plan-schema.md` for the full contract. #### 5b. Single-page fast path (skip Task dispatch when N=1) **If the plan's Pages table contains exactly one row**, do NOT dispatch a Task subagent. Inline the page-builder workflow directly in the orchestrator: -1. Read `${CLAUDE_PLUGIN_ROOT}/references/rules.md` +1. Read `${PLUGIN_ROOT}/references/rules.md` 2. Read the sample listed in the plan's `## Relevant Samples` 3. If the plan's Per-Page Specification has `Needs caching: true`, also read - `${CLAUDE_PLUGIN_ROOT}/references/data-caching.md` + `${PLUGIN_ROOT}/references/data-caching.md` 4. If the plan's `## Environment` indicates non-English languages, also read - `${CLAUDE_PLUGIN_ROOT}/references/localization.md` + `${PLUGIN_ROOT}/references/localization.md` 5. Read `genpage-plan.md` (already in working directory) and `RuntimeTypes.ts` if Data mode is dataverse 6. Write the `.tsx` file to `/.tsx` following all rules 7. After writing, Grep every named import from `@fluentui/react-icons` against - `${CLAUDE_PLUGIN_ROOT}/references/verified-icons.txt` (one Grep per name). + `${PLUGIN_ROOT}/references/verified-icons.txt` (one Grep per name). Rewrite any unverified names with the closest verified alternative; do not load the full icon list into context 8. Proceed to Phase 6 @@ -298,7 +298,7 @@ For each page, pass a prompt that includes: - Absolute path to `genpage-plan.md` - Data mode (see below) — either a RuntimeTypes path or an explicit mock flag - Working directory -- Plugin root: `${CLAUDE_PLUGIN_ROOT}` +- Plugin root: `${PLUGIN_ROOT}` **For Dataverse pages**, include the RuntimeTypes line: @@ -309,7 +309,7 @@ For each page, pass a prompt that includes: > - Data mode: **dataverse** > - RuntimeTypes: [absolute path to RuntimeTypes.ts] > - Working directory: [absolute path from Phase 0] -> - Plugin root: ${CLAUDE_PLUGIN_ROOT} +> - Plugin root: ${PLUGIN_ROOT} > > Follow the instructions in your agent file. Write [filename].tsx and return your > result when done. @@ -322,7 +322,7 @@ For each page, pass a prompt that includes: > - Plan document: [absolute path to genpage-plan.md] > - Data mode: **mock** > - Working directory: [absolute path from Phase 0] -> - Plugin root: ${CLAUDE_PLUGIN_ROOT} +> - Plugin root: ${PLUGIN_ROOT} > > Follow the instructions in your agent file. Write [filename].tsx and return your > result when done. @@ -428,7 +428,7 @@ After successful deployment, ask the user via `AskUserQuestion`: Options: **Yes, verify in browser** / **Skip verification** - If the user picks **Skip verification** → jump to Phase 8. -- If the user picks **Yes** → read `${CLAUDE_PLUGIN_ROOT}/skills/genpage/verify-flow.md` +- If the user picks **Yes** → read `${PLUGIN_ROOT}/skills/genpage/verify-flow.md` for the full Playwright verification workflow (navigate, structural verification including below-the-fold, interactive testing, screenshots, fix-and-redeploy). The orchestrator only loads that file on demand to keep diff --git a/plugins/model-apps/skills/genpage/edit-flow.md b/plugins/model-apps/skills/genpage/edit-flow.md index 54067f2a1..9a67575d9 100644 --- a/plugins/model-apps/skills/genpage/edit-flow.md +++ b/plugins/model-apps/skills/genpage/edit-flow.md @@ -100,7 +100,7 @@ Invoke the `genpage-edit-planner` agent via the `Task` tool. Pass: - The user's edit intent: `$ARGUMENTS` - The working directory (absolute path) -- The plugin root: `${CLAUDE_PLUGIN_ROOT}` +- The plugin root: `${PLUGIN_ROOT}` - The app-id and page-id - The download directory: `//` @@ -114,7 +114,7 @@ Read `/genpage-edit-plan.md` for the approved change list and preservation constraints. Also read: -- `${CLAUDE_PLUGIN_ROOT}/references/rules.md` — all code-gen +- `${PLUGIN_ROOT}/references/rules.md` — all code-gen rules still apply to edits (Fluent UI V9 only, makeStyles with tokens, WCAG AA, no `100vh`/`100vw`, etc.) - `/RuntimeTypes.ts` — if generated in Edit Phase 3, for verified diff --git a/plugins/model-apps/skills/report-issue/SKILL.md b/plugins/model-apps/skills/report-issue/SKILL.md index 8a482a361..169e8dc03 100644 --- a/plugins/model-apps/skills/report-issue/SKILL.md +++ b/plugins/model-apps/skills/report-issue/SKILL.md @@ -10,4 +10,4 @@ allowed-tools: Read, Bash, Glob, Grep, AskUserQuestion, TaskCreate, TaskUpdate, model: sonnet --- -**Shared workflow: [report-issue-workflow.md](${CLAUDE_PLUGIN_ROOT}/../../shared/skills/report-issue/report-issue-workflow.md)** — Read and follow all phases defined in that file. +**Workflow: [report-issue-workflow.md](${PLUGIN_ROOT}/skills/report-issue/report-issue-workflow.md)** — Read and follow all phases defined in that bundled file. diff --git a/plugins/model-apps/skills/report-issue/report-issue-workflow.md b/plugins/model-apps/skills/report-issue/report-issue-workflow.md new file mode 100644 index 000000000..709963825 --- /dev/null +++ b/plugins/model-apps/skills/report-issue/report-issue-workflow.md @@ -0,0 +1,230 @@ +# Report Issue + +File a bug report on the [power-platform-skills](https://github.com/equinor/power-platform-skills) GitHub repository. This workflow gathers the required information, presents a full preview, and creates the issue on the user's behalf. + +**Initial request:** $ARGUMENTS + +> **WARNING:** Before proceeding, inform the user: "This will create an issue on the **public** GitHub repository [equinor/power-platform-skills](https://github.com/equinor/power-platform-skills). **Do not include any sensitive information** such as credentials, internal URLs, tenant IDs, customer data, or proprietary code in the bug report." + +--- + +## Phase 1: Identify Plugin & Version + +**Goal**: Determine which plugin the bug is for and its version. + +**Actions**: + +1. Create a task list with all 5 phases (see [Progress Tracking](#progress-tracking) table). +2. **Auto-detect the plugin name** from the current plugin context by reading `${PLUGIN_ROOT}/.plugin/plugin.json`. Extract the `name` field. +3. **Auto-detect the plugin version** from the same `plugin.json` — extract the `version` field. +4. If the plugin name or version cannot be determined, use `AskUserQuestion` to ask the user. + +**Output**: Plugin name and version confirmed. + +--- + +## Phase 2: Gather Bug Details + +**Goal**: Collect all required and optional fields for the bug report. + +**Actions**: + +### 2.1 Check Arguments + +If `$ARGUMENTS` contains a description of the bug, extract as much information as possible from it to pre-fill the fields below. Still confirm with the user. + +### 2.2 Ask for Bug Details + +Use `AskUserQuestion` to gather bug details. `AskUserQuestion` supports 1-4 questions per call, but each question requires 2-4 predefined options. Since bug report fields are free-text, use options as prompt hints and let the user type their actual response via "Other". Skip any field the user already provided in `$ARGUMENTS`. + +**Call 1** — Skill and bug description (2 questions): + +1. **"Which skill or command were you using?"** (header: "Skill") — Provide the plugin's known skills as options (e.g., `deploy-site`, `create-site`). This is optional — the user can skip it. +2. **"Please describe the bug clearly and concisely."** (header: "Bug") — Provide short prompt-hint options like "Skill failed with error", "Unexpected behavior", "Skill hangs or times out". The user will typically select "Other" to type their description. + +**Call 2** — Repro steps and expected vs actual (3 questions): + +1. **"What steps reproduce the bug?"** (header: "Repro steps") — Provide prompt-hint options like "Ran the skill with default settings", "Ran the skill with custom arguments". The user will typically select "Other" to type their steps. +2. **"What did you expect to happen?"** (header: "Expected") — Provide prompt-hint options like "Skill should have succeeded", "Different output expected". The user will typically select "Other" to type their answer. +3. **"What actually happened?"** (header: "Actual") — Provide prompt-hint options like "Got an error message", "Wrong output produced", "Skill hung or timed out". The user will typically select "Other" to type their answer. + +**Call 3** — Environment details: + +Use `AskUserQuestion` with a single question: **"We recommend including environment details (OS, Claude Code version, PAC CLI version) to help maintainers reproduce and debug the issue faster. Include them?"** (header: "Environment") — Options: "Yes, auto-collect them" (recommended), "No, skip". + +- If **yes**: Auto-collect the following by running shell commands: + - **OS**: Detect the user's OS name and version using an appropriate command for their platform. + - **Claude Code version**: Run `claude --version` + - **PAC CLI version**: Run `pac help` and extract the version from the output + - Present the collected details to the user. +- If **no**: Skip environment fields. + +**Call 4** — Logs: + +Use `AskUserQuestion` with a single question: **"Do you want to include any relevant logs or error messages?"** (header: "Logs") — Options: "Yes, I'll paste them", "No, skip". + +- If **yes**: Use `AskUserQuestion` to ask: **"Please paste any relevant logs or error messages."** (header: "Paste logs") — Provide prompt-hint options like "Error message from terminal", "Skill output log". The user will typically select "Other" to paste their content. +- If **no**: Skip this field. + +**Output**: All bug report fields gathered. + +--- + +## Phase 3: Preview Issue + +**Goal**: Present the complete issue to the user for review before creating it. + +**Actions**: + +Remind the user: **"This issue will be created on the public GitHub repository. Please review and make sure it does not contain any sensitive information (credentials, internal URLs, tenant IDs, customer data, or proprietary code)."** + +Format and display the full issue exactly as it will be filed: + +``` +===== ISSUE PREVIEW ===== + +Title: [Bug] + +Plugin: +Plugin Version: +Skill / Command: + +Bug Description: + + +Steps to Reproduce: + + +Expected Behavior: + + +Actual Behavior: + + +Relevant Logs / Screenshots: + + +Environment: + + +Labels: bug, + +--- +🤖 This issue was created using the /report-issue skill. + +===== END PREVIEW ===== +``` + +Use `AskUserQuestion` to confirm: + +> "Here's the issue that will be created. Would you like to proceed, or would you like to make any changes?" + +- If the user wants changes, go back and update the relevant fields, then preview again. +- If the user approves, proceed to Phase 4. + +**Output**: User-approved issue ready for creation. + +--- + +## Phase 4: Create Issue + +**Goal**: Create the GitHub issue using the `gh` CLI. + +**Actions**: + +1. Build the issue body from the gathered fields, matching the bug report template structure: + + ``` + ### Plugin + + + + ### Plugin Version + + + + ### Skill / Command + + + + ### Bug Description + + + + ### Steps to Reproduce + + + + ### Expected Behavior + + + + ### Actual Behavior + + + + ### Relevant Logs / Screenshots + + + + ### Environment + + + + --- + 🤖 *This issue was created using the `/report-issue` skill.* + ``` + +2. Create the issue via `gh`: + + ```bash + gh issue create --repo equinor/power-platform-skills --title "[Bug] " --label "bug" --label "" --body "" + ``` + + Use a HEREDOC for the body to preserve formatting. + +3. Capture and display the issue URL to the user. + +**Output**: Issue created, URL shared with user. + +--- + +## Phase 5: Summarize + +**Goal**: Confirm success and suggest next steps. + +**Actions**: + +1. Display the created issue URL. +2. Let the user know they can track the issue on GitHub. + +**Output**: Issue URL and next steps presented. + +--- + +## Important Notes + +### Throughout All Phases + +- **Use TaskCreate/TaskUpdate** to track progress at every phase. +- **Always use AskUserQuestion** before collecting any environment information — never run commands to gather system details without explicit user consent. +- **Always preview the full issue** before creating it — the user must approve the content. +- **Use the `gh` CLI** to create the issue — confirm it is authenticated before attempting. + +### Progress Tracking + +Before starting Phase 1, create a task list with all phases using `TaskCreate`: + +| Task subject | activeForm | Description | +|-------------|------------|-------------| +| Identify plugin and version | Identifying plugin | Auto-detect plugin name and version from plugin.json | +| Gather bug details | Gathering details | Collect bug description, repro steps, expected/actual behavior | +| Preview issue | Previewing issue | Show complete issue for user review and approval | +| Create issue | Creating issue | File the bug report on GitHub via gh CLI | +| Summarize | Summarizing | Confirm success and share issue URL | + +Mark each task `in_progress` when starting it and `completed` when done via `TaskUpdate`. + +--- + +**Begin with Phase 1: Identify Plugin & Version** diff --git a/plugins/power-pages/.claude-plugin/plugin.json b/plugins/power-pages/.claude-plugin/plugin.json index 06fd5d811..200e85826 100644 --- a/plugins/power-pages/.claude-plugin/plugin.json +++ b/plugins/power-pages/.claude-plugin/plugin.json @@ -1,13 +1,13 @@ { "name": "power-pages", - "version": "2.0.0", + "version": "2.6.0", "description": "Create and deploy Power Pages sites using modern development approaches. Supports code sites (SPAs) with React, Angular, Vue, or Astro. Includes ALM orchestration (plan-alm) with a solution-splitting decision tree, per-solution pipelines, Azure Blob asset advisory, manifest schema v2 for multi-solution deployments, and force-link remediation for cross-host pipeline migrations.", "author": { "name": "Microsoft", "url": "https://www.microsoft.com" }, - "homepage": "https://github.com/microsoft/power-platform-skills/", - "repository": "https://github.com/microsoft/power-platform-skills/", + "homepage": "https://github.com/equinor/power-platform-skills/", + "repository": "https://github.com/equinor/power-platform-skills/", "license": "MIT", "keywords": [ "power-pages", diff --git a/plugins/power-pages/.plugin/plugin.json b/plugins/power-pages/.plugin/plugin.json new file mode 100644 index 000000000..200e85826 --- /dev/null +++ b/plugins/power-pages/.plugin/plugin.json @@ -0,0 +1,39 @@ +{ + "name": "power-pages", + "version": "2.6.0", + "description": "Create and deploy Power Pages sites using modern development approaches. Supports code sites (SPAs) with React, Angular, Vue, or Astro. Includes ALM orchestration (plan-alm) with a solution-splitting decision tree, per-solution pipelines, Azure Blob asset advisory, manifest schema v2 for multi-solution deployments, and force-link remediation for cross-host pipeline migrations.", + "author": { + "name": "Microsoft", + "url": "https://www.microsoft.com" + }, + "homepage": "https://github.com/equinor/power-platform-skills/", + "repository": "https://github.com/equinor/power-platform-skills/", + "license": "MIT", + "keywords": [ + "power-pages", + "site-builder", + "code-sites", + "spa", + "react", + "angular", + "vue", + "astro", + "pac-cli", + "dataverse", + "odata", + "web-api", + "tables", + "schema", + "solution", + "alm", + "plan-alm", + "ci-cd", + "pipeline", + "pipelines-host", + "force-link", + "diagnostics", + "shared-lib", + "solution-splitting", + "asset-advisory" + ] +} diff --git a/plugins/power-pages/AGENTS.md b/plugins/power-pages/AGENTS.md index ebb4420aa..9be05f207 100644 --- a/plugins/power-pages/AGENTS.md +++ b/plugins/power-pages/AGENTS.md @@ -15,7 +15,7 @@ Read `PLUGIN_DEVELOPMENT_GUIDE.md` for UX and reliability standards when creatin - **Script changes require tests** — Whenever you add a new script or modify an existing script, add or update `node:test` coverage under `scripts/tests/`. Prefer one `*.test.js` file per script/module being tested, and keep the test command passing: `node --test plugins/power-pages/scripts/tests/` (Node's built-in runner discovers `*.test.js` files under the given directory). Validator changes are not an exception; they must always ship with test coverage. - **Dataverse-backed validation** must stay opt-in for local runs only. Do not require live Dataverse connectivity in CI workflows or default test runs; gate it behind explicit local flags such as `--validate-dataverse-relationships`. - **Azure CLI `--allow-no-subscriptions`** — this flag is only valid on `az login`. Other `az` subcommands (`az account get-access-token`, `az account show`, etc.) reject it as an unrecognized argument and exit 2, so do NOT add it to anything other than `az login`. When the user is not logged in to the Azure CLI, suggest plain `az login` first; only suggest `az login --allow-no-subscriptions` as a fallback if they don't have any associated Azure subscription, since that variant lets subscription-less accounts sign in and still mint AAD-scoped Dataverse/Power Platform tokens via subsequent `az account get-access-token` calls. Reuse the shared `getAuthToken` helper in `scripts/lib/validation-helpers.js` instead of shelling out to `az` directly. -- **Reference docs** shared across skills live in `references/` — reference via `${CLAUDE_PLUGIN_ROOT}/references/` paths, don't duplicate. +- **Reference docs** shared across skills live in `references/` — reference via `${PLUGIN_ROOT}/references/` paths, don't duplicate. - **Templates** use `__PLACEHOLDER__` tokens (e.g., `__SITE_NAME__`) replaced during scaffolding. The `gitignore` file is stored without the dot prefix and renamed to `.gitignore` during scaffolding. - **Hooks** are defined centrally in `hooks/hooks.json`, using `PostToolUse` with matcher `Skill` so validation runs when a tracked Power Pages skill completes. - **ALM split-decision thresholds** are intentionally tighter than the platform hard caps. `scripts/lib/alm-thresholds.js` recommends a split at 75 MB / 4000 components (vs platform caps of 95 MB / 6000), reserving ~20 MB / ~2000-component growth headroom in each split child. Override per-project via `.alm-config.json` if you have a justified reason to push closer to the caps. @@ -27,12 +27,15 @@ Read `PLUGIN_DEVELOPMENT_GUIDE.md` for UX and reliability standards when creatin ## Skill Development Conventions ``` -.claude-plugin/plugin.json ← Plugin metadata (name, version, keywords) +.plugin/plugin.json ← Open Plugins metadata (name, version, keywords) .mcp.json ← MCP server config (Playwright for browser automation) agents/ data-model-architect.md ← Agent: proposes Dataverse data models (read-only) webapi-integration.md ← Agent: implements Web API integration in frontend code - webapi-permissions.md ← Agent: proposes Web API permissions plan (read-only) + table-permissions-architect.md ← Agent: proposes table permissions plan (read-only) + webapi-settings-architect.md ← Agent: proposes Web API site settings with validated column names (read-only) + ai-webapi-integration.md ← Agent: implements generative-AI summarization service code + UI wiring + ai-webapi-settings-architect.md ← Agent: proposes Summarization/* site settings (read-only) scripts/ generate-uuid.js ← Shared UUID v4 generator (used by multiple skills) check-activation-status.js ← Checks if site is already activated (used by deploy-site, activate-site) @@ -76,6 +79,14 @@ skills/ integrate-webapi/ SKILL.md ← Web API integration skill definition scripts/validate-webapi-integration.js ← Node script validating Web API integration code + add-ai-webapi/ + SKILL.md ← Generative-AI summarization integration skill (Layer 3; preview) + references/ai-api-reference.md ← Canonical Search/Data Summarization API shapes, headers, error codes + references/explore-prompt.md ← Phase 2 Explore-agent prompt body + manifest shape + references/scope-classification.md ← Phase 3 list-trigger / scope-confirmation question mapping + references/agent-invocation-prompt.md ← Phase 5 ai-webapi-integration prompt template + references/framework-equivalents.md ← Vue/Angular/Astro safe-markdown + citation rendering snippets + scripts/validate-ai-webapi.js ← Node script validating summarization code, headers, and Summarization/* settings setup-auth/ SKILL.md ← Authentication & authorization skill definition references/authentication-reference.md ← Login/logout flow, auth service, framework patterns @@ -107,13 +118,13 @@ skills/ ## ALM intent routing — `plan-alm` is the front door -When the user expresses an **ALM intent** in natural language — *promote this site to {env}, ship to staging, deploy to production, set up CI/CD, move to next environment, push out a release, run the pipeline, export and import to staging* — invoke **`/power-pages:plan-alm` first**, before any individual ALM skill. The orchestrator detects the project state, runs the pre-plan completeness check, asks about promotion strategy, and dispatches to the right skills (`setup-solution`, `setup-pipeline`, `deploy-pipeline`, `activate-site`, `test-site`) in the right order. +When the user expresses an **ALM intent** in natural language — *promote this site to {env}, ship to staging, deploy to production, set up CI/CD, move to next environment, push out a release, run the pipeline, export and import to staging* — invoke **`/power-pages:plan-alm` first**, before any individual ALM skill. `plan-alm` is a **planner**: it detects the project state, runs the pre-plan completeness check, asks about promotion strategy, and writes a rendered HTML plan (whose `steps[]` array is the recommended execution sequence). **It does not deploy anything.** After the user approves the plan, *the user runs* the individual skills (`setup-solution`, `setup-pipeline`, `deploy-pipeline`, or `export-solution`/`import-solution`, plus `activate-site`/`test-site`) in the plan's order. Each detects the approved plan via its Phase 0 gate, proceeds without re-nagging, refreshes the plan on completion, and points the user at the next step — but never auto-chains. This separation keeps `plan-alm` safe to run unattended (no single answer can trigger an irreversible deployment). -**Do not** jump straight to `/power-pages:setup-pipeline`, `/power-pages:deploy-pipeline`, `/power-pages:export-solution`, or `/power-pages:import-solution` in response to an ALM intent. Those are individual building blocks; running them out of order misses the orchestrator's gates (completeness check, host resolution, deployment-strategy selection, post-deploy validation, rendered HTML plan). +**Do not** jump straight to `/power-pages:setup-pipeline`, `/power-pages:deploy-pipeline`, `/power-pages:export-solution`, or `/power-pages:import-solution` in response to an ALM intent. Those are individual building blocks; running them without a plan first misses the planner's analysis (completeness check, host resolution, deployment-strategy selection, size/split decisions, rendered HTML plan). **Skip `plan-alm` only when the user is explicit about the individual skill.** Phrases like *"just run setup-pipeline"*, *"skip planning, just deploy"*, *"I only need to export the solution zip"* are direct invocations — honor them. Anything ambiguous about deployment intent → `plan-alm` first. -`setup-pipeline` and `deploy-pipeline` enforce this with a Phase 0 ALM-plan gate. If a user invokes them directly without a plan, those skills surface the recommendation to run `plan-alm` first (with an "I know what I'm doing" escape hatch). The Phase 0 gate is meant to fail closed — don't bypass it on the user's behalf. +Every ALM execution skill enforces this with a Phase 0 ALM-plan gate. If a user invokes one directly without a plan, the skill recommends running `plan-alm` first (option 1, recommended) with a *"continue without a plan"* escape hatch; choosing to plan runs `plan-alm` (which only plans) and then the skill proceeds. The Phase 0 gate is meant to fail closed — don't bypass it on the user's behalf. ## Plugin Components @@ -123,7 +134,10 @@ Auto-triggered by the main conversation when relevant: - `data-model-architect`: Read-only agent that analyzes site requirements, discovers existing Dataverse tables via OData API, and proposes a data model (new/modified/reused tables + Mermaid ER diagram). Uses `pac env who` + Azure CLI auth to query Dataverse. Renders the ER diagram visually in the browser via Playwright (writes a temp HTML file with Mermaid.js CDN, navigates to it, takes a screenshot) before entering plan mode. Does NOT create, modify, or delete any tables — purely advisory. The main conversation uses its output to create tables. - `webapi-integration`: Implementation agent that creates production-ready Web API integration code for a single Dataverse table in a Power Pages code site. Detects the frontend framework (React/Vue/Angular/Astro), creates a shared `powerPagesApi.ts` client (token management, retry logic, OData URL builder) if one doesn't exist, then generates TypeScript entity types, a domain mapper, and a CRUD service layer for the target table. Also creates framework-specific hooks (React), composables (Vue), or injectable services (Angular). Follows Power Pages Web API best practices: `/_api/` endpoints, dual token headers, `@odata.bind` for lookups, explicit `$select` (never `*`), formatted value annotations, exponential backoff retry, and 8-minute token TTL. Handles one table per invocation — invoke separately for multiple tables. -- `webapi-permissions`: Read-only agent that analyzes site code, discovers existing web roles and table permissions, queries Dataverse for table columns, and proposes a complete Web API permissions plan (table permissions + site settings). Checks for `.powerpages-site` folder to verify site deployment. Renders a Mermaid flowchart showing web roles → table permissions → tables visually in the browser via Playwright. Never uses `*` for Web API field settings — always lists specific columns. Does NOT create any YAML files — purely advisory. The main conversation uses its output to create table permission and site setting files. +- `table-permissions-architect`: Read-only agent that analyzes site code, discovers existing web roles and table permissions, and proposes a table-permissions plan (web roles → table permissions with CRUD flags and scopes) rendered as a Mermaid flowchart. Checks for `.powerpages-site` folder to verify site deployment. Presents the plan via plan mode; after approval, creates web role and table-permission YAML files using deterministic scripts. Supports an **AI-only read posture** (invoked transitively by `/add-ai-webapi` via `/integrate-webapi`) that proposes `read: true` only, with Parent scope + `appendTo` for `$expand` targets. Invoked by `/integrate-webapi` and `/audit-permissions`. +- `webapi-settings-architect`: Read-only agent that queries Dataverse for exact column LogicalNames (case-sensitive) and proposes `Webapi//enabled` and `Webapi/
/fields` site settings. Never uses `*` for field settings except for aggregate OData queries — always lists specific columns. Presents the plan via plan mode; after approval, creates site-setting YAML files using `create-site-setting.js`. Supports the **AI-only read posture** (minimal fields list: no primary key, only `__value` lookup read forms). Invoked by `/integrate-webapi`. +- `ai-webapi-integration`: Implementation agent that creates production-ready generative-AI summarization service code for a Power Pages SPA site — Search Summary (`/_api/search/v1.0/summary`) and Data Summarization (`/_api/summarization/data/v1.0/...`). Uses raw `fetch` (never the OData wrapper), attaches the `__RequestVerificationToken` CSRF header, groups all functions in a single `aiSummaryService.*` file, emits a framework-idiomatic wrapper (React hook / Vue composable / Angular service / Astro util), and wires real UI call sites with loading/error/content/empty branches, citation rendering, and a safe-markdown renderer. Invoked **sequentially per target** by `/add-ai-webapi` (every target shares the one service file, so parallel runs would conflict). +- `ai-webapi-settings-architect`: Read-only agent that proposes the three Layer-3 summarization settings — `Summarization/Data/Enable`, per-prompt `Summarization/prompt/`, and `Summarization/Data/ContentSizeLimit` (mandatory `200000` for list summaries). Cross-checks that Layer 1/2 prerequisites (`Webapi/
/*`, table permissions) exist for every summarised table and `$expand` target. Presents the plan via plan mode; after approval, creates site-setting YAMLs (script path, or hand-written block-literal YAML for long/complex prompts). Invoked by `/add-ai-webapi` Phase 6. ### Skills @@ -136,7 +150,8 @@ User-invocable via `/power-pages:`: - `activate-site`: 5-step workflow — verify prerequisites (PAC CLI auth + Azure CLI token + cloud-aware API URL resolution + activation status check via shared script), gather parameters (site name, subdomain, website record ID), confirm with user, activate & poll via `skills/activate-site/scripts/activate-site.js`, present summary with site URL. - `add-seo`: 7-step workflow — verify site exists, gather SEO config (production URL, exclusions, meta description), plan & approve, create robots.txt, generate sitemap.xml from discovered routes, add meta tags (title, description, viewport, Open Graph, Twitter Card, favicon) to index.html, verify via Playwright & commit. - `create-webroles`: 6-step workflow — verify `.powerpages-site/web-roles/` exists (redirect to deploy-site if missing), discover existing roles, determine new roles needed, create web role YAML files with UUIDs from shared `scripts/generate-uuid.js`, verify web roles (validate files, UUIDs, uniqueness constraints), review & prompt deployment via deploy-site skill. -- `integrate-webapi`: 7-step workflow — verify site exists, use Explore agent to analyze code and identify tables needing Web API integration, review plan with user, invoke `webapi-integration` agent per table to create API client/types/services/hooks, verify integrations (validate all files exist, project builds), invoke `webapi-permissions` agent to configure table permissions and site settings, review & deploy via `deploy-site` skill. +- `integrate-webapi`: 7-step workflow — verify site exists, use Explore agent to analyze code and identify tables needing Web API integration, review plan with user, invoke `webapi-integration` agent per table to create API client/types/services/hooks, verify integrations (validate all files exist, project builds), invoke `table-permissions-architect` and `webapi-settings-architect` agents (in parallel) to configure table permissions and site settings, review & deploy via `deploy-site` skill. Supports an `[AI-READ-ONLY]` sentinel that hardens the flow to read-only when invoked by `/add-ai-webapi`. +- `add-ai-webapi`: 8-phase workflow — verify site/deployment, Explore-agent scan for search/data summarization candidates, review plan with user, **delegate Layer 1/2** (Web API site settings + table permissions) to `/integrate-webapi` in AI-only read mode and to `/create-webroles`, invoke `ai-webapi-integration` agent **sequentially per target** to create the summarization service + framework wrapper + UI wiring, invoke `ai-webapi-settings-architect` for Layer 3 (`Summarization/*` settings), verify (header-contract grep, `$select` grep, build, validator), review & deploy. This skill owns **Layer 3 only** and delegates everything else. Validator: `skills/add-ai-webapi/scripts/validate-ai-webapi.js`. AI summarization APIs are a **preview** feature gated by a three-level admin hierarchy. - `setup-auth`: 8-step workflow — verify prerequisites (site deployed + web roles), gather auth requirements and plan, create auth service with Entra ID login/logout (anti-forgery token + form POST), create authorization utilities (role checking), create auth UI (AuthButton component), apply role-based access control to components, verify auth setup (validate files, build, auth UI renders), create `ProfileRedirectEnabled` site setting and deploy. - `setup-solution`: 7-step workflow — verify prerequisites, gather publisher/solution configuration (publisher prefix is irreversible — requires explicit confirmation), check existing publishers/solutions to avoid duplicates, create publisher + solution via OData API, add Power Pages website and web role components via `AddSolutionComponent`, verify components and write `.solution-manifest.json`, present summary. Reuses `references/solution-api-patterns.md`. - `export-solution`: 7-step workflow — verify prerequisites, identify solution (from `.solution-manifest.json` or user input), confirm managed vs unmanaged export (irreversible choice), trigger `ExportSolutionAsync`, poll via `scripts/poll-async-operation.js`, download and decode solution zip via `DownloadSolutionExportData`, verify zip contains `Solution.xml`. Reuses `scripts/poll-async-operation.js` and `references/solution-api-patterns.md`. @@ -145,7 +160,7 @@ User-invocable via `/power-pages:`: - `setup-pipeline`: 7-phase workflow — detect project context (`powerpages.config.json`, `.solution-manifest.json`, `pac env who`, `pac env list`, `RetrieveSetting('DefaultCustomPipelinesHostEnvForTenant')` on dev env to auto-discover host environment), select platform (Power Platform Pipelines = full; GitHub/ADO = coming soon), confirm pipeline configuration with auto-filled values (pipeline name, host env URL, target environments), run preflight checks (Pipelines installed, solution exists, no name conflict), create `deploymentenvironments` records for source + each target (poll `validationstatus` until Succeeded), create `deploymentpipelines` record + `$ref` associate source env (relative path + `@odata.context`) + create `deploymentstages` per target, verify and write `docs/alm/last-pipeline.json` + `docs/pipeline-setup.md` + commit. Uses `references/cicd-pipeline-patterns.md` for all HAR-confirmed API patterns. - `deploy-pipeline`: 8-phase workflow — verify prerequisites (`docs/alm/last-pipeline.json`, az login, host env token), select target stage (from stages in `docs/alm/last-pipeline.json`; warn if last deploy failed), **pre-flight check on the target env's `blockedattachments` setting** via `fix-blocked-attachments.js --dry-run` (Phase 2.5, Power Pages projects only — prompts the user to unblock `.js`/`.css` proactively when they're on the env's blocklist, saving the ~50-75 min wasted import for sites with thousands of bundle chunks; complementary to the reactive Phase 7.6 handler), resolve pipeline info via `RetrieveDeploymentPipelineInfo` (v9.1) to get `SourceDeploymentEnvironmentId` and available artifacts, create `deploymentstageruns` record + call `ValidatePackageAsync` (204) + poll `operation` field until not `200000201` (surface `validationresults` issues), optionally PATCH `deploymentsettingsjson` for env var / connection reference overrides, **final deploy consent gate at Phase 6.0** (explicit `Deploy now / Cancel` `AskUserQuestion` before either `DeployPackageAsync` or the `pac pipeline deploy` fallback — closes a gap where Phase 5 → Phase 6.1 could fire without a final confirmation when validation passes cleanly), call `DeployPackageAsync` + poll `stagerunstatus` until terminal (handle approval gates with user pause), write `docs/alm/last-deploy.json` + present deployment summary. - `force-link-environment`: 6-phase workflow — verify prerequisites (Azure CLI token for the target host, PAC CLI auth) and ground in Microsoft Learn (`custom-host-pipelines#using-force-link…`), identify host env URL (from `docs/alm/last-host-check.json`, `docs/alm/last-pipeline.json`, or user input) and source dev env's BAP env GUID, resolve or create the `deploymentenvironments` record on the new host (re-querying by `environmentid` to recover the record ID when `create-deployment-environment.js` throws on the "already associated" validation failure), require explicit `AskUserQuestion` consent for the destructive cross-host stamp move (makers in the previous host lose pipeline access for this env; previous host's record is left with stale `validationstatus`; reversible by re-running from the previous host), call `scripts/lib/force-link-environment.js` to POST `ManageEnvironmentStamp` + re-poll `validationstatus` until Succeeded, write `docs/alm/last-force-link.json` marker. Auto-fix entry point for Pattern 15 in `references/deployment-error-catalog.md`. -- `plan-alm`: 8-phase orchestrator workflow — detect project state (powerpages.config.json, existing manifests, pac env who), gather ALM strategy via branched question flow (PP Pipelines or Manual export/import path), generate HTML ALM plan (docs/alm-plan.html with pipeline diagram and execution checklist), get user approval, then execute: setup-solution (conditional), setup-pipeline or export-solution (path-dependent), deploy-pipeline or import-solution per stage, finalize with HTML status update and git commit. +- `plan-alm`: 4-phase **planner** workflow — detect project state (powerpages.config.json, existing manifests, pac env who), gather ALM strategy via branched question flow (PP Pipelines or Manual export/import path), generate HTML ALM plan (docs/alm-plan.html with pipeline diagram and a recommended-execution checklist), then save it (Approved or Draft) and commit. **It does not execute any deployment.** The user runs the individual ALM skills afterward — `setup-solution`, `setup-pipeline`/`export-solution`, `deploy-pipeline`/`import-solution`, `activate-site`, `test-site` — each of which detects the plan (Phase 0 gate), proceeds, and refreshes the plan on completion (via `refresh-alm-plan-data.js`, which also reports the next recommended step). This keeps `plan-alm` safe under autopilot: it never triggers an irreversible action. For small mid-cycle changes (one file, one snippet, one site setting) that previously used a separate hotfix solution: instead, run `setup-solution` in sync mode to adopt the modified components into the existing base solution, bump the solution version, and use `deploy-pipeline` to ship. This keeps a single solution lineage (cleaner audit trail, simpler dependency management) and avoids solution sprawl. Power Platform Pipelines computes incremental imports internally, so re-deploying the base after a small fix is fast. @@ -153,25 +168,27 @@ Skills are defined in `SKILL.md` files with YAML frontmatter (name, description, ### Hooks -Hook registration is centralized in `hooks/hooks.json` — a single PostToolUse hook (matcher `Skill`) runs `hooks/run-skill-posttool-validation.js` after every Skill tool call. The runner consults the `TRACKED_SKILLS` map in `scripts/lib/powerpages-hook-utils.js`, looks up the validator for the skill that just completed, and invokes it with the current cwd. +Hook registration is centralized in `hooks/hooks.json` — a single PostToolUse hook (matcher `Skill`) runs `hooks/run-skill-posttool-validation.js` after every Skill tool call. The runner derives tracked skills directly from `skills/*/SKILL.md` via `scripts/lib/powerpages-hook-utils.js`, looks up an optional `skills//scripts/validate*.js` validator for the skill that just completed, and invokes it with the current cwd. + +**ALM plan reconcile backstop (auto-heal).** After any **ALM plan skill** completes (`powerpages-hook-utils.js → ALM_PLAN_SKILLS` / `isAlmPlanSkill`) and a `docs/.alm-plan-data.json` exists in the cwd, the runner also `spawnSync`s `refresh-alm-plan-data.js --reconcile --render`. The `refresh-alm-plan-data.js` calls in each SKILL.md are advisory — silently dropped on session fragmentation, manual execution, or oversight — so the reconcile *performs* any refresh whose marker (`docs/alm/last-*.json`) is newer than the plan. This is **best-effort and non-blocking**: it never changes the hook's exit code, honors `.alm-deferred`, and is idempotent. Because it fires on *any* ALM skill (not just the marker's writer), a skip in skill A is healed when the next ALM skill (B) completes. Skills keep their explicit per-phase refresh calls as defense-in-depth + immediate render; the hook is the backstop. To wire a new skill into validation: 1. Write the validator at `skills//scripts/validate-.js` using the `runValidation((cwd) => { ... })` pattern from `scripts/lib/validation-helpers.js`. -2. Register the skill in `TRACKED_SKILLS` (in `scripts/lib/powerpages-hook-utils.js`) with its `validatorScript` path. -3. Add test coverage in `scripts/tests/powerpages-hook-utils.test.js` so an unregistered skill is caught in CI. +2. No manual tracked-skill registration is needed. Any folder with `skills//SKILL.md` is automatically tracked for telemetry and hook detection. +3. Add or update test coverage in `scripts/tests/powerpages-hook-utils.test.js` if you introduce a new validator naming pattern. -Skills currently registered with command-backed validators: `activate-site`, `add-cloud-flow`, `add-seo`, `add-server-logic`, `audit-permissions`, `configure-env-variables`, `create-site`, `create-webroles`, `deploy-pipeline`, `ensure-pipelines-host`, `export-solution`, `force-link-environment`, `import-solution`, `integrate-webapi`, `plan-alm`, `setup-auth`, `setup-datamodel`, `setup-pipeline`, `setup-solution`. `add-sample-data` and `test-site` are tracked without command validators (no artifacts to verify). `diagnose-deployment` is intentionally not tracked — it's read-only and produces no artifacts to verify. +All skill folders are tracked. Skills without a `scripts/validate*.js` file are tracked for telemetry/detection but skip validation. **Anti-patterns** (see `PLUGIN_DEVELOPMENT_GUIDE.md` for the rationale): do not add `hooks: Stop:` blocks to individual SKILL.md frontmatter — they duplicate the centralized PostToolUse hook and fire too often. Do not use `type: prompt` Stop hooks for skill-completion checks — they create runaway forced-continuation loops. ### Shared Scripts -Shared utility scripts live at `scripts/` and are referenced by multiple skills and agents via `${CLAUDE_PLUGIN_ROOT}/scripts/`. +Shared utility scripts live at `scripts/` and are referenced by multiple skills and agents via `${PLUGIN_ROOT}/scripts/`. - `generate-uuid.js`: Generates a random UUID v4. Self-contained, no dependencies. Used by `create-webroles` and the main agent when creating table permission / site setting files from the `webapi-permissions` agent plan. - `update-skill-tracking.js`: Updates skill usage tracking site settings. Takes `--projectRoot`, `--skillName`, and `--authoringTool` args. The agent passes its own name as `--authoringTool` (e.g., `ClaudeCode`, `GitHubCopilot`). Creates/increments a per-skill counter (`Site-AI-.sitesetting.yml`) and records the authoring tool (`Site-AI-AuthoringTool.sitesetting.yml`). Exits silently if `.powerpages-site/site-settings/` does not exist. Used by every user-invocable skill (each skill calls it in its final phase per the skill-tracking convention). -- `check-activation-status.js`: Checks whether a Power Pages site is already activated (provisioned) in the environment. Takes `--projectRoot` arg. Reads `siteName` from `powerpages.config.json`, looks up `websiteRecordId` via `pac pages list`, queries the Power Platform GET websites API, and matches by both `websiteRecordId` and `name`. Outputs JSON: `{ activated: true/false, siteName, websiteRecordId, websiteUrl }` or `{ error }`. Used by `deploy-site` and `activate-site`. +- `check-activation-status.js`: Checks whether a Power Pages site is already activated (provisioned) in the environment. Takes `--projectRoot` arg. **Site-identity resolution (`resolveSiteIdentity()`, exported + injectable for tests): (1) `powerpages.config.json` (code/SPA sites) → `siteName` + optional `websiteRecordId`; (2) else `.powerpages-site/website.yml` (declarative/data-model sites) → `name`→`siteName`, `id`→`websiteRecordId`; (3) `pac pages list` ONLY when the GUID is still unknown** — declarative sites (and code sites whose config included the GUID) skip the `pac pages list` exec entirely. Then queries the Power Platform GET websites API and matches by both `websiteRecordId` and `name`. Outputs JSON: `{ activated: true/false, siteName, websiteRecordId, websiteUrl }` or `{ error }`. The CLI flow is guarded by `require.main === module`; `module.exports = { resolveSiteIdentity, getWebsites }`. Used by `deploy-site` and `activate-site`. - `poll-async-operation.js`: Polls a Dataverse `asyncoperations` record until it reaches a terminal state (Succeeded/Failed/Canceled) or times out. Args: `--asyncJobId`, `--envUrl`, `--token` (optional, refreshed via Azure CLI if omitted), `--intervalMs` (default 5000), `--maxAttempts` (default 60). Outputs JSON status. Used by `export-solution` and `import-solution`. - `encode-solution-file.js`: Base64-encodes a solution zip file for use in Dataverse OData request bodies (`ImportSolutionAsync`, `StageSolution`). Args: `--zipPath`. Outputs `{ encoded, fileSizeBytes, fileName }`. Used by `import-solution`. - `parse-deployment-errors.js`: Parses PAC CLI stderr output or OData error JSON into structured findings array. Each finding has `{ patternId, type, severity, message, rawMatch, autoFixAvailable, suggestedFix }`. Reads from `--input`, `--file`, or stdin. Used by `diagnose-deployment`. @@ -180,17 +197,23 @@ Shared lib modules live at `scripts/lib/` and are imported by other scripts via #### ALM Prerequisites & Context -- `scripts/lib/verify-alm-prerequisites.js`: Verifies all prerequisites for ALM skills — PAC CLI installed + authenticated (`pac env who`), Azure CLI installed + logged in, Dataverse API reachable (`WhoAmI`). Args: `--envUrl` (opt, overrides env from PAC CLI), `--require-manifest` (fails if `.solution-manifest.json` not found). Output: `{ envUrl, token, userId, organizationId, tenantId }`. Exit 0 on success, exit 1 on any failure. Used by `setup-solution`, `export-solution`, `import-solution`, `setup-pipeline`, `deploy-pipeline`, `plan-alm`. -- `scripts/lib/detect-project-context.js`: Reads Power Pages project context files from the project root — `powerpages.config.json`, `.solution-manifest.json`, and `.datamodel-manifest.json`. Args: `--projectRoot` (opt, auto-discovered from cwd if omitted). Output: `{ projectRoot, siteName, websiteRecordId, environmentUrl, solutionManifest, datamodelManifest }`. Exit 0 on success, exit 1 if `powerpages.config.json` not found. +- `scripts/lib/verify-alm-prerequisites.js`: Verifies all prerequisites for ALM skills — PAC CLI installed + authenticated (`pac env who`), Azure CLI installed + logged in, Dataverse API reachable (`WhoAmI`). Args: `--envUrl` (opt, overrides env from PAC CLI), `--require-manifest` (fails if `.solution-manifest.json` not found), `--expectedEnvUrl` (opt — **env-drift guard**: assert the resolved env matches this origin and HARD-STOP on mismatch). Output: `{ envUrl, token, userId, organizationId, tenantId }`. Exit 0 on success, exit 1 on any failure. **`--expectedEnvUrl` is the recommended guard for any ALM skill that runs against the project's source/dev env**: since `getEnvironmentUrl()` now parses PAC 2.8.x's `Org URL:` successfully, a drifted PAC context resolves silently instead of failing loudly (the old parse-miss had been an accidental safety net), so an ALM op could target the wrong environment (e.g. PROD). Skills pass the project's env URL (from `.solution-manifest.json` top-level `environmentUrl` / `powerpages.config.json` `environmentUrl` / the approved plan's source env) so a mismatch stops the run before any token/write. Prefer this over pinning `--envUrl`, which only redirects the Dataverse-API calls while later PAC-CLI ops (`pac pipeline deploy`, `pac env select`) still follow the ambient context. Used by `setup-solution`, `export-solution`, `import-solution`, `setup-pipeline`, `deploy-pipeline`, `plan-alm`. +- `scripts/lib/detect-project-context.js`: Reads Power Pages project context from the project root. The `siteType` discriminator is the **build axis** — code/SPA vs declarative (design-studio) site — NOT the Dataverse data-model axis (a declarative site can be on the standard OR enhanced data model; both download to a `.powerpages-site/` tree). `siteType: "declarative"` is the declarative bucket (it was historically labeled `"data-model"`; that value is now the legacy alias — nothing branches on the literal, so older plan-data carrying `"data-model"` stays equivalent). Resolves identity in order: (1) `powerpages.config.json` → `siteType: "code"` (code/SPA sites); (2) `.powerpages-site/` → `siteType: "declarative"` (declarative design-studio sites — standard or enhanced data model — which have **no** `powerpages.config.json`). The **authoritative declarative marker is the `.powerpages-site/.portalconfig/` directory** (only declarative sites have it); `website.yml` is the identity source (`id`→`websiteRecordId`, `name`→`siteName`) but is NOT a reliable declarative signal alone because **both** site types carry it. `environmentUrl: null` for declarative sites (no env URL in the local files — callers re-confirm via `pac env who`). Also reads `.solution-manifest.json` and `.datamodel-manifest.json`. Args: `--projectRoot` (opt). Output: `{ projectRoot, siteType, siteName, websiteRecordId, environmentUrl, solutionManifest, datamodelManifest }`. Exit 0 on success, exit 1 only if neither `powerpages.config.json` nor a `.powerpages-site/` (`.portalconfig/`/`website.yml`) marker is found. Note: `findProjectRoot` (in `validation-helpers.js`) likewise treats a `.powerpages-site/` directory as a project-root marker. - `scripts/lib/alm-paths.js`: Single source of truth for ALM artifact paths. Exports `ALM_DIR` (always `docs/alm`), `FILE_NAMES` (frozen object mapping logical key → filename for all 14 ALM artifacts), `almDir(projectRoot) → path`, `almPath(projectRoot, key) → path`, `ensureAlmDir(projectRoot) → path` (mkdir -p idempotent). Every ALM-only state file (5 plan/decision JSONs + 9 `last-*.json` skill-run markers including `last-export.json`) writes under `/docs/alm/`. **Always resolve through this helper** — never inline a raw `docs/alm/...` path in a script. Files intentionally NOT moved here (and not in `FILE_NAMES`): `.solution-manifest.json`, `.datamodel-manifest.json`, `.alm-config.json`, `.alm-deferred`, `deployment-settings.json`. Adding a new ALM marker means adding its key + filename to `FILE_NAMES` first; `almPath` throws on unknown keys to catch typos at call-site. -- `scripts/lib/check-alm-plan.js`: Phase 0 gate helper used by every ALM skill to detect (a) whether an ALM plan exists for this project, (b) whether the user has explicitly deferred ALM via the `.alm-deferred` marker, and (c) whether an existing plan is stale (the source solution was modified after the plan was generated). Args: `--projectRoot`, `--envUrl` (opt — required for staleness check), `--token` (opt), `--solutionId` (opt — required for staleness check). Output: `{ exists, deferred, deferral, planPath, htmlPath, stale, staleness: { reason, detail }, generatedAt, planStatus, solution: {...} }`. Without env/solution context the helper does an existence-only check; with them it queries Dataverse for `solutions(solutionId)?$select=modifiedon` and compares against `planData.generatedAt`. Used by `setup-solution`, `setup-pipeline`, `deploy-pipeline`, `export-solution`, `import-solution`, `configure-env-variables`, `ensure-pipelines-host`, `force-link-environment` Phase 0 gates — the "fail closed when no plan" pattern. +- `scripts/lib/check-alm-plan.js`: Phase 0 gate helper used by every ALM skill to detect (a) whether an ALM plan exists for this project, (b) whether the user has explicitly deferred ALM via the `.alm-deferred` marker, and (c) whether an existing plan is stale (the source solution was modified after the plan was generated). Args: `--projectRoot`, `--envUrl` (opt — required for staleness check), `--token` (opt), `--solutionId` (opt — required for staleness check). Output: `{ exists, deferred, deferral, planPath, htmlPath, stale, staleness: { reason, detail }, generatedAt, planStatus, solution: {...} }`. Without env/solution context the helper does an existence-only check; with them it queries Dataverse for `solutions(solutionId)?$select=modifiedon` and compares against `planData.generatedAt`. Used by `setup-solution`, `setup-pipeline`, `deploy-pipeline`, `export-solution`, `import-solution`, `configure-env-variables`, `ensure-pipelines-host`, `force-link-environment` Phase 0 gates — the "fail closed when no plan" pattern. **PLAN_STATUS lifecycle — promotes `Approved` → `In Execution`:** plan-alm is plan-only and leaves the plan `Approved`; this helper performs the `Approved` → `In Execution` transition (and writes the first heartbeat) the **first time an execution skill's Phase 0 runs** — it is the only thing that sets `In Execution`, so without it the heartbeat/active-chain machinery (multi-hour-deploy `stale-heartbeat` reclassification) never engages. Gated on heartbeat-write: read-only callers pass `--no-heartbeat` (plan-alm's own deferral check, audits, tests) and are never promoted. The terminal `In Execution` → `Completed` transition is owned by `refresh-alm-plan-data.js` (completion evaluator). +- `scripts/lib/set-plan-status.js`: **The single deterministic owner of the creation-time `Draft` / `Approved` write** — the one PLAN_STATUS transition that used to be done by hand-authored `Edit`s in plan-alm Phase 4 (to the HTML spans *and* the JSON), with no helper. Because the badge + `approved-by` / `approval-date` spans are re-derived from `docs/.alm-plan-data.json` on every render, the old manual HTML Edit was non-durable (reverted on the next refresh) and a partial write left the plan "approver recorded but PLAN_STATUS=Draft" — stuck forever, since `check-alm-plan.js` only promotes from `Approved`. This helper writes `PLAN_STATUS` + `PLAN_MODE` + `APPROVED_BY` + `APPROVAL_DATE` **together** (atomic temp+rename) and optionally re-renders (reuses `refresh-alm-plan-data.js → findRendererPath`/`invokeRenderer`). Enforced invariants: only `Draft` / `Approved` are settable here (`In Execution` is owned by `check-alm-plan.js`, `Completed` by `refresh-alm-plan-data.js`); `Approved` **requires** a non-empty `--approver`; `Draft` **clears** the approver fields; a plan already `In Execution` / `Completed` is **not** re-drafted without `--force`. Args: `--projectRoot`, `--status Draft|Approved`, `--approver`, `--approvalDate` (opt — defaults to now), `--force`, `--render`, `--rendererPath` (opt). Output: `{ ok, previousStatus, status, mode, approver, approvalDate, rendered }`. Called by plan-alm Phase 4 (both save options) and the Phase 1 step-0b in-place Draft→Approved fast-path. The `validate-plan-alm.js` consistency guard blocks the two half-written states (`Draft`+approver, `Approved`+no-approver) for plans created the old way or hand-edited. + - `scripts/lib/resolve-target-solution.js`: Resolves "which solution should this new Dataverse record land in?" Implements the strict 3-step order from the ALM-aware-by-default principle: (1) explicit `--solutionUniqueName` (or equivalent caller arg) wins; (2) `.solution-manifest.json` in the project root; (3) neither → throw `NoSolutionConfiguredError`. **The module NEVER auto-picks from Dataverse** — interactive prompt UX is the caller's responsibility (catch the error, present an `AskUserQuestion` list, re-invoke with `explicit` populated). Callers that need to confirm the solution still exists in Dataverse can pass `verifyExists: true`; the module then enriches the result with `{ solutionId, version, ismanaged }`. Component-creation scripts must require this helper and pass through `--solutionUniqueName` so records land in the user's solution instead of `Default`. #### Solution Splitting Decision Tree (v1.3.0+) - `scripts/lib/alm-thresholds.js`: Central default threshold constants for the split decision tree. Loads optional `.alm-config.json` from project root and merges over defaults. Exports `DEFAULTS`, `DEFAULT_CONFIG`, `loadConfig(projectRoot)`, `classifyTier(value, greenUpperExclusive, yellowUpperExclusive)`, `deepMerge(target, source)`. Used by `estimate-solution-size.js` and `compute-split-plan.js`. -- `scripts/lib/estimate-solution-size.js`: Estimates solution size + component counts by querying Dataverse. Args: `--envUrl`, `--websiteRecordId`, `--token` (opt), `--publisherPrefix` (opt), `--siteName` (opt), `--solutionId` (opt — scopes env var count to the target solution; without it falls back to a publisher-prefix tenant-wide query that overcounts when prefix is shared), `--datamodelManifest` (opt), `--projectRoot` (opt — enables disk cross-check: walks the local build-output directory (`dist/`, `public-output/`, `build/`, `.output/`) and surfaces the byte total). Output: `{ totalSizeMB, componentCountSiteTotal, componentCountSiteActionable, componentCountInSolution, orphansOnSite, tableCount, schemaAttrCount, webFilesAggregateMB, webFilesIndividual[], webFileCount, webFileSampleSize, webFilesDiskMeasuredMB, webFilesDiskMeasuredPath, webFilesDiskFileCount, cloudFlowCount, botCount, envVarCount, envVarCountScope, envVarCountTenantWide, mediaRatio, siteType, tables[], breakdown, estimationMethod, estimationAccuracyPct, truncationSuspected, truncationWarnings, ppcGroundTruthCount }`. Metadata-based estimation with ±15% caveat. Web-file size is measured via stratified sample (first 50 + middle 50 + last 50, cap 150) scaled to full count. Disk fields are null unless `--projectRoot` was passed AND a build-output directory was found. Truncation canaries fire when Dataverse pagination disagrees with `@odata.count`, when ppcs land on a page-size boundary, when sampled average bytes/file < 1 KB at scale, or when the disk total exceeds the Dataverse total by >2× — any signal flips `truncationSuspected: true` with a per-cause `truncationWarnings[]` entry. Used by `plan-alm` Phase 1 Step 10. -- `scripts/lib/compute-split-plan.js`: Runs the split decision tree against a size-estimate blob. Args: `--estimate `, `--projectRoot` (opt — for `.alm-config.json` overrides), `--siteName` (opt), `--publisherPrefix` (opt). Output: `{ sizeAnalysis, assetAdvisory, splitStrategy, appliedStrategies, compositeSubPartitioned, proposedSolutions[], recommendations[], truncationSuspected, truncationWarnings }`. Evaluates strategies in priority order: Strategy 3 (Schema Segmentation) → Strategy 1 (Layer Split) → Strategy 2 (Change-Frequency) → Strategy 4 (Config Isolation). Strategy 4 stacks additively. Strategy 1 also runs a composite sub-partition pass: when Core still exceeds the size OR component-count cap after Web Assets are peeled off, Core is replaced with change-frequency-shaped sub-children (`_Foundation`/`_Config`/`_Content`, plus `_Integration` whenever the parent had any flows or bots — coverage takes priority over the `changeFreqMinFlows` heuristic, which governs only the TOP-LEVEL strategy choice). When additive Strategy 4 is firing concurrently (a top-level `_EnvVars` solution), `_Config` drops `Environment Variable` from its componentTypes to avoid double-claim; when it isn't, `_Config` absorbs env vars so they have an owner. Sub-partitioning sets `compositeSubPartitioned: true` and appends `composite-sub-partition` to `appliedStrategies`. `validateSplits` checks BOTH the size AND component-count cap per split (skipping `isFutureBuffer` solutions). Supports `.alm-config.json` overrides including `strategyOverride` to bypass the tree. See `solution-splitting-logic.md` spec in design docs for full logic. +- `scripts/lib/estimate-solution-size.js`: Estimates solution size + component counts by querying Dataverse. Args: `--envUrl`, `--websiteRecordId`, `--token` (opt), `--publisherPrefix` (opt), `--siteName` (opt), `--solutionId` (opt — scopes env var count to the target solution; without it falls back to a publisher-prefix tenant-wide query that overcounts when prefix is shared), `--datamodelManifest` (opt), `--projectRoot` (opt — enables disk cross-check: walks the local build-output directory (`dist/`, `public-output/`, `build/`, `.output/`) and surfaces the byte total). Output: `{ totalSizeMB, componentCountSiteTotal, componentCountSiteActionable, componentCountInSolution, orphansOnSite, tableCount, tableCountScope, schemaAttrCount, webFilesAggregateMB, webFilesIndividual[], webFileCount, webFileSampleSize, webFilesDiskMeasuredMB, webFilesDiskMeasuredPath, webFilesDiskFileCount, cloudFlowCount, botCount, envVarCount, envVarCountScope, envVarCountTenantWide, mediaRatio, siteType, tables[], tableRelationships[], breakdown, estimationMethod, estimationAccuracyPct, truncationSuspected, truncationWarnings, ppcGroundTruthCount }`. **Table discovery is site-referenced, NOT publisher-prefix:** `tableCount`/`tables[]` are scoped to the custom tables the site actually references — its `.powerpages-site/table-permissions/` (+ datamodel manifest) intersected with the env's custom-unmanaged tables (via `resolve-site-tables.js` + `query-metadata.js`). `tableCountScope` ∈ `"site-referenced" | "manifest-only" | "unavailable"` (the last → 0 tables, never an env-wide prefix dump). `--publisherPrefix` now scopes ONLY the env var count, not tables. `tableRelationships[]` are `[a,b]` dependency edges (lookups + N:N, via `query-table-relationships.js`) among the scoped tables, consumed by `compute-split-plan.js` to cluster related tables into the same solution. Metadata-based estimation with ±15% caveat. Web-file size is measured via stratified sample (first 50 + middle 50 + last 50, cap 150) scaled to full count. Disk fields are null unless `--projectRoot` was passed AND a build-output directory was found. Truncation canaries fire when Dataverse pagination disagrees with `@odata.count`, when ppcs land on a page-size boundary, when sampled average bytes/file < 1 KB at scale, or when the disk total exceeds the Dataverse total by >2× — any signal flips `truncationSuspected: true` with a per-cause `truncationWarnings[]` entry. Used by `plan-alm` Phase 1 Step 10. +- `scripts/lib/compute-split-plan.js`: Runs the split decision tree against a size-estimate blob. Args: `--estimate `, `--projectRoot` (opt — for `.alm-config.json` overrides), `--siteName` (opt), `--publisherPrefix` (opt). Output: `{ sizeAnalysis, assetAdvisory, splitStrategy, appliedStrategies, compositeSubPartitioned, proposedSolutions[], recommendations[], truncationSuspected, truncationWarnings }`. Evaluates strategies in priority order: Strategy 3 (Schema Segmentation) → Strategy 1 (Layer Split) → Strategy 2 (Change-Frequency) → Strategy 4 (Config Isolation). **Schema Segmentation is dependency-aware + capacity-bounded:** it builds connected-component clusters from `estimate.tableRelationships` (union-find), then bin-packs whole clusters (never splitting a relationship) into the fewest solutions that keep each under `maxTableCount`/`maxSchemaAttrs` **where possible** — capped at `maxSchemaSplitSolutions` (default 8). This replaced the old one-solution-per-table-name-stem heuristic that produced ~one solution per table. Two cases CAN exceed a per-solution cap, and BOTH raise an `recommendations[]` warning rather than failing silently: (a) an indivisible dependency cluster larger than `maxTableCount` stays whole (oversized-cluster table-count warning); (b) when MORE than `maxSchemaSplitSolutions` independent attr-heavy clusters must share the capped solution count, the FFD least-loaded fallback co-locates clusters and a solution's summed columns exceed `maxSchemaAttrs` (oversized-schema attr-cap warning). The split trigger + thresholds are unchanged — only the packing. Strategy 4 stacks additively. Strategy 1 also runs a composite sub-partition pass: when Core still exceeds the size OR component-count cap after Web Assets are peeled off, Core is replaced with change-frequency-shaped sub-children (`_Foundation`/`_Config`/`_Content`, plus `_Integration` whenever the parent had any flows or bots — coverage takes priority over the `changeFreqMinFlows` heuristic, which governs only the TOP-LEVEL strategy choice). When additive Strategy 4 is firing concurrently (a top-level `_EnvVars` solution), `_Config` drops `Environment Variable` from its componentTypes to avoid double-claim; when it isn't, `_Config` absorbs env vars so they have an owner. Sub-partitioning sets `compositeSubPartitioned: true` and appends `composite-sub-partition` to `appliedStrategies`. `validateSplits` checks BOTH the size AND component-count cap per split (skipping `isFutureBuffer` solutions). Supports `.alm-config.json` overrides including `strategyOverride` to bypass the tree. See `solution-splitting-logic.md` spec in design docs for full logic. +- `scripts/lib/resolve-site-tables.js`: Single source of truth for "which custom tables does this site actually use." `collectReferencedEntityNames({ projectRoot, datamodelManifestPath })` reads `.powerpages-site/table-permissions/*.tablepermission.yml` (`entitylogicalname`, via `powerpages-config.js → loadTablePermissions`) + the datamodel manifest → `{ names:Set, available, sources }`. `scopeCustomTables(referencedNames, customUnmanagedTables)` intersects that set with the env's custom-unmanaged tables. SME-confirmed: table permissions are the complete signal ("if a table is used in the site there will be permissions for it"), so forms/lists are NOT scanned. Used by `estimate-solution-size.js` and `discover-site-components.js` to replace the publisher-prefix table dump. +- `scripts/lib/query-metadata.js`: `queryCustomUnmanagedTables(envUrl, token, makeRequest?)` → `[{ logicalName, metadataId, schemaName, displayName }]` (the single `EntityDefinitions?$filter=IsCustomEntity` query, `IsManaged===false` filtered). Consolidates the formerly-triplicated custom-table query (estimator, discover-site-components, setup-solution). Reuses `odataGetAll` from `validation-helpers.js`. +- `scripts/lib/query-table-relationships.js`: `fetchTableRelationships(envUrl, table, token, makeRequest?)` → `{ oneToMany[], manyToMany[] }`. Extracted from `skills/audit-permissions/scripts/query-table-relationships.js` (now a thin CLI wrapper over this lib) and extended with ManyToMany. OneToMany errors propagate; ManyToMany is best-effort. Used by the estimator to build `tableRelationships[]` and by audit-permissions for relationship-scope validation. +- `scripts/lib/validation-helpers.js` also exports `odataGet(url, token, makeRequest?)` + `odataGetAll(url, token, makeRequest?, maxPages?)` — the shared, injectable OData GET + `@odata.nextLink` pagination used by the new metadata/relationship helpers (avoids each lib rolling its own paginator). #### Solution Management @@ -206,10 +229,11 @@ Shared lib modules live at `scripts/lib/` and are imported by other scripts via - `scripts/lib/link-site-setting-to-env-var.js`: Links an `mspp_sitesetting` record to an `environmentvariabledefinition` via OData PATCH on the v9.0 API (not v9.2). HAR-confirmed: navigation property is `EnvironmentValue@odata.bind`; headers `if-match: *` and `clienthost: Browser` are required (omitting causes 400). Args: `--envUrl`, `--token`, `--siteSettingId`, `--definitionId`, `--schemaName`. Output: `{ ok, verified, siteSettingId, definitionId }`. - `scripts/lib/install-pipelines-app.js`: Installs the Power Platform Pipelines application package on an existing Dataverse env (replaces ensure-pipelines-host Phase 4.B's manual PPAC click-through). Resolution: BAP `applicationPackages` LIST + `/install` POST → 200 sync / 202 + Location poll, with PAC CLI fallback (`pac application install --environment-id ... --application-list msdyn_AppDeploymentAnchor`) on 401/403/5xx. 409 on install POST treated as idempotent (already-installed). Args: `--bapToken`, `--envId`, `--instanceApiUrl` (opt — for verification probe), `--hostToken` (opt), `--no-pac-fallback` (opt), `--correlationId`, `--timeoutSec`, `--apiVersion`, `--bapBase`. Output: `{ status, alreadyInstalled, installPath: 'bap'\|'pac'\|'cached', packageUniqueName, pipelinesSolutionVersion, durationSec, correlationId, pollAttempts, locationHeader, pacFallbackReason }`. - `scripts/lib/discover-env-var-definitions.js`: Enumerates env var definitions matching a publisher prefix and joins each with its bound `mspp_sitesetting` (if any). Used by `plan-alm` Phase 1 Step 10b to populate `planData.envVars[]` with row-level metadata so the rendered plan's Env Variables tab shows schema name, type, default value, and bound site setting per definition (instead of just a count). Args: `--envUrl`, `--publisherPrefix`, `--websiteRecordId`, `--token` (opt). Output: `{ envVars: [{ schemaName, type, defaultValue, siteSetting }], count }`. Degrades gracefully (empty array, exit 0) on auth failure or query errors so the renderer's count-summary fallback can take over. -- `scripts/lib/refresh-alm-plan-data.js`: Updates `docs/.alm-plan-data.json` with post-run state from the marker files written by setup-pipeline / deploy-pipeline / ensure-pipelines-host / test-site / import-solution / activate-site / configure-env-variables / setup-solution / export-solution, then optionally re-renders `docs/alm-plan.html`. Used by plan-alm Phases 6 / 7 / 8 so the rendered Pipelines tab, Validation tab, hostResolution card, env var values matrix, checklist, and risks list reflect actual run state instead of frozen pre-run intent. Args: `--projectRoot`, `--phase` (`setup-solution`/`setup-pipeline`/`configure-env-variables`/`deploy-pipeline`/`export-solution`/`import-solution`/`activate-site`/`test-site`/`finalize`), `--render` (also invoke renderer), `--stageName` (required for `test-site`; preferred for `import-solution`/`activate-site` though both can resolve via marker URL match). Output: `{ ok, phase, dataPath, htmlPath, rendered }`. Returns `ok:false` (soft no-op) when `docs/.alm-plan-data.json` is missing — caller should preserve that file across phases for the helper to work. Plan-alm Phase 3 must NOT delete the file after the initial render — it's read by `check-alm-plan.js` for downstream Phase 0 ALM-plan gates and by this helper for post-run refreshes. **Cross-cutting behaviors**: (a) `setStepStatus` flips the matching entry in `planData.steps[]` to `completed` (or `failed` when the phase's marker indicates failure) — case-insensitive keyword match + stage filter, respects `skip: true`, never regresses completed→pending; (b) `deploy-pipeline` AND `configure-env-variables` both backfill `planData.envVars[i].values{}` from the project root's `deployment-settings.json` so the rendered plan's "Values by Environment" matrix auto-populates (accepts both top-level-stage and nested-`stages` shapes; `SchemaName`/`Value` and camelCase variants; never overwrites a populated cell — manual override wins); (c) `configure-env-variables` and `setup-solution` both re-ingest `docs/alm/last-env-vars.json` (when present) so freshly-created definitions appear in `planData.envVars[]` and `plannedEnvVarCount` zeros out; (d) `export-solution` ingests `docs/alm/last-export.json` into `planData.manualMeta.lastExport` (all 10 marker fields: solutionUniqueName/solutionId/previousVersion/version/managed/sourceEnvironmentUrl/zipPath/fileSizeBytes/asyncOperationId/exportedAt) so the Manual-path tab can show the most recent export. Marker absence is a silent step-sync-only no-op (no `manualMeta.lastExport: null` row in the rendered plan); (e) `deploy-pipeline` ingests the `batchValidation` block from `last-deploy.json` into `planData.pipelineMeta.lastDeploy.batchValidation` (totalSolutions/succeeded/failed/pendingApproval/timedOut/elapsedSeconds/perSolutionStageRunIds) so the rendered plan can show the Phase 3.6 parallel-validation outcome distinct from the serial deploy outcome. Explicitly set to `null` for single-solution / legacy v2 deploys so renderers can branch on it; legacy `elapsedSecondsApprox` field name is accepted and normalized to `elapsedSeconds` on ingest. +- `scripts/lib/refresh-alm-plan-data.js`: Updates `docs/.alm-plan-data.json` with post-run state from the marker files written by setup-pipeline / deploy-pipeline / ensure-pipelines-host / test-site / import-solution / activate-site / configure-env-variables / setup-solution / export-solution, then optionally re-renders `docs/alm-plan.html`. Driven by the execution skills' final-phase refresh (and the PostToolUse `--reconcile` backstop) — NOT by plan-alm, which is now a plan-only 4-phase planner that only renders the initial plan in Phase 3 — so the rendered Pipelines tab, Validation tab, hostResolution card, env var values matrix, checklist, and risks list reflect actual run state instead of frozen pre-run intent. Args: `--projectRoot`, `--phase` (`setup-solution`/`setup-pipeline`/`configure-env-variables`/`deploy-pipeline`/`export-solution`/`import-solution`/`activate-site`/`test-site`/`ensure-pipelines-host`/`finalize`) **OR `--reconcile`** (mutually exclusive with `--phase`), `--render` (also invoke renderer), `--stageName` (required for `test-site`; preferred for `import-solution`/`activate-site` though both can resolve via marker URL match). Output: `{ ok, phase, dataPath, htmlPath, rendered }`. Returns `ok:false` (soft no-op) when `docs/.alm-plan-data.json` is missing — caller should preserve that file across phases for the helper to work. Plan-alm Phase 3 must NOT delete the file after the initial render — it's read by `check-alm-plan.js` for downstream Phase 0 ALM-plan gates and by this helper for post-run refreshes. **Cross-cutting behaviors**: (a) `setStepStatus` flips the matching entry in `planData.steps[]` to `completed` (or `failed` when the phase's marker indicates failure) — case-insensitive keyword match + stage filter, respects `skip: true`, never regresses completed→pending; (b) `deploy-pipeline` AND `configure-env-variables` both backfill `planData.envVars[i].values{}` from the project root's `deployment-settings.json` so the rendered plan's "Values by Environment" matrix auto-populates (accepts both top-level-stage and nested-`stages` shapes; `SchemaName`/`Value` and camelCase variants; never overwrites a populated cell — manual override wins); (c) `configure-env-variables` and `setup-solution` both re-ingest `docs/alm/last-env-vars.json` (when present) so freshly-created definitions appear in `planData.envVars[]` and `plannedEnvVarCount` zeros out; (d) `export-solution` ingests `docs/alm/last-export.json` into `planData.manualMeta.lastExport` (all 10 marker fields: solutionUniqueName/solutionId/previousVersion/version/managed/sourceEnvironmentUrl/zipPath/fileSizeBytes/asyncOperationId/exportedAt) so the Manual-path tab can show the most recent export. Marker absence is a silent step-sync-only no-op (no `manualMeta.lastExport: null` row in the rendered plan); (e) `deploy-pipeline` ingests the `batchValidation` block from `last-deploy.json` into `planData.pipelineMeta.lastDeploy.batchValidation` (totalSolutions/succeeded/failed/pendingApproval/timedOut/elapsedSeconds/perSolutionStageRunIds) so the rendered plan can show the Phase 3.6 parallel-validation outcome distinct from the serial deploy outcome. Explicitly set to `null` for single-solution / legacy v2 deploys so renderers can branch on it; legacy `elapsedSecondsApprox` field name is accepted and normalized to `elapsedSeconds` on ingest. **`ensure-pipelines-host` phase**: host-only update of `planData.hostResolution` from `last-host-check.json` (drops NoHost risks) WITHOUT touching `pipelineMeta` or the `Setup pipeline` step — for when the host was resolved but the pipeline doesn't exist yet. **`--reconcile` mode**: the enforcement backstop — scans the `last-*.json` markers and, for each one newer than `docs/.alm-plan-data.json` (a skipped refresh), applies the mapped phase (`MARKER_TO_PHASE`; `lastPipeline`→setup-pipeline supersedes the host-only phase; `lastEnvVars`→configure-env-variables if `deployment-settings.json` exists else setup-solution) against a single loaded planData, writes once, renders once. Honors `.alm-deferred`, soft no-op when no plan, idempotent. Output `{ ok, reconciled:[phases healed], failed:[{phase,error}], rendered }` — a phase whose refresh throws (e.g. a marker schema it can't parse) is captured in `failed` (and written to stderr) instead of being silently swallowed, while the remaining phases still heal. Invoked by the PostToolUse hook after every ALM skill (see Hooks). **Completion evaluator (`In Execution` → `Completed`):** after every phase's step-sync (both `refresh()` and `reconcile()`), `evaluatePlanCompletion` flips `PLAN_STATUS` to `Completed` + stamps `COMPLETED_AT` once every non-`skip` step is `completed` and none is `failed`. This is what makes the LAST execution skill terminate the plan automatically — no skill calls `--phase finalize` (the explicit `finalize` phase / `refreshFinalize` exists but nothing invoked it, so the lifecycle previously never reached `Completed`). Only advances from `In Execution` (the normal post-promotion state — see `check-alm-plan.js`) or `Approved` (defensive fallback); never regresses a `Draft` or already-`Completed` plan, and a `failed` step blocks completion so a failed deploy can't look "done". #### PP Pipelines +- `scripts/lib/list-environments.js`: Enumerates the Dataverse environments the signed-in PAC user can access, as JSON, for `ENV_LIST` pre-fill (plan-alm Phase 1 Step 5, setup-pipeline, ensure-pipelines-host "Other (paste URL)" prompts). **Why it exists:** the skills used to run `pac env list --output json`, which is INVALID on current PAC CLI (verified 2.8.1 — `pac env list` accepts only `--filter` and errors on `--output`), so the JSON pre-fill silently never worked. This helper runs the plain `pac env list` and parses its table (anchored on the env GUID + https URL + unique-name tokens, so display names with spaces survive). `pac admin list --json` was rejected as the source — it's admin-only and tenant-wide, the wrong scope for a per-user pre-fill. Exports `parseEnvList(stdout)` (pure, tested) + `listEnvironments()`. CLI prints a JSON array of `{ displayName, environmentId, environmentUrl, uniqueName, active }`; prints `[]` and exits 0 on any failure (unauthenticated PAC, parse miss) so callers degrade to manual entry. Match envs by `environmentUrl` origin. - `scripts/lib/discover-pipelines-host.js`: Discovers the tenant-level default Power Platform Pipelines host environment URL by calling `RetrieveSetting('DefaultCustomPipelinesHostEnvForTenant')` on the dev/source environment. Args: `--envUrl`, `--token`, `--userId`. Output: `{ found, hostEnvUrl }`. Exit 0 (including when not found); exit 1 on error. - `scripts/lib/create-deployment-environment.js`: Creates a `deploymentenvironments` record in the Pipelines host environment using the **unprefixed** field schema (`name`, `environmentid`, `environmenttype`), then polls `validationstatus` until Succeeded (`200000001`) or Failed (`200000002`). Args: `--hostEnvUrl`, `--token`, `--name`, `--bapEnvId`, `--environmentType` (`200000000` Dev / `200000001` Target), `--environmentUrl` (opt, only echoed in output marker). Idempotent: if a record already exists for the same `environmentid`, returns it with `reused: true`. Output: `{ deploymentEnvironmentId, name, bapEnvId, environmentUrl, environmentType, validationStatus, reused }`. - `scripts/lib/create-deployment-pipeline.js`: Creates a `deploymentpipelines` record, associates the source environment via `$ref` (relative path + `@odata.context`), and creates `deploymentstages` records for each target environment. Args: `--hostEnvUrl`, `--token`, `--pipelineName`, `--description`, `--sourceDeploymentEnvironmentId`, `--stagesJson` (JSON array of `{ name, targetDeploymentEnvironmentId, order }`). Output: `{ pipelineId, pipelineName, stages[] }`. @@ -242,9 +266,9 @@ Shared reference documents live at `references/` and are referenced by multiple - `solution-api-patterns.md`: OData body templates for publisher POST, solution POST, `AddSolutionComponent`, `ExportSolutionAsync`, `DownloadSolutionExportData`, `ImportSolutionAsync`, `StageSolution`. Also documents `.solution-manifest.json` format. Used by `setup-solution`, `export-solution`, and `import-solution`. - `deployment-error-catalog.md`: Catalog of 10 known deployment failure patterns (stale manifest, blocked JS, missing websiteRecordId, auth expiry, empty build output, solution missing dependencies, solution timeout, PAC CLI not installed, environment mismatch, duplicate component). Each entry includes root cause, severity, auto-fix availability, and fix procedure. Used by `diagnose-deployment`. - `cicd-pipeline-patterns.md`: PAC CLI service principal auth syntax; complete ADO `azure-pipelines.yml` template; complete GitHub Actions `deploy.yml` template; commented solution export/import blocks; secrets/variables setup tables; manual steps that cannot be automated; **Power Platform Pipelines API patterns** (HAR-confirmed): host env discovery via `RetrieveSetting`, `deploymentenvironments` create + `validationstatus` poll, `deploymentpipelines` create, `$ref` associate source (relative path format), `deploymentstages` create, `RetrieveDeploymentPipelineInfo`, stage run create + `ValidatePackageAsync` (204) + `operation` poll, `deploymentsettingsjson` PATCH, `DeployPackageAsync`, `stagerunstatus` terminal values, `docs/alm/last-pipeline.json` and `docs/alm/last-deploy.json` formats. Used by `setup-pipeline` and `deploy-pipeline`. -- `approval-gates.md`: Canonical terminology, marker syntax, and catalog of every user-confirmation point ("Approval Gate") across the ALM skill family. Defines six categories (`intent` / `plan` / `progress` / `consent` / `final` / `pause`), an explicit-pairing marker (`` + human `> 🚦 Gate (...)` block), the `cancel-leaves` vocabulary, and proposed lint rules (`GATE-must-have-marker`, `GATE-id-must-be-unique`, `GATE-must-be-in-catalog`, `GATE-intent-must-call-helper`, `GATE-cancel-leaves-known-vocab`). Currently scoped to the 12 ALM skills (`plan-alm`, `setup-solution`, `setup-pipeline`, `deploy-pipeline`, `export-solution`, `import-solution`, `configure-env-variables`, `ensure-pipelines-host`, `force-link-environment`, `activate-site`, `test-site`, `diagnose-deployment`). **The catalog will be extended to non-ALM skills in a follow-up.** New skills authoring any `AskUserQuestion` block should follow §3 (categories), §4 (marker syntax), and add their gates to §6 (catalog). +- `approval-gates.md`: Canonical terminology, marker syntax, and catalog of every user-confirmation point ("Approval Gate") across the **entire power-pages skill set** (12 ALM + 12 non-ALM). Defines six categories (`intent` / `plan` / `progress` / `consent` / `final` / `pause`), an explicit-pairing marker (`` + human `> 🚦 Gate (...)` block), the `cancel-leaves` vocabulary, and the seven gate-related lint rules enforced by `scripts/lint-skills-alm.js` at hard-fail severity: `GATE-must-have-marker`, `GATE-id-must-be-unique`, `GATE-must-be-in-catalog`, `GATE-intent-must-call-helper`, `GATE-cancel-leaves-known-vocab`, `GATE-prose-block-required` (marker must be followed by a 🚦 prose block within 10 lines, outside any code fence), and `CATALOG-row-must-have-marker` (reverse of `GATE-must-be-in-catalog` — every `kind: gate` catalog row must have a SKILL.md marker). §6.1–§6.12 catalogue the ALM skills; §6.13–§6.24 catalogue the non-ALM skills (`create-site`, `deploy-site`, `add-server-logic`, `add-cloud-flow`, `setup-auth`, `integrate-webapi`, `setup-datamodel`, `add-sample-data`, `add-seo`, `create-webroles`, `audit-permissions`, `integrate-backend`). `report-issue` is excluded because its workflow lives in the cross-plugin shared file. **New skills must extend §6 in the same PR they introduce an `AskUserQuestion`** — lint will block the PR otherwise. -Skill-specific reference docs (e.g., `skills/setup-datamodel/references/odata-api-patterns.md`) contain only patterns unique to that skill and point to the shared docs via `${CLAUDE_PLUGIN_ROOT}/references/` paths for common content. +Skill-specific reference docs (e.g., `skills/setup-datamodel/references/odata-api-patterns.md`) contain only patterns unique to that skill and point to the shared docs via `${PLUGIN_ROOT}/references/` paths for common content. ### MCP Integration @@ -333,21 +357,21 @@ Note: `allowed-tools` must be a comma-separated list, not JSON array or YAML lis Every SKILL.md must include the following line immediately after the closing `---` of the frontmatter (before the `#` title): ```markdown -> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. +> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. ``` This runs a lightweight check comparing the local plugin version against `origin/main` and shows an update notice if a newer version is available. ### Key Patterns -- **Approval Gates** — Every load-bearing `AskUserQuestion` is an **Approval Gate**. Pause at minimum after gathering requirements, after presenting a plan, after implementation, and before deployment (Three-Point Approval Pattern). For ALM skills, every gate must (a) be catalogued in `references/approval-gates.md` §6 with a stable `gate-id`, and (b) be marked in SKILL.md with the explicit-pairing comment `` followed by a human-readable `> 🚦 **Gate (...)**` block. New ALM skills must extend the catalog in the same PR that introduces the skill. Non-ALM skills should follow the same convention as the catalog is extended in a follow-up; lint runs warn-only on non-ALM skills until then. Do not coin alternative terms ("review gate", "approval checkpoint", "manual step" etc.) — the canonical term is **Approval Gate**. +- **Approval Gates** — Every load-bearing `AskUserQuestion` is an **Approval Gate**. Pause at minimum after gathering requirements, after presenting a plan, after implementation, and before deployment (Three-Point Approval Pattern). **Every skill in this plugin** (ALM and non-ALM alike) must (a) catalogue each gate in `references/approval-gates.md` §6 with a stable `gate-id`, and (b) mark it in SKILL.md with the explicit-pairing comment `` followed by a human-readable `> 🚦 **Gate (...)**` block. Pure data-gathering prompts (free-text fallbacks, configuration sub-prompts) take a `` comment instead. `scripts/lint-skills-alm.js` enforces this at **hard-fail** severity across the whole plugin — there is no warn-only carve-out for any skill class. **When you add a new skill that introduces an `AskUserQuestion`, you must extend `references/approval-gates.md` §6 with the new gate-id(s) in the same PR; CI will block the PR otherwise.** Do not coin alternative terms ("review gate", "approval checkpoint", "manual step" etc.) — the canonical term is **Approval Gate**. - **Deployment prompt** — Skills that modify site artifacts should end by asking "Ready to deploy?" and invoke `/deploy-site` if yes. -- **Lifecycle hooks** — If a skill needs command validation or checklist enforcement, update `hooks/hooks.json` and `scripts/lib/powerpages-hook-utils.js`. Do not define hook registration in individual `SKILL.md` files. +- **Lifecycle hooks** — Hook registration is centralized in `hooks/hooks.json`; `scripts/lib/powerpages-hook-utils.js` derives tracked skills from `skills/*/SKILL.md` and discovers optional `scripts/validate*.js` validators. Do not define hook registration in individual `SKILL.md` files. - **Graceful failure** — Track API call results, never auto-rollback, report failures clearly, continue with remaining items. - **Token refresh** — Refresh Azure CLI token every ~20 records / 3-4 tables / ~60 seconds. - **Git commits** — Commit after every significant milestone (each page/component, design foundations, phase completion). - **Agent spawning** — Process sequentially (not parallel), wait for completion, present output for approval. -- **Skill tracking** — Every skill must record usage in its final phase via `> Reference: ${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md` (pointer pattern, not hardcoded command). When adding a new skill, also add its entry to the skill name mapping table in `references/skill-tracking-reference.md`. +- **Skill tracking** — Every skill must record usage in its final phase via `> Reference: ${PLUGIN_ROOT}/references/skill-tracking-reference.md` (pointer pattern, not hardcoded command). When adding a new skill, also add its entry to the skill name mapping table in `references/skill-tracking-reference.md`. - **Shell-agnostic docs** — SKILL.md, agent, and reference files must not embed shell-specific syntax inside shell commands or code blocks. Use ` ```bash ` fences (or plain ` ``` `) only for cross-platform commands like `pac`, `az`, `dotnet`, and `node`. Do not use PowerShell cmdlets (`Get-ChildItem`, `Test-Path`, `New-Item`, `Get-Content`, `Remove-Item`, `ConvertFrom-Json`, `Invoke-RestMethod`, etc.) or PowerShell-only variable syntax inside shell commands/code blocks (e.g., `$var = command`, `$env:...`) — prefer `` angle-bracket style there (e.g., ``). Repo runtime placeholders used in prose/templates (such as `**Initial request:** $ARGUMENTS`) are allowed. For filesystem and JSON operations the agent already has first-class tools (`Glob`, `Read`, `Write`, `Edit`) — describe the intent in prose rather than prescribing a shell command. - **Dataverse API calls** — Use deterministic Node.js scripts (in the skill's `scripts/` directory) for Dataverse API queries. Scripts should import `getAuthToken` and `makeRequest` from `scripts/lib/validation-helpers.js`. Never use inline PowerShell `Invoke-RestMethod` for API calls — scripts are more reliable, testable, and cross-platform. - **ALM-aware by default** — Any skill that creates, modifies, or depends on Dataverse records that belong in a Power Pages site's solution (site components, env var definitions, web roles, site settings, server logic, cloud flow bindings, bot consumers, custom tables/columns, etc.) MUST ensure those records land in the user's solution when `.solution-manifest.json` exists. Concrete rules: @@ -388,6 +412,18 @@ These patterns have caused repeated PR review feedback. Check for them before su - **Template placeholders in ` + + +``` + +### Angular + +`src/app/components/summary-markdown.component.ts` — a pure component with `text` as `@Input()`, +rendering the same paragraph/line/segment structure with `*ngFor` / `*ngIf`. No `[innerHTML]`. + +```ts +import { Component, Input } from '@angular/core'; + +interface Segment { bold: boolean; text: string; } +interface Line { segments: Segment[]; } +interface Para { lines: Line[]; } + +@Component({ + selector: 'app-summary-markdown', + template: ` +
+

+ + + {{ seg.text }} + {{ seg.text }} + +
+
+

+
+ `, +}) +export class SummaryMarkdownComponent { + paragraphs: Para[] = []; + + @Input() set text(raw: string) { + this.paragraphs = (raw ?? '') + .split(/\n{2,}/) + .map((p) => p.trim()) + .filter(Boolean) + .map((p) => ({ lines: p.split('\n').map((line) => ({ segments: this.splitBold(line) })) })); + } + + private splitBold(line: string): Segment[] { + const out: Segment[] = []; + const re = /\*\*([^*]+?)\*\*/g; + let last = 0; + let m: RegExpExecArray | null; + while ((m = re.exec(line)) !== null) { + if (m.index > last) out.push({ bold: false, text: line.slice(last, m.index) }); + out.push({ bold: true, text: m[1] }); + last = re.lastIndex; + } + if (last < line.length) out.push({ bold: false, text: line.slice(last) }); + return out; + } +} +``` + +### Astro + +If the site mounts React/Vue islands, reuse the island component rather than duplicating the +logic. For a pure-Astro component, do the parsing in the frontmatter and iterate in the template +— Astro escapes `{...}` expressions by default, so plain text interpolation is safe. + +```astro +--- +interface Props { text: string } +const { text } = Astro.props + +const paragraphs = text + .split(/\n{2,}/) + .map((p) => p.trim()) + .filter(Boolean) + .map((p) => + p.split('\n').map((line) => { + const segs: { bold: boolean; text: string }[] = [] + const re = /\*\*([^*]+?)\*\*/g + let last = 0 + let m: RegExpExecArray | null + while ((m = re.exec(line)) !== null) { + if (m.index > last) segs.push({ bold: false, text: line.slice(last, m.index) }) + segs.push({ bold: true, text: m[1] }) + last = re.lastIndex + } + if (last < line.length) segs.push({ bold: false, text: line.slice(last) }) + return segs + }), + ) +--- +
+ {paragraphs.map((lines) => ( +

+ {lines.map((segs, li) => ( + <> + {segs.map((seg) => (seg.bold ? {seg.text} : {seg.text}))} + {li < lines.length - 1 &&
} + + ))} +

+ ))} +
+``` + +--- + +## 2. Citation rendering (Search Summary) + +`parseSummaryWithCitations(Summary)` returns `SummaryPart[]`. Emit framework-native anchors for +`citation` parts; rewrite the href via `resolveCitationHref` (which calls +`extractKnowledgeArticleId` and falls back to the raw URL on SPA sites); label with +`CitationTitleMapping[token]` falling back to the URL, then the bare token. Keep the visible text +as the token so inline citations stay scannable. + +`resolveCitationHref` is shared (define once in the service): + +```ts +import { extractKnowledgeArticleId } from '../services/aiSummaryService'; + +export function resolveCitationHref(url: string): string { + const articleId = extractKnowledgeArticleId(url); + return articleId ? `/knowledge/${articleId}` : url; // confirm the project's actual KB route +} +``` + +### Vue (SFC) + +```vue + + + +``` + +### Angular + +```ts +import { Component, Input } from '@angular/core'; +import { parseSummaryWithCitations } from '../services/aiSummaryService'; +import { resolveCitationHref } from '../services/citations'; + +@Component({ + selector: 'app-summary-with-citations', + template: ` +

+ + {{ part.text }} + {{ part.token }} + +

+ `, +}) +export class SummaryWithCitationsComponent { + @Input() summary = ''; + @Input() citationTitleMapping?: Record; + resolveCitationHref = resolveCitationHref; + + get parts() { + return parseSummaryWithCitations(this.summary); + } + + label(token: string, url: string): string { + return this.citationTitleMapping?.[token] ?? url ?? token; + } +} +``` + +### Astro + +```astro +--- +import { parseSummaryWithCitations } from '../services/aiSummaryService' +import { resolveCitationHref } from '../services/citations' + +interface Props { summary: string; citationTitleMapping?: Record } +const { summary, citationTitleMapping } = Astro.props +const parts = parseSummaryWithCitations(summary) +const label = (token: string, url: string) => citationTitleMapping?.[token] ?? url ?? token +--- +

+ {parts.map((part) => + part.kind === 'text' ? ( + {part.text} + ) : ( + {part.token} + ), + )} +

+``` + +> **Citation list ("Sources" footer).** The visible label for each row is +> `CitationTitleMapping[token]`; fall back to the URL **only** when the mapping is missing. +> Never show a bare URL when a mapping exists — it reads as broken UI. diff --git a/plugins/power-pages/skills/add-ai-webapi/references/scope-classification.md b/plugins/power-pages/skills/add-ai-webapi/references/scope-classification.md new file mode 100644 index 000000000..096f363c9 --- /dev/null +++ b/plugins/power-pages/skills/add-ai-webapi/references/scope-classification.md @@ -0,0 +1,102 @@ +# Phase 3 follow-up questions — list trigger and scope confirmation + +After the user confirms which APIs / targets to integrate (Phase 3 main question), the +orchestrator asks per-target follow-ups for: + +- **List-trigger choice** — every confirmed list target (one question per list target). +- **Scope confirmation** — every target Phase 2 flagged `scope-extends-beyond-existing-fetch`, + `needs-definition`, or `intent-mismatch`. The question text varies by target kind and + classification. + +Skip both follow-ups when the corresponding flag isn't set on the row. + +Each chosen value flows into Phase 5's agent-invocation prompt — the Phase 5 agent does NOT +re-derive scope from the existing fetch. The original UI fetch on the target component stays +in place unchanged regardless of what scope the summary uses. + +--- + +## List-trigger follow-up (one per list target) + +For every confirmed target whose page renders a **collection** of records (filename matches +`*List*`, `*History*`, `*Results*`, or the target component iterates a server-returned array +in the UI), ask: + +| Question | Header | Options | +|----------|--------|---------| +| Should the summary appear automatically when the page opens, or only when the user clicks a button? | Trigger | Load the summary when the page opens (Recommended when the list is short and the extra API call won't slow the page noticeably), Load only when the user clicks a button (Recommended when the list is large or filters change frequently) | + +Single-record summary targets (any record-detail page, including the support-case detail +page) skip this question — single-record summaries always trigger on a user action. + +Both options produce the same hook/composable surface (`refresh`, +`summariseWithRecommendation`); only the initial state of the wrapper differs. Record the +choice per target and pass it to the `ai-webapi-integration` agent in Phase 5 so it wires +the correct initial trigger. + +--- + +## Scope-confirmation follow-up + +Ask only when Phase 2 flagged the target with one of these classifications. The question +text varies by target kind and classification. + +### LIST target — `scope-extends-beyond-existing-fetch` + +User qualifier detected; existing list fetch has a different or no `$filter`. + +| Question | Header | Options | +|----------|--------|---------| +| You asked for a summary of `` ``. The existing list on this page currently shows ``. Which scope should the summary cover? | Scope | Use my scope — `$filter=` (Recommended), Mirror the existing list — `$filter=`, Let me write the OData `$filter`, Both — create two summary cards (advanced) | + +### LIST target — `needs-definition` + +Target page has no existing list fetch. + +| Question | Header | Options | +|----------|--------|---------| +| The target page has no existing list to mirror. Which rows should the summary cover? | Scope | Use my scope — `$filter=` (Recommended), Summarise all `` rows the signed-in user can see (no filter beyond row-level security), Let me write the OData `$filter` | + +### SINGLE-RECORD target — `scope-extends-beyond-existing-fetch` + +User mentions facets / related records not in the existing record fetch — e.g., "include its +line items" when the fetch has no `$expand`. + +| Question | Header | Options | +|----------|--------|---------| +| Your request mentions `` — this isn't in the existing record fetch (which selects ``). Which facets should the summary include? | Facets | Include the mentioned facets — `$select=,`, `$expand=` (Recommended), Use the existing fetch's columns only (no additions), Let me write the `$select`/`$expand` | + +Translate the qualifier to concrete columns / expansions using the datamodel manifest — e.g., +"include its line items" on `cr363_order` with a related `cr363_orderlineitem` table via a +`cr363_Order_LineItems` navigation property maps to +`$expand=cr363_Order_LineItems($select=)`. Show the proposed +value inside the option. **Any new `$expand` target becomes a new Phase 4 prerequisite** (Web +API enabled + parent-scope permission on the child table) — re-evaluate the Phase 2 +delegation decision after the user picks. + +### Any target — `intent-mismatch` + +Filename heuristic and the user's verbal intent disagree on target kind (filename says +single-record, request says "my open cases" → list; or vice versa). + +| Question | Header | Options | +|----------|--------|---------| +| The target page is `` (looks like ``). Your request mentions `` — that sounds more like a ``. Which do you want? | Target kind | `` — (Recommended — matches your ask), `` — , Both — wire two summary cards on the same page | + +Resolve `intent-mismatch` **before** any scope-confirmation question — the chosen target +kind decides which scope question (LIST or SINGLE-RECORD form) to ask next. + +--- + +## Handling the chosen values to Phase 5 + +Phase 5's `ai-webapi-integration` agent-invocation prompt has a structured **Scope for the +summary call** block. Populate: + +- `$filter` — the exact filter string (or `none`). +- `$orderby` — typically mirrors the existing fetch. +- `Scope source` — one of `mirror-existing-fetch`, `user-verbal-scope`, + `user-custom-odata`, `no-filter`, `both` (for the dual-summary advanced option). +- `Target's existing list fetch` — for reference only, never as an override target. + +The agent applies these verbatim and does not re-read the existing fetch. diff --git a/plugins/power-pages/skills/add-ai-webapi/scripts/validate-ai-webapi.js b/plugins/power-pages/skills/add-ai-webapi/scripts/validate-ai-webapi.js new file mode 100644 index 000000000..1963ec4de --- /dev/null +++ b/plugins/power-pages/skills/add-ai-webapi/scripts/validate-ai-webapi.js @@ -0,0 +1,402 @@ +#!/usr/bin/env node + +// Validates that the add-ai-webapi skill produced AI summarization integration code. +// Runs as the PostToolUse Skill hook validator. +// +// A valid run must produce at least one of: +// - a search-summary service that POSTs to /_api/search/v1.0/summary +// - a data-summarization service that POSTs to /_api/summarization/data/v1.0/ +// +// Blocking checks (these are documented or structural and break the API at runtime). All are +// project-wide: the required token must appear somewhere under src/, not necessarily in the same +// file as the endpoint URL — a correct integration commonly centralizes header and URL +// construction in a shared helper (the same pattern validate-webapi-integration.js accepts for +// powerPagesApi.ts), so a per-file requirement would false-fail those projects. +// - The integration must attach the __RequestVerificationToken header. The Data Summarization +// docs require a CSRF token on these POST requests; omitting it produces a token-validation +// failure. (These endpoints are semantically read-only — they never mutate Dataverse — but the +// runtime enforces CSRF on POST regardless of mutation semantics, so the token is still required.) +// - Data summarization must include $select — Power Pages Web API never allows wildcard columns, +// and the Microsoft sample URL has $select. +// - Data summarization must set OData-MaxVersion: 4.0 and OData-Version: 4.0. The endpoint +// inherits the Power Pages Web API rules and rejects requests without the OData version headers. +// - Search Summary must use Content-Type: application/x-www-form-urlencoded. The endpoint rejects +// application/json with a 400 — this is the most common copy-paste failure when the data +// endpoint's headers leak into the search call. +// +// Advisory only (missing prints a warning but does not block): +// - X-Requested-With: XMLHttpRequest — matches shell.ajaxSafePost's default behaviour +// used by the Microsoft-shipped case-page Copilot snippet, but neither summarization +// doc mandates it. Worth flagging so reviewers can confirm it was intentional. +// - Search Summary citation parsing — the API embeds [[N]](url) markdown tokens inline +// in Summary. Rendering Summary directly shows raw markdown. Warn when no source file +// references parseSummaryWithCitations or contains a [[N]](url) parsing pattern. +// - Search Summary KB-id rewrite — on Single Page Application (SPA) sites the API returns +// /page-not-found/?id= citation URLs that need rewriting to the SPA's KB route. +// Warn when search-summary code is present but no file references +// extractKnowledgeArticleId (or an equivalent inline rewrite). +// - Search Summary disabled-state envelope — the endpoint returns HTTP 200 with +// { Code, Message } when the site-level Gen AI Search toggle is off. Naive code +// treats this as a success. Warn when search code is present but no file references +// SearchSummaryApiError / isGenAiSearchDisabled (or an equivalent inline detection +// of body.Code + body.Message). +// - List-summary ContentSizeLimit — when fetchListSummary appears in source, the +// Summarization/Data/ContentSizeLimit site setting must be present at >= 200000. +// The 100k server default silently truncates list content; truncation is invisible +// (no error code), so summaries ship based on partial data. Warn when the YAML is +// missing or its value is below 200000. +// - Plain-scalar Summarization/prompt/* values > 200 chars. Plain-scalar YAML breaks +// pac pages upload-code-site silently when prompts contain ": ", "|", "<|", colons- +// followed-by-quotes, or newlines — and any prompt > 200 chars is likely to hit one +// of those. Warn so the maker switches to block-literal (value: |) before deploy. +// +// Blocking checks (these will refuse the run): +// - Summarization/prompt/* size > 2000 chars. The adx_sitesetting.adx_value column has +// a default Memo MaxLength of 2000 in older environments, so a longer value can fail +// the Dataverse upload silently or produce a truncated prompt at runtime. 2000 is the +// hard ceiling we support. +// +// Advisory only: +// - Summarization/prompt/* size > 1000 chars. Long prompts cost more per call and are +// harder to maintain. Warn so makers can compress before hitting the 2000 ceiling. + +const fs = require('fs'); +const path = require('path'); +const { approve, block, runValidation, findProjectRoot } = require('../../../scripts/lib/validation-helpers'); +const { validatePowerPagesSchema } = require('../../../scripts/lib/powerpages-schema-validator'); + +const SEARCH_SUMMARY_URL = '/_api/search/v1.0/summary'; +const DATA_SUMMARIZATION_URL = '/_api/summarization/data/v1.0/'; +const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.vue', '.astro']); + +runValidation((cwd) => { + const projectRoot = findProjectRoot(cwd); + if (!projectRoot) approve(); + + const srcDir = path.join(projectRoot, 'src'); + if (!fs.existsSync(srcDir)) approve(); + + const sourceFiles = collectSourceFiles(srcDir); + const hits = []; + + for (const file of sourceFiles) { + let content; + try { + content = fs.readFileSync(file, 'utf8'); + } catch { + continue; + } + const hasSearch = content.includes(SEARCH_SUMMARY_URL); + const hasData = content.includes(DATA_SUMMARIZATION_URL); + if (hasSearch || hasData) { + hits.push({ file, content, hasSearch, hasData }); + } + } + + if (hits.length === 0) approve(); + + const errors = []; + const warnings = []; + + // Header and URL-token checks are project-wide, not per-file. A correct integration commonly + // centralizes header construction and URL building in a shared helper (the same pattern + // validate-webapi-integration.js accepts for powerPagesApi.ts), so requiring every token to + // appear in the same file as the endpoint URL would false-fail those projects. We read the + // whole src/ tree once and block only when a required token is absent project-wide. + const allContent = sourceFiles + .map((f) => { + try { + return fs.readFileSync(f, 'utf8'); + } catch { + return ''; + } + }) + .join('\n'); + + const projectHasData = hits.some((h) => h.hasData); + const projectHasSearchSummary = hits.some((h) => h.hasSearch); + + if (!allContent.includes('__RequestVerificationToken')) { + errors.push( + 'summarization integration is missing the __RequestVerificationToken header anywhere under src/ (CSRF token is required on these POST requests — fetch it from /_layout/tokenhtml)' + ); + } + if (!allContent.includes('X-Requested-With')) { + warnings.push( + 'summarization integration does not set the X-Requested-With: XMLHttpRequest header anywhere under src/ (not strictly required by the docs, but matches shell.ajaxSafePost behaviour used by the Microsoft case-page snippet)' + ); + } + if (projectHasData) { + if (!/\$select=/.test(allContent)) { + errors.push( + 'data summarization integration is missing $select anywhere under src/ — Power Pages Web API requires explicit column lists, never wildcards' + ); + } + // OData 4.0 headers are mandatory on the data-summarization endpoint — it inherits + // the Power Pages Web API rules and rejects requests without them. + if (!allContent.includes('OData-MaxVersion')) { + errors.push( + 'data summarization integration is missing the OData-MaxVersion: 4.0 header anywhere under src/ — the Power Pages Web API rejects requests without it' + ); + } + if (!allContent.includes('OData-Version')) { + errors.push( + 'data summarization integration is missing the OData-Version: 4.0 header anywhere under src/ — the Power Pages Web API rejects requests without it' + ); + } + } + if (projectHasSearchSummary) { + // Search Summary requires application/x-www-form-urlencoded. Sending application/json + // (the most common copy-paste failure from the data endpoint) returns 400. + if (!allContent.includes('application/x-www-form-urlencoded')) { + errors.push( + 'Search Summary integration is missing Content-Type: application/x-www-form-urlencoded anywhere under src/ — sending application/json returns 400 (this is the #1 way to break /_api/search/v1.0/summary)' + ); + } + } + + // Project-wide checks for Search Summary UI rendering. The parser/rewrite typically lives in a + // UI component (or a shared util), not the file containing the fetch call. We only run them when + // the project actually calls /_api/search/v1.0/summary somewhere. + if (projectHasSearchSummary) { + // [[N]](url) parser: either by helper name or by a pattern that handles the token. + // The documented parser is a regex literal `/\[\[(\d+)\]\]\(([^)]+)\)/`, so the source + // text contains the escaped-bracket characters `\[\[ ... \]\]\(`. Match that form (any + // capture-group content between the escaped brackets), or a literal `[[N]](` token. + const usesParserHelper = allContent.includes('parseSummaryWithCitations'); + const handlesTokenInline = + /\\\[\\\[.*?\\\]\\\]\\\(/.test(allContent) || /\[\[\d+\]\]\(/.test(allContent); + if (!usesParserHelper && !handlesTokenInline) { + warnings.push( + 'Search Summary is integrated but no source file references parseSummaryWithCitations or a [[N]](url) parsing pattern — Summary will render as raw markdown unless a parser is wired in.' + ); + } + + // KB-id rewrite for SPA sites: either by helper name or by inline reading of ?id=. + const usesRewriteHelper = allContent.includes('extractKnowledgeArticleId'); + const handlesRewriteInline = /searchParams\.get\(\s*['"]id['"]\s*\)/.test(allContent); + if (!usesRewriteHelper && !handlesRewriteInline) { + warnings.push( + "Search Summary is integrated but no source file references extractKnowledgeArticleId or reads the citation URL's ?id parameter — citation links will land on the built-in /page-not-found page on SPA sites." + ); + } + + // Disabled-state envelope: the endpoint returns HTTP 200 with { Code, Message } when + // the site-level Gen AI Search toggle is off. Naive code that calls response.json() on + // a 200 treats this as success and renders the empty-state message. Detection can be + // by helper name (SearchSummaryApiError / isGenAiSearchDisabled) or by an inline check + // that pattern-matches body.Code (number) + body.Message (string) without a Summary. + const usesDisabledHelper = + allContent.includes('SearchSummaryApiError') || allContent.includes('isGenAiSearchDisabled'); + const handlesDisabledInline = + /\.\s*Code\s*[=!]==?/.test(allContent) && /\.\s*Message\s*[=!]==?/.test(allContent); + if (!usesDisabledHelper && !handlesDisabledInline) { + warnings.push( + 'Search Summary is integrated but no source file references SearchSummaryApiError / isGenAiSearchDisabled or detects the embedded { Code, Message } envelope inline — when the site-level Gen AI Search toggle is off, the endpoint returns HTTP 200 with that envelope and the UI will silently render the empty-state message. See agents/ai-webapi-integration.md §3.2.' + ); + } + } + + // List-summary check: when fetchListSummary is referenced, ContentSizeLimit must be >= 200000. + // The collection endpoint silently truncates input content at the server-side cap; the 100k + // default produces summaries based on partial data with no error to catch. + const projectHasListSummary = sourceFiles.some((f) => { + try { + return fs.readFileSync(f, 'utf8').includes('fetchListSummary'); + } catch { + return false; + } + }); + if (projectHasListSummary) { + const settingPath = path.join( + projectRoot, + '.powerpages-site', + 'site-settings', + 'Summarization-Data-ContentSizeLimit.sitesetting.yml' + ); + let yaml = null; + try { + yaml = fs.readFileSync(settingPath, 'utf8'); + } catch { + yaml = null; + } + if (yaml === null) { + warnings.push( + 'List summary (fetchListSummary) is integrated but Summarization-Data-ContentSizeLimit.sitesetting.yml is missing — the 100k server default will silently truncate list content. Set Summarization/Data/ContentSizeLimit = 200000.' + ); + } else { + // Reuse extractSiteSettingValue so block-literal YAML (`value: |\n 200000`) is parsed as + // well as plain/quoted scalars. ContentSizeLimit is conventionally a plain integer, but a + // hand-written block-literal would otherwise slip past validation silently. + const rawValue = extractSiteSettingValue(yaml); + const trimmedValue = rawValue !== null ? rawValue.trim() : ''; + const numericValue = /^\d+$/.test(trimmedValue) ? parseInt(trimmedValue, 10) : null; + if (numericValue === null) { + warnings.push( + 'Summarization-Data-ContentSizeLimit.sitesetting.yml exists but its `value` field is not a parseable integer — list-summary truncation cannot be verified. Set value: 200000.' + ); + } else if (numericValue < 200000) { + warnings.push( + `Summarization/Data/ContentSizeLimit is ${numericValue}; list summaries should use at least 200000 to avoid silent truncation of ~500-row payloads.` + ); + } + } + } + + // Summarization/prompt/* size check. + // The prompt is stored in adx_sitesetting.adx_value (Memo column). Default Memo MaxLength is 2000 + // in many environments, so we treat 2000 as the hard ceiling. We also warn at 1000 — long prompts + // cost more per summarization call and are harder to maintain. + const siteSettingsDir = path.join(projectRoot, '.powerpages-site', 'site-settings'); + if (fs.existsSync(siteSettingsDir)) { + let entries = []; + try { + entries = fs.readdirSync(siteSettingsDir); + } catch { + entries = []; + } + for (const entry of entries) { + if (!entry.startsWith('Summarization-prompt-') || !entry.endsWith('.sitesetting.yml')) continue; + const filePath = path.join(siteSettingsDir, entry); + let yaml; + try { + yaml = fs.readFileSync(filePath, 'utf8'); + } catch { + continue; + } + const promptValue = extractSiteSettingValue(yaml); + if (promptValue === null) continue; + const charCount = promptValue.length; + if (charCount > 2000) { + errors.push( + `${entry}: prompt value is ${charCount} characters, exceeding the supported maximum of 2000. Shorten this site-setting prompt — condense or drop inline examples within the prompt text. The Data Summarization request body does not accept prompt text (only InstructionIdentifier / RecommendationConfig), so the full instruction must fit in this site-setting value.` + ); + } else if (charCount > 1000) { + warnings.push( + `${entry}: prompt value is ${charCount} characters; aim for ≤1000 to keep prompts compact and maintainable. Hard ceiling is 2000.` + ); + } + // Plain-scalar prompts > 200 chars are likely to break pac pages upload-code-site + // silently because long prompts almost always contain ": ", "|", "<|", or newlines — + // any of which trip plain-scalar YAML parsing. Block-literal (value: |) is the + // documented safe form. See agents/ai-webapi-settings-architect.md §5.1a. + if (charCount > 200 && isPlainScalarValue(yaml)) { + warnings.push( + `${entry}: prompt value is ${charCount} characters and uses plain-scalar YAML (value: ). Plain-scalar parsing breaks pac pages upload-code-site for prompts containing ": ", "|", "<|", or newlines — any of which is likely above 200 chars. Switch to block-literal form (value: |\\n ).` + ); + } + } + } + + const schemaValidation = validatePowerPagesSchema(projectRoot); + const schemaErrors = schemaValidation.findings + .filter(finding => finding.severity === 'error') + .map(finding => finding.filePath ? `${finding.message} (${path.basename(finding.filePath)})` : finding.message); + + if (schemaErrors.length > 0) { + errors.push('Invalid Power Pages permissions/site-settings schema:\n - ' + schemaErrors.join('\n - ')); + } + + if (warnings.length > 0) { + process.stderr.write('AI summarization integration warnings:\n- ' + warnings.join('\n- ') + '\n'); + } + + if (errors.length > 0) { + block('AI summarization integration validation failed:\n- ' + errors.join('\n- ')); + } + + approve(); +}); + +// Extract the `value:` field from a site-setting YAML. Handles four shapes: +// plain: value: some text +// quoted: value: "some text" or value: 'some text' +// block-literal: value: | +// indented +// multi-line content +// folded-scalar: value: > +// paragraph one +// continuation +// +// For block-literal and folded-scalar we read the indented block the same way and join with +// newlines. Folded scalar would render newlines as spaces at runtime, so the joined string +// is a slight over-estimate of the runtime length — that's acceptable for the size guard. +// Returns the string value, or null if the file has no `value:` key. +function extractSiteSettingValue(yaml) { + const lines = yaml.split(/\r?\n/); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const blockMatch = line.match(/^value\s*:\s*[|>][+-]?\s*$/); + if (blockMatch) { + // Collect subsequent indented lines until indentation drops back to column 0 (or non-blank line at column 0). + const collected = []; + let baseIndent = null; + for (let j = i + 1; j < lines.length; j++) { + const next = lines[j]; + if (next.trim() === '') { + collected.push(''); + continue; + } + const indentMatch = next.match(/^(\s+)/); + if (!indentMatch) break; // dedent — block ended + const indent = indentMatch[1].length; + if (baseIndent === null) baseIndent = indent; + if (indent < baseIndent) break; + collected.push(next.slice(baseIndent)); + } + return collected.join('\n').replace(/\n+$/, ''); + } + const inlineMatch = line.match(/^value\s*:\s*(.*)$/); + if (inlineMatch) { + let raw = inlineMatch[1].trim(); + if (raw === '') return ''; + // Strip matching outer quotes (single or double). + if ( + (raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) || + (raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2) + ) { + raw = raw.slice(1, -1); + } + return raw; + } + } + return null; +} + +// Returns true when the YAML stores `value` as a plain (non-block) scalar — i.e. inline +// after the colon, optionally quoted. Block-literal (`value: |`) and folded-scalar +// (`value: >`) return false. Used to flag long prompts that should be migrated to +// block-literal form to avoid pac pages upload-code-site parsing failures. +function isPlainScalarValue(yaml) { + const lines = yaml.split(/\r?\n/); + for (const line of lines) { + if (/^value\s*:\s*[|>][+-]?\s*$/.test(line)) return false; + if (/^value\s*:\s*.+$/.test(line)) return true; + if (/^value\s*:\s*$/.test(line)) return false; // empty value (no scalar at all) + } + return false; +} + +function collectSourceFiles(dir) { + const results = []; + const stack = [dir]; + while (stack.length > 0) { + const current = stack.pop(); + let entries; + try { + entries = fs.readdirSync(current, { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) continue; + const full = path.join(current, entry.name); + if (entry.isDirectory()) { + stack.push(full); + } else if (entry.isFile() && SOURCE_EXTENSIONS.has(path.extname(entry.name))) { + results.push(full); + } + } + } + return results; +} diff --git a/plugins/power-pages/skills/add-cloud-flow/SKILL.md b/plugins/power-pages/skills/add-cloud-flow/SKILL.md index 845884288..37755d55c 100644 --- a/plugins/power-pages/skills/add-cloud-flow/SKILL.md +++ b/plugins/power-pages/skills/add-cloud-flow/SKILL.md @@ -11,7 +11,7 @@ allowed-tools: Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion, Skill, Task model: opus --- -> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. +> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. # Add Cloud Flow @@ -76,6 +76,14 @@ Look for the `.powerpages-site` folder in the project root. **If not found**: + + +> 🚦 **Gate (plan · add-cloud-flow:1.3.deploy-first):** `.powerpages-site` missing — cloud flow YAML lives inside it. Deploy first or stop. +> +> **Trigger:** Phase 1.3 found no `.powerpages-site` directory. +> **Why we ask:** Cloud flow YAML written to a non-existent path will never deploy. +> **Cancel leaves:** Nothing — no YAML files written. + Use `AskUserQuestion`: | Question | Options | @@ -100,7 +108,7 @@ Store these as the **already-registered flows** list. These flows already have m ### 1.6 Detect Frontend Framework -Read `package.json` to detect the framework (React, Vue, Angular, Astro). Note the framework and its conventions for Phase 7. See `${CLAUDE_PLUGIN_ROOT}/references/framework-conventions.md` for the detection mapping. +Read `package.json` to detect the framework (React, Vue, Angular, Astro). Note the framework and its conventions for Phase 7. See `${PLUGIN_ROOT}/references/framework-conventions.md` for the detection mapping. **Output**: Project root, site name, framework, available web roles, already-registered flows (with processid, name, and web roles) @@ -115,7 +123,7 @@ Read `package.json` to detect the framework (React, Vue, Angular, Astro). Note t Run the list-cloud-flows script (reads environment from `pac auth who` internally): ```bash -node "${CLAUDE_PLUGIN_ROOT}/skills/add-cloud-flow/scripts/list-cloud-flows.js" +node "${PLUGIN_ROOT}/skills/add-cloud-flow/scripts/list-cloud-flows.js" ``` This calls the **Power Automate Flow RP API** with the filter `properties/definitionSummary/triggers/any(t: t/kind eq 'powerpages')`. Results include: @@ -190,6 +198,14 @@ Already registered (available for additional frontend integration): 3. Support Ticket Handler — Already connected, can be wired into more pages ``` + + +> 🚦 **Gate (plan · add-cloud-flow:3.1.select-flows):** Multi-select over discovered + already-registered flows. Drives the rest of Phases 4–7. +> +> **Trigger:** Phase 2 list-cloud-flows returned at least one flow. +> **Why we ask:** Wrong flows get registered (new `.cloudflowconsumer.yml` files written) or wrong existing flows get re-wired into the frontend. +> **Cancel leaves:** Nothing — no YAML or client code written yet. + Use `AskUserQuestion`: | Question | Options | @@ -218,6 +234,8 @@ For each selected flow, identify its scenario from the name and description: | **Admin action** | Bulk processing, content approval, data export | Admins / specific roles only | | **Background / system** | Scheduled sync, enrichment | Not triggered by portal users directly | + + If a flow's scenario is unclear, use `AskUserQuestion` per flow: | Question | Context | @@ -325,12 +343,11 @@ Assemble the plan JSON (kept in memory — not written to disk). Include all sel ``` For **`integration-only`** flows, `webRoles` should reflect the existing roles from the `.cloudflowconsumer.yml` (read-only — not being changed). The `rationale` should describe where the flow will be additionally integrated (e.g., "Wiring existing Contact Form flow into the support page"). -``` ### 5.2 Render HTML Plan ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/render-cloudflow-plan.js" \ +node "${PLUGIN_ROOT}/scripts/render-cloudflow-plan.js" \ --output "/docs/cloud-flow-plan.html" \ --data-inline '' ``` @@ -343,6 +360,14 @@ Open the rendered file in the default browser (`open` on macOS, `start` on Windo Give a brief CLI summary: number of flows, scenarios, role count, any anonymous-role warnings. + + +> 🚦 **Gate (plan · add-cloud-flow:5.3.plan-approval):** Final sign-off on the rendered HTML plan before any web role / `.cloudflowconsumer.yml` / client code is written. +> +> **Trigger:** Phase 5.2 rendered the HTML plan. +> **Why we ask:** Wrong web role assignments committed (especially Anonymous Users on auth-protected flows); orphaned YAML files in `.powerpages-site/cloud-flow-consumer/`. +> **Cancel leaves:** Nothing — no YAML or frontend changes yet. + Use `AskUserQuestion`: | Question | Options | @@ -385,7 +410,7 @@ Repeat steps 6.3–6.4 for every **`new`** flow in the approved plan. ### 6.4 Run the Create Metadata Script ```bash -node "${CLAUDE_PLUGIN_ROOT}/skills/add-cloud-flow/scripts/create-cloud-flow-metadata.js" \ +node "${PLUGIN_ROOT}/skills/add-cloud-flow/scripts/create-cloud-flow-metadata.js" \ --projectRoot "" \ --fileSlug "" \ --flowName "" \ @@ -564,7 +589,7 @@ If any check fails, fix the issue in the service file or UI before continuing. ### 8.2 Record Skill Usage -> Reference: `${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md` +> Reference: `${PLUGIN_ROOT}/references/skill-tracking-reference.md` Use `--skillName "AddCloudFlow"`. @@ -583,12 +608,28 @@ Use `--skillName "AddCloudFlow"`. ### 8.4 Ask to Deploy + + +> 🚦 **Gate (plan · add-cloud-flow:8.4.deploy):** Post-implementation deploy prompt — flows don't trigger until deployed. +> +> **Trigger:** All flow YAML + client integration in place. +> **Why we ask:** Auto-deploy picks wrong env. +> **Cancel leaves:** Nothing — artifacts stay on disk; no deploy fired. + Use `AskUserQuestion`: | Question | Options | |----------|---------| | Everything is ready. Deploy the site to make the flows live? | Yes, deploy now (Recommended), No, I'll deploy later | + + +> 🚦 **Gate (plan · add-cloud-flow:8.4.test):** Post-deploy validation prompt — invokes `/test-site` to confirm the flow trigger endpoint returns 202/200. +> +> **Trigger:** Deploy from the previous gate succeeded. +> **Why we ask:** Skipping is harmless (manual test still possible); auto-invoking `/test-site` adds runtime and Playwright traffic. +> **Cancel leaves:** Nothing — deploy has already completed. + **If "Yes"**: Invoke `/deploy-site`. After it succeeds, use `AskUserQuestion`: | Question | Options | diff --git a/plugins/power-pages/skills/add-sample-data/SKILL.md b/plugins/power-pages/skills/add-sample-data/SKILL.md index 2e52ffac8..078a486a0 100644 --- a/plugins/power-pages/skills/add-sample-data/SKILL.md +++ b/plugins/power-pages/skills/add-sample-data/SKILL.md @@ -9,7 +9,7 @@ allowed-tools: Read, Write, Bash, Grep, Glob, AskUserQuestion, Task, TaskCreate, model: sonnet --- -> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. +> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. # Add Sample Data @@ -32,7 +32,7 @@ Populate Dataverse tables with sample records via OData API so users can test an **Actions**: 1. Create todo list with all 6 phases (see [Progress Tracking](#progress-tracking) table) -2. Follow the prerequisite steps in `${CLAUDE_PLUGIN_ROOT}/references/dataverse-prerequisites.md` to verify PAC CLI auth, acquire an Azure CLI token, and confirm API access. Note the environment URL as `` for subsequent script calls. +2. Follow the prerequisite steps in `${PLUGIN_ROOT}/references/dataverse-prerequisites.md` to verify PAC CLI auth, acquire an Azure CLI token, and confirm API access. Note the environment URL as `` for subsequent script calls. **Output**: Authenticated session with valid token and confirmed API access @@ -48,20 +48,20 @@ Populate Dataverse tables with sample records via OData API so users can test an Check if `.datamodel-manifest.json` exists in the project root (written by the `setup-datamodel` skill). If it exists, read it -- it already contains table logical names, display names, and column info. -See `${CLAUDE_PLUGIN_ROOT}/references/datamodel-manifest-schema.md` for the full manifest schema. +See `${PLUGIN_ROOT}/references/datamodel-manifest-schema.md` for the full manifest schema. ### Path B: Query OData API (Fallback) If no manifest exists, discover custom tables via OData: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions?\$select=LogicalName,DisplayName,EntitySetName&\$filter=IsCustomEntity eq true" +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions?\$select=LogicalName,DisplayName,EntitySetName&\$filter=IsCustomEntity eq true" ``` For each discovered table, fetch its custom columns: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='
')/Attributes?\$select=LogicalName,DisplayName,AttributeType,RequiredLevel&\$filter=IsCustomAttribute eq true" +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='
')/Attributes?\$select=LogicalName,DisplayName,AttributeType,RequiredLevel&\$filter=IsCustomAttribute eq true" ``` ### 2.1 Present Available Tables @@ -80,10 +80,26 @@ Show the user the list of discovered tables with their columns so they can choos ### 3.1 Select Tables + + +> 🚦 **Gate (plan · add-sample-data:3.1.tables):** Multi-select over discovered tables — decides which tables get populated with sample data. +> +> **Trigger:** Phase 2 discovery completed. +> **Why we ask:** Auto-selecting all tables can pollute production-shape tables (e.g. `contact`) with throwaway records. +> **Cancel leaves:** Nothing — no record inserts yet. + Use `AskUserQuestion` to ask which tables they want to populate (use `multiSelect: true`). List all discovered tables as options. ### 3.2 Select Record Count + + +> 🚦 **Gate (plan · add-sample-data:3.2.count):** Pick records-per-table (5 / 10 / 25 / Custom). Custom branch is a follow-up free-text prompt under the same gate. +> +> **Trigger:** Tables selected in 3.1. +> **Why we ask:** Inserting hundreds of records can blow past Dataverse storage / API throttling budgets. +> **Cancel leaves:** Nothing — no record inserts yet. + Use `AskUserQuestion` to ask how many sample records per table: | Option | Description | @@ -158,7 +174,7 @@ Refer to `references/odata-record-patterns.md` for full patterns. For each table, get the entity set name (needed for the API URL): ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='
')?\$select=EntitySetName" +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='
')?\$select=EntitySetName" ``` ### 5.2 Get Picklist Options @@ -166,7 +182,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDe For any picklist/choice columns, query valid option values before insertion: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='
')/Attributes(LogicalName='')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?\$expand=OptionSet" +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='
')/Attributes(LogicalName='')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?\$expand=OptionSet" ``` Use the actual `Value` integers from the option set in your sample data. @@ -176,7 +192,7 @@ Use the actual `Value` integers from the option set in your sample data. Insert records into parent/referenced tables first to capture their IDs: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" POST "" --body '{"cr123_name":"Sample Record","cr123_description":"A sample record for testing"}' --include-headers +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST "" --body '{"cr123_name":"Sample Record","cr123_description":"A sample record for testing"}' --include-headers ``` The `--include-headers` flag includes the `OData-EntityId` response header, which contains the created record ID. Parse the GUID from the response to use in child table lookups. @@ -188,7 +204,7 @@ Store parent record IDs for use in child table lookups. For child/referencing tables, use `@odata.bind` syntax to set lookup fields: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" POST "" --body '{"cr123_name":"Child Record","cr123_ParentId@odata.bind":"/()"}' --include-headers +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST "" --body '{"cr123_name":"Child Record","cr123_ParentId@odata.bind":"/()"}' --include-headers ``` ### 5.5 Track Progress @@ -204,7 +220,7 @@ Track each insertion attempt: The `dataverse-request.js` script handles 401 token refresh internally. For long-running operations (many records), periodically re-run `verify-dataverse-access.js` to confirm the session is still valid: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/verify-dataverse-access.js" +node "${PLUGIN_ROOT}/scripts/verify-dataverse-access.js" ``` **Output**: All approved records inserted with parent-child relationships established @@ -222,14 +238,14 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/verify-dataverse-access.js" For each table that was populated, query the record count: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" GET "?\$count=true&\$top=0" +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET "?\$count=true&\$top=0" ``` The `@odata.count` field in the response gives the total record count. ### 6.2 Record Skill Usage -> Reference: `${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md` +> Reference: `${PLUGIN_ROOT}/references/skill-tracking-reference.md` Follow the skill tracking instructions in the reference to record this skill's usage. Use `--skillName "AddSampleData"`. diff --git a/plugins/power-pages/skills/add-sample-data/references/odata-record-patterns.md b/plugins/power-pages/skills/add-sample-data/references/odata-record-patterns.md index 8f0033f02..623b3fc7b 100644 --- a/plugins/power-pages/skills/add-sample-data/references/odata-record-patterns.md +++ b/plugins/power-pages/skills/add-sample-data/references/odata-record-patterns.md @@ -2,7 +2,7 @@ Reference document for the `add-sample-data` skill. Contains patterns for inserting records, setting lookup bindings, handling different column types, and querying record counts via the Dataverse OData Web API (v9.2). -> **Authentication, error handling, and retry patterns** are in the shared reference: `${CLAUDE_PLUGIN_ROOT}/references/odata-common.md`. Read that file first for headers, token refresh, HTTP status codes, and retry logic. +> **Authentication, error handling, and retry patterns** are in the shared reference: `${PLUGIN_ROOT}/references/odata-common.md`. Read that file first for headers, token refresh, HTTP status codes, and retry logic. --- @@ -13,7 +13,7 @@ Entity set names are required for all record operations. They differ from logica **Endpoint:** `GET {envUrl}/api/data/v9.2/EntityDefinitions(LogicalName='
')?$select=EntitySetName` ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='cr123_project')?\$select=EntitySetName" +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='cr123_project')?\$select=EntitySetName" ``` The response JSON `data.EntitySetName` contains the entity set name (e.g., `"cr123_projects"`). @@ -25,7 +25,7 @@ The response JSON `data.EntitySetName` contains the entity set name (e.g., `"cr1 **Endpoint:** `POST {envUrl}/api/data/v9.2/` ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" POST "cr123_projects" --body '{"cr123_name":"Website Redesign","cr123_description":"Modernize the corporate website with a fresh design","cr123_startdate":"2025-06-15T10:30:00Z","cr123_budget":15000.00,"cr123_isactive":true,"cr123_status":100000000}' +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST "cr123_projects" --body '{"cr123_name":"Website Redesign","cr123_description":"Modernize the corporate website with a fresh design","cr123_startdate":"2025-06-15T10:30:00Z","cr123_budget":15000.00,"cr123_isactive":true,"cr123_status":100000000}' ``` ### Capturing the Created Record ID @@ -33,7 +33,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" POST "cr123_p The record ID is returned in the `OData-EntityId` response header. Use `--include-headers` to capture it: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" POST "cr123_projects" --body '{"cr123_name":"Website Redesign"}' --include-headers +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST "cr123_projects" --body '{"cr123_name":"Website Redesign"}' --include-headers ``` The response JSON includes a `headers` object with the `OData-EntityId` value. Parse the GUID from it to use in subsequent lookups. @@ -133,7 +133,7 @@ Before inserting records with picklist/choice columns, query the valid option va **Endpoint:** `GET {envUrl}/api/data/v9.2/EntityDefinitions(LogicalName='
')/Attributes(LogicalName='')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$expand=OptionSet` ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='cr123_project')/Attributes(LogicalName='cr123_status')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?\$expand=OptionSet" +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET "EntityDefinitions(LogicalName='cr123_project')/Attributes(LogicalName='cr123_status')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?\$expand=OptionSet" ``` The response `data.OptionSet.Options` array contains objects with `Value` (e.g., `100000000`) and `Label.LocalizedLabels[0].Label` (e.g., `"Active"`). @@ -149,7 +149,7 @@ Use these actual `Value` integers in your sample data — never guess option val A task referencing a project: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" POST "cr123_tasks" --body '{"cr123_name":"Design mockups","cr123_duedate":"2025-07-01T00:00:00Z","cr123_ProjectId@odata.bind":"/cr123_projects()"}' --include-headers +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST "cr123_tasks" --body '{"cr123_name":"Design mockups","cr123_duedate":"2025-07-01T00:00:00Z","cr123_ProjectId@odata.bind":"/cr123_projects()"}' --include-headers ``` ### Multiple Lookups @@ -157,7 +157,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" POST "cr123_t A record referencing multiple parent tables: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" POST "" --body '{"cr123_name":"Project Update Meeting","cr123_ProjectId@odata.bind":"/cr123_projects()","cr123_ContactId@odata.bind":"/contacts()"}' --include-headers +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST "" --body '{"cr123_name":"Project Update Meeting","cr123_ProjectId@odata.bind":"/cr123_projects()","cr123_ContactId@odata.bind":"/contacts()"}' --include-headers ``` --- @@ -169,7 +169,7 @@ Verify how many records exist in a table after insertion: **Endpoint:** `GET {envUrl}/api/data/v9.2/?$count=true&$top=0` ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" GET "cr123_projects?\$count=true&\$top=0" +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" GET "cr123_projects?\$count=true&\$top=0" ``` The response `data["@odata.count"]` contains the total record count. @@ -189,7 +189,7 @@ For inserting many records efficiently, use OData batch requests to send multipl Authentication is handled automatically by `dataverse-request.js`. To send a batch request, use POST to the `$batch` endpoint. The script handles auth headers and token refresh internally: ``` -node "${CLAUDE_PLUGIN_ROOT}/scripts/dataverse-request.js" POST "\$batch" --body '' +node "${PLUGIN_ROOT}/scripts/dataverse-request.js" POST "\$batch" --body '' ``` **Body format:** @@ -226,4 +226,4 @@ Content-Type: application/json ## Error Handling -See `${CLAUDE_PLUGIN_ROOT}/references/odata-common.md` for HTTP status codes, error response format, Dataverse error codes, and retry patterns. +See `${PLUGIN_ROOT}/references/odata-common.md` for HTTP status codes, error response format, Dataverse error codes, and retry patterns. diff --git a/plugins/power-pages/skills/add-seo/SKILL.md b/plugins/power-pages/skills/add-seo/SKILL.md index b9ff322e5..4c73a302d 100644 --- a/plugins/power-pages/skills/add-seo/SKILL.md +++ b/plugins/power-pages/skills/add-seo/SKILL.md @@ -9,7 +9,7 @@ allowed-tools: Read, Write, Edit, Grep, Glob, Bash, AskUserQuestion, Task, TaskC model: sonnet --- -> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. +> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. # Add SEO @@ -55,7 +55,7 @@ Read `powerpages.config.json` to get the site name and config. #### 1.3 Detect Framework & Discover Routes -Read `package.json` to determine the framework and locate key files. See `${CLAUDE_PLUGIN_ROOT}/references/framework-conventions.md` for the full framework → public directory → index HTML mapping and route discovery patterns. +Read `package.json` to determine the framework and locate key files. See `${PLUGIN_ROOT}/references/framework-conventions.md` for the full framework → public directory → index HTML mapping and route discovery patterns. Build a list of all routes (e.g., `/`, `/about`, `/contact`, `/blog`). @@ -73,6 +73,8 @@ Build a list of all routes (e.g., `/`, `/about`, `/contact`, `/blog`). ### Actions + + Use `AskUserQuestion` to collect SEO preferences: #### Call 1 @@ -82,6 +84,8 @@ Use `AskUserQuestion` to collect SEO preferences: | What is the production URL for your site? (e.g., ) | Site URL | *(free text — use single generic option so user types via "Other")* | | Which pages should be excluded from search engine indexing? | Exclusions | None — index all pages (Recommended), Admin/auth pages only, Let me specify | + + #### Call 2 | Question | Header | Options | @@ -111,6 +115,14 @@ Present the SEO additions that will be made as a clear, inline summary: 3. **Meta tags to add to index.html** — title, description, viewport, charset, Open Graph, Twitter Card 4. **Favicon** — link tag and placeholder SVG + + +> 🚦 **Gate (plan · add-seo:3.plan-approval):** Final sign-off on the SEO additions before any `robots.txt` / `sitemap.xml` / `index.html` write. +> +> **Trigger:** Phase 3 has presented the full plan inline (robots.txt, sitemap.xml, meta tags, favicon). +> **Why we ask:** SEO assets land on disk with the wrong production URL, wrong exclusions, or unwanted OG tags — fixable but noisy in git history. +> **Cancel leaves:** Nothing — no file writes yet. + After presenting the plan, use `AskUserQuestion` to get approval: | Question | Header | Options | @@ -296,7 +308,7 @@ If a dev server is running (or start one): #### 7.3 Record Skill Usage -> Reference: `${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md` +> Reference: `${PLUGIN_ROOT}/references/skill-tracking-reference.md` Follow the skill tracking instructions in the reference to record this skill's usage. Use `--skillName "AddSeo"`. diff --git a/plugins/power-pages/skills/add-server-logic/SKILL.md b/plugins/power-pages/skills/add-server-logic/SKILL.md index c08b77f5c..ff74b8209 100644 --- a/plugins/power-pages/skills/add-server-logic/SKILL.md +++ b/plugins/power-pages/skills/add-server-logic/SKILL.md @@ -11,7 +11,7 @@ allowed-tools: Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion, Skill, Task model: opus --- -> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. +> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. # Add Server Logic @@ -69,7 +69,7 @@ Read `powerpages.config.json` to get the site name and configuration: ### 1.3 Detect Framework -Read `package.json` to determine the frontend framework (React, Vue, Angular, or Astro). This is needed for Phase 8 (client-side integration guidance). See `${CLAUDE_PLUGIN_ROOT}/references/framework-conventions.md` for the full framework detection mapping. +Read `package.json` to determine the frontend framework (React, Vue, Angular, or Astro). This is needed for Phase 8 (client-side integration guidance). See `${PLUGIN_ROOT}/references/framework-conventions.md` for the full framework detection mapping. ### 1.4 Explore Existing Server Logic and Frontend Code @@ -103,6 +103,14 @@ Look for the `.powerpages-site` folder: > "The `.powerpages-site` folder was not found. Server logic files are stored inside this folder, so the site must be deployed at least once before creating server logic. Would you like to deploy now?" + + +> 🚦 **Gate (plan · add-server-logic:1.5.deploy-first):** `.powerpages-site` missing — server logic files live inside it. Deploy first or stop. +> +> **Trigger:** Phase 1.5 found no `.powerpages-site` directory. +> **Why we ask:** Server logic `.js`/`.yml` files written to a non-existent path won't deploy. +> **Cancel leaves:** Nothing — no server logic files written yet. + Use `AskUserQuestion`: | Question | Options | @@ -168,7 +176,7 @@ If any planned server logic item involves Dataverse operations, check whether th **Step 1 — Fetch custom actions:** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/list-custom-actions.js" "" +node "${PLUGIN_ROOT}/scripts/list-custom-actions.js" "" ``` The script outputs a JSON object with: @@ -182,6 +190,14 @@ Each entry includes: `name`, `displayName`, `description`, `type` (`action` or ` If custom actions are found (`total > 0`), present a summary to the user grouped by binding type (unbound vs. entity-bound) and ask whether any should be used: + + +> 🚦 **Gate (plan · add-server-logic:2.1.2.use-custom-actions):** Custom actions discovered — decide whether to wrap existing Dataverse Custom APIs/Process Actions or build server logic from scratch. Choice changes the Phase 5 implementation shape. +> +> **Trigger:** `list-custom-actions.js` returned at least one entry. +> **Why we ask:** Auto-wrapping could attach the wrong action; auto-skipping duplicates logic that already exists in Dataverse. +> **Cancel leaves:** Nothing — no server logic files written yet. + Use `AskUserQuestion`: | Question | Options | @@ -196,6 +212,8 @@ If the user says **No**, skip to Phase 2.2. If the user says **Yes**, for each server logic item being created, ask which custom action (if any) it should wrap: + + Use `AskUserQuestion` for each server logic item: | Question | Context | @@ -248,6 +266,14 @@ These values will be used in Phase 7 to create the environment variables and sit If secrets were identified in Phase 2.3, ask the user now whether they want to use Azure Key Vault. This decision must happen before Phase 4 so the implementation plan can show the chosen secret management approach. + + +> 🚦 **Gate (plan · add-server-logic:2.3.1.keyvault):** Pick secret-storage mechanism (Key Vault vs plain env var). Choice changes the Phase 4 rendered plan and the Phase 7 implementation pipeline. +> +> **Trigger:** Phase 2.3 identified at least one secret value. +> **Why we ask:** Plain env var creation can expose secrets in solution exports; auto-picking Key Vault forces additional Azure setup. +> **Cancel leaves:** Nothing — no env var definitions written yet. + Use `AskUserQuestion`: | Question | Options | @@ -258,6 +284,8 @@ Record the user's choice — it will be shown in the HTML plan (Phase 4) and exe ### 2.4 Confirm with User + + If the requirements are ambiguous, use `AskUserQuestion` to clarify: | Question | Context | @@ -285,7 +313,7 @@ This step is critical because Server Logic is a preview feature and the SDK surf Use the reference document below as the source of truth for how to discover, classify, fetch, and reconcile Server Logic documentation: -> Reference: `${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-docs.md` +> Reference: `${PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-docs.md` Follow that reference to: @@ -319,7 +347,7 @@ From the fetched docs, extract and note the items that matter for the current ta Build the server logic plan data and render the HTML plan before asking for approval. -> Reference: `${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-plan-data-format.md` +> Reference: `${PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-plan-data-format.md` The rendered plan should summarize: @@ -346,7 +374,7 @@ Create the `docs/` folder if it does not already exist. Keep this HTML file insi Do **not** hand-author the HTML. Use the render script: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/render-serverlogic-plan.js" --output "" --data "" +node "${PLUGIN_ROOT}/scripts/render-serverlogic-plan.js" --output "" --data "" ``` The render script refuses to overwrite existing files. Before calling it, check if the default output path (`/docs/serverlogic-plan.html`) already exists. If it does, choose a new descriptive filename based on context — e.g., `serverlogic-plan-exchange-rate.html`, `serverlogic-plan-apr-2026.html`. Pass the chosen name via `--output`. @@ -367,6 +395,14 @@ Do not restate the per-server-logic breakdown, rationale, role assignments, or f ### 4.4 Confirm with User + + +> 🚦 **Gate (plan · add-server-logic:4.4.plan-approval):** Final sign-off on the rendered HTML plan before Phase 5 writes any `.serverlogic.yml` / `.js` files or Phase 7 creates env vars. +> +> **Trigger:** Phase 4.2 rendered the HTML plan; Phase 4.3 surfaced the CLI summary. +> **Why we ask:** Server logic files committed under wrong names / wrong roles; env var definitions created against the wrong secret-storage mode. +> **Cancel leaves:** Nothing — no server logic files written yet. + Use `AskUserQuestion`: | Question | Options | @@ -686,7 +722,7 @@ function get() { When a function returns the result of a `Server.Connector.Dataverse.*` method, the client sees a double-wrapped payload — the most common cause of broken frontend integrations. Before writing the function, pick one of three response shapes and record the choice for Phase 9: **Approach A — raw passthrough** (return the connector result as-is), **Approach B — envelope that wraps the connector result** (return `{ status, data: result }` without unwrapping `Body`), or **Approach C — fully normalized** (parse `Body` server-side and return a feature-specific shape — recommended for non-generic endpoints). -See `${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/frontend-integration-reference.md` → "Dataverse Connector Response Format" for the double-wrapping explanation, the `CreateRecord` / `entityid` header behavior, and server- and client-side examples for each shape. +See `${PLUGIN_ROOT}/skills/add-server-logic/references/frontend-integration-reference.md` → "Dataverse Connector Response Format" for the double-wrapping explanation, the `CreateRecord` / `entityid` header behavior, and server- and client-side examples for each shape. #### Referencing Secrets in Code @@ -707,7 +743,7 @@ Do **not** duplicate Microsoft Learn SDK usage patterns inline in this skill. Us For each approved server logic item where the plan status is `create`, generate the metadata file with the deterministic writer script instead of hand-authoring the YAML. The script generates the UUID, writes the fields in the correct order, and returns the created file path as JSON. **Skip this step for `update` / `reuse` items** — the YAML already exists and should be updated manually if needed. ```bash -node "${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/scripts/create-serverlogic-metadata.js" --projectRoot "" --name "" --displayName "" --description "" --webRoleIds "" +node "${PLUGIN_ROOT}/skills/add-server-logic/scripts/create-serverlogic-metadata.js" --projectRoot "" --name "" --displayName "" --description "" --webRoleIds "" ``` The generated `/.powerpages-site/server-logic//.serverlogic.yml` file has this structure: @@ -787,7 +823,7 @@ Extract the entity set name (first argument) from each method call. Build a mapp ### 6.2 Use the Table Permissions Architect -When any approved server logic item uses `Server.Connector.Dataverse`, invoke the `table-permissions-architect` agent at `${CLAUDE_PLUGIN_ROOT}/agents/table-permissions-architect.md` to determine and create the required table permissions. +When any approved server logic item uses `Server.Connector.Dataverse`, invoke the `table-permissions-architect` agent at `${PLUGIN_ROOT}/agents/table-permissions-architect.md` to determine and create the required table permissions. **Prompt:** @@ -837,7 +873,7 @@ If the user chose Azure Key Vault in Phase 2.3.1: **Step 1 — List available Key Vaults:** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/list-azure-keyvaults.js" +node "${PLUGIN_ROOT}/scripts/list-azure-keyvaults.js" ``` The script outputs a JSON array of Key Vaults (`name`, `resourceGroup`, `location`) from the user's Azure subscription. @@ -846,6 +882,8 @@ The script outputs a JSON array of Key Vaults (`name`, `resourceGroup`, `locatio If Key Vaults were found, present the list and ask which one to use: + + Use `AskUserQuestion`: | Question | Context | @@ -854,6 +892,14 @@ Use `AskUserQuestion`: If **no Key Vaults are found**, ask the user how to proceed: + + +> 🚦 **Gate (plan · add-server-logic:7.2a.no-vaults):** No Key Vaults found in the user's subscription — create one or fall back to plain env vars. Branches the secret-storage flow. +> +> **Trigger:** Phase 2.3.1 chose Key Vault but `list-azure-keyvaults.js` returned an empty list. +> **Why we ask:** Auto-creating a Key Vault provisions Azure resources without explicit consent; auto-falling-back stores secrets as plain env vars after the user explicitly opted in to Key Vault. +> **Cancel leaves:** Nothing — no Azure or Dataverse writes yet. + Use `AskUserQuestion`: | Question | Options | @@ -862,6 +908,8 @@ Use `AskUserQuestion`: **If "Create a new Key Vault"**: Ask for a vault name, resource group, and location, then create it: + + Use `AskUserQuestion`: | Question | Context | @@ -869,7 +917,7 @@ Use `AskUserQuestion`: | What name, resource group, and Azure region would you like for the new Key Vault? | Vault names must be 3-24 characters, globally unique, start with a letter, and contain only alphanumerics and hyphens. Suggest a name based on the project/site name. | ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-azure-keyvault.js" \ +node "${PLUGIN_ROOT}/scripts/create-azure-keyvault.js" \ --name "" \ --resourceGroup "" \ --location "" @@ -893,7 +941,7 @@ Present the commands as a numbered list the user can copy and run. Use the stdin For each secret, run the following command (replacing with the actual value): 1. : - printf '%s' '' | node "${CLAUDE_PLUGIN_ROOT}/scripts/store-keyvault-secret.js" \ + printf '%s' '' | node "${PLUGIN_ROOT}/scripts/store-keyvault-secret.js" \ --vaultName "" \ --secretName "" ``` @@ -927,7 +975,7 @@ Tell the user the Secret Identifier URI looks like `https://.vault.a After the user shares the `secretUri` output from each command, create an environment variable definition in Dataverse that references the Key Vault secret. Use the `secret` type: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "" \ +node "${PLUGIN_ROOT}/scripts/create-environment-variable.js" "" \ --schemaName "" \ --displayName "" \ --type "secret" \ @@ -939,7 +987,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "" For each environment variable, create a site setting YAML that maps to it: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-site-setting.js" \ +node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \ --projectRoot "" \ --name "" \ --envVarSchema "" @@ -956,7 +1004,7 @@ If the user chose not to use Azure Key Vault: For each secret identified in Phase 2.3, create the environment variable in Dataverse with a placeholder value: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "" \ +node "${PLUGIN_ROOT}/scripts/create-environment-variable.js" "" \ --schemaName "" \ --displayName "" \ --value "PLACEHOLDER_SET_ACTUAL_VALUE" @@ -965,7 +1013,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "" **Step 2 — Create site setting for the environment variable:** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-site-setting.js" \ +node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \ --projectRoot "" \ --name "" \ --envVarSchema "" @@ -1017,7 +1065,7 @@ The following site settings control server logic behavior. Only create settings Use the existing site setting creation script: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-site-setting.js" --projectRoot "" --name "ServerLogic/AllowedDomains" --value "api.example.com,api.other.com" --description "Restrict server logic external API calls to these domains" +node "${PLUGIN_ROOT}/scripts/create-site-setting.js" --projectRoot "" --name "ServerLogic/AllowedDomains" --value "api.example.com,api.other.com" --description "Restrict server logic external API calls to these domains" ``` ### 8.2 Git Commit @@ -1040,6 +1088,14 @@ Server logic creates the backend — but without frontend code to call it, the e ### 9.1 Ask User About Integration Scope + + +> 🚦 **Gate (plan · add-server-logic:9.1.frontend-scope):** Decide whether the skill also wires the server logic into the frontend UI or stops at the backend. +> +> **Trigger:** Phase 8 completed (server logic deployed-ready). +> **Why we ask:** Auto-integrating mutates UI files the user wanted to handle themselves; auto-skipping leaves the endpoints unreachable from the app. +> **Cancel leaves:** Nothing — server logic backend is already on disk; this prompt only decides frontend follow-through. + Use `AskUserQuestion`: | Question | Options | @@ -1052,7 +1108,7 @@ Use `AskUserQuestion`: Use the reference below for the frontend integration approach, examples, and framework-specific patterns: -> Reference: `${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/frontend-integration-reference.md` +> Reference: `${PLUGIN_ROOT}/skills/add-server-logic/references/frontend-integration-reference.md` Based on the Explore agent's findings from Phase 1.4 and the approved plan, choose the integration approach from that reference and apply it consistently across all server logic endpoints being wired into the frontend. @@ -1146,7 +1202,7 @@ Use the frontend integration reference from Phase 9 for the exact calling patter ### 11.1 Record Skill Usage -> Reference: `${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md` +> Reference: `${PLUGIN_ROOT}/references/skill-tracking-reference.md` Follow the skill tracking instructions in the reference to record this skill's usage. Use `--skillName "AddServerLogic"`. @@ -1170,12 +1226,28 @@ Present a summary of everything that was done: ### 11.3 Ask to Deploy + + +> 🚦 **Gate (plan · add-server-logic:11.3.deploy):** Post-implementation deploy prompt — server logic endpoints aren't reachable until deployed. +> +> **Trigger:** All server logic artifacts written and committed. +> **Why we ask:** Auto-deploy picks wrong env. +> **Cancel leaves:** Nothing — artifacts stay on disk; no deploy fired. + Use `AskUserQuestion`: | Question | Options | |----------|---------| | The server logic work is ready. To make it live, the site needs to be deployed. Would you like to deploy now? | Yes, deploy now (Recommended), No, I'll deploy later | + + +> 🚦 **Gate (plan · add-server-logic:11.3.test):** Post-deploy validation prompt — invokes `/test-site` to exercise the new endpoints live. +> +> **Trigger:** Deploy from the previous gate succeeded. +> **Why we ask:** Skipping is harmless (manual test still possible); auto-invoking `/test-site` adds runtime. +> **Cancel leaves:** Nothing — deploy has already completed. + **If "Yes, deploy now"**: Invoke the `/deploy-site` skill to deploy the site. After deployment succeeds, use `AskUserQuestion`: diff --git a/plugins/power-pages/skills/add-server-logic/references/server-logic-plan-data-format.md b/plugins/power-pages/skills/add-server-logic/references/server-logic-plan-data-format.md index f810f4f8e..94a18154d 100644 --- a/plugins/power-pages/skills/add-server-logic/references/server-logic-plan-data-format.md +++ b/plugins/power-pages/skills/add-server-logic/references/server-logic-plan-data-format.md @@ -153,7 +153,7 @@ When `SECRETS_DATA` is `null` or `useKeyVault` is `false`, the banner and per-ca Do **not** write the HTML manually. Use the render script: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/render-serverlogic-plan.js" --output "" --data "" +node "${PLUGIN_ROOT}/scripts/render-serverlogic-plan.js" --output "" --data "" ``` The render script refuses to overwrite existing files. Before calling it, check if the default output path (`/docs/serverlogic-plan.html`) already exists. If it does, choose a new descriptive filename based on context — e.g., `serverlogic-plan-exchange-rate.html`, `serverlogic-plan-apr-2026.html`. Pass the chosen name via `--output`. diff --git a/plugins/power-pages/skills/audit-permissions/SKILL.md b/plugins/power-pages/skills/audit-permissions/SKILL.md index 505ad79c4..caa40ca44 100644 --- a/plugins/power-pages/skills/audit-permissions/SKILL.md +++ b/plugins/power-pages/skills/audit-permissions/SKILL.md @@ -11,7 +11,7 @@ allowed-tools: Read, Write, Bash, Glob, Grep, AskUserQuestion, TaskCreate, TaskU model: opus --- -> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. +> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. # Audit Permissions @@ -95,7 +95,7 @@ Build a map of: which tables are referenced in code, which CRUD operations are p Run the shared validator against the existing site: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/validate-permissions-schema.js" --projectRoot "" +node "${PLUGIN_ROOT}/scripts/validate-permissions-schema.js" --projectRoot "" ``` Parse the JSON output and carry the findings into the audit. Treat: @@ -109,7 +109,7 @@ These findings should be included in the final audit report even if the later co After Step 3.1 determines the environment URL, if this audit is running locally with Dataverse access available, rerun the shared validator with live relationship verification enabled and merge any additional findings: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/validate-permissions-schema.js" --projectRoot "" --validate-dataverse-relationships --envUrl "" +node "${PLUGIN_ROOT}/scripts/validate-permissions-schema.js" --projectRoot "" --validate-dataverse-relationships --envUrl "" ``` Use this Dataverse-backed relationship validation only for local runs. Do **not** require it in CI or other offline contexts. @@ -133,7 +133,7 @@ Extract the `Environment URL` (e.g., `https://org12345.crm.dynamics.com`) and us For each table that has permissions with `create` or `write` enabled, use the lookup query script: ```bash -node "${CLAUDE_PLUGIN_ROOT}/skills/audit-permissions/scripts/query-table-lookups.js" --envUrl "" --table "" +node "${PLUGIN_ROOT}/skills/audit-permissions/scripts/query-table-lookups.js" --envUrl "" --table "" ``` The script returns a JSON array of `{ logicalName, targets }` for each lookup column. Capture this output for the maps described below. @@ -156,7 +156,7 @@ Both maps are used in Sections H and H2: For tables with parent-scope permissions, verify the relationship names using the relationship query script: ```bash -node "${CLAUDE_PLUGIN_ROOT}/skills/audit-permissions/scripts/query-table-relationships.js" --envUrl "" --table "" +node "${PLUGIN_ROOT}/skills/audit-permissions/scripts/query-table-relationships.js" --envUrl "" --table "" ``` The script returns a JSON array of `{ schemaName, referencedEntity, referencingEntity, referencingAttribute }`. Use `schemaName` to validate the `parentrelationship` value in parent-scope permissions. @@ -466,7 +466,7 @@ Write a temporary JSON data file (e.g., `/audit-data.json`) with the Run the render script (it creates the output directory if needed): ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/render-audit-report.js" --output "" --data "" +node "${PLUGIN_ROOT}/scripts/render-audit-report.js" --output "" --data "" ``` The render script refuses to overwrite existing files. Before calling it, check if the default output path (`/docs/permissions-audit.html`) already exists. If it does, choose a new descriptive filename based on context — e.g., `permissions-audit-apr-2026.html`, `permissions-audit-post-migration.html`. Pass the chosen name via `--output`. @@ -483,12 +483,20 @@ Open the actual output path in the user's default browser. ### 6.1 Record Skill Usage -> Reference: `${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md` +> Reference: `${PLUGIN_ROOT}/references/skill-tracking-reference.md` Follow the skill tracking instructions in the reference to record this skill's usage. Use `--skillName "AuditPermissions"`. ### 6.2 Present Summary + + +> 🚦 **Gate (plan · audit-permissions:6.fix-offer):** Offer to apply auto-fixes for critical/warning findings. The audit report has already been written; declining here just leaves the HTML report in place — no Dataverse / filesystem mutation. +> +> **Trigger:** Phase 6 has tallied findings and the HTML report is saved. +> **Why we ask:** Tooling could silently invoke the table-permissions-architect agent — accept-by-default would write or mutate permission YAML against the user's intent. +> **Cancel leaves:** Nothing — the audit report stays at its saved path. No web-role / table-permission files written. + Present a summary to the user: 1. **Critical findings count** — these need immediate attention diff --git a/plugins/power-pages/skills/audit-permissions/scripts/query-table-relationships.js b/plugins/power-pages/skills/audit-permissions/scripts/query-table-relationships.js index a5bdf93f3..ca98582dc 100644 --- a/plugins/power-pages/skills/audit-permissions/scripts/query-table-relationships.js +++ b/plugins/power-pages/skills/audit-permissions/scripts/query-table-relationships.js @@ -1,19 +1,21 @@ #!/usr/bin/env node +// Thin CLI wrapper over scripts/lib/query-table-relationships.js. // Queries Dataverse for one-to-many relationships on a given table. // Returns JSON array of { schemaName, referencedEntity, referencingEntity, referencingAttribute }. // // Usage: // node query-table-relationships.js --envUrl --table // -// Output (stdout): JSON array -// [{ "schemaName": "cr4fc_order_orderitem", "referencedEntity": "cr4fc_order", "referencingEntity": "cr4fc_orderitem", "referencingAttribute": "cr4fc_orderid" }] +// Output (stdout): JSON array (OneToMany relationships only — the audit-permissions +// relationship-scope validation consumes schemaName + referencedEntity). // // Exit codes: // 0 = success (JSON on stdout) // 1 = error (message on stderr) -const { getAuthToken, makeRequest } = require('../../../scripts/lib/validation-helpers'); +const { getAuthToken } = require('../../../scripts/lib/validation-helpers'); +const { fetchTableRelationships } = require('../../../scripts/lib/query-table-relationships'); const args = process.argv.slice(2); function getArg(name) { @@ -37,29 +39,10 @@ if (!envUrl || !table) { } try { - const result = await makeRequest({ - url: `${envUrl}/api/data/v9.2/EntityDefinitions(LogicalName='${table}')/OneToManyRelationships?$select=SchemaName,ReferencedEntity,ReferencingEntity,ReferencingAttribute`, - headers: { - Authorization: `Bearer ${token}`, - Accept: 'application/json', - }, - timeout: 15000, - }); - - if (result.error || result.statusCode !== 200) { - process.stderr.write(`API error (${result.statusCode}): ${result.error || result.body}\n`); - process.exit(1); - } - - const parsed = JSON.parse(result.body); - const rels = (parsed.value || []).map(r => ({ - schemaName: r.SchemaName, - referencedEntity: r.ReferencedEntity, - referencingEntity: r.ReferencingEntity, - referencingAttribute: r.ReferencingAttribute, - })); - - process.stdout.write(JSON.stringify(rels, null, 2) + '\n'); + // OneToMany errors propagate here (preserves the original exit-1-on-API-error + // behavior); ManyToMany is best-effort inside the lib and unused by this CLI. + const { oneToMany } = await fetchTableRelationships(envUrl, table, token); + process.stdout.write(JSON.stringify(oneToMany, null, 2) + '\n'); } catch (err) { process.stderr.write(`Request failed: ${err.message}\n`); process.exit(1); diff --git a/plugins/power-pages/skills/configure-env-variables/SKILL.md b/plugins/power-pages/skills/configure-env-variables/SKILL.md index e355add2b..c1a2d5c61 100644 --- a/plugins/power-pages/skills/configure-env-variables/SKILL.md +++ b/plugins/power-pages/skills/configure-env-variables/SKILL.md @@ -14,7 +14,7 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, Task model: opus --- -> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. +> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. # configure-env-variables @@ -53,7 +53,7 @@ When `inExecution.status` is anything other than `"active"` (`"not-running"`, `" **Step 1 — Run the gate helper.** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/check-alm-plan.js" --projectRoot "." +node "${PLUGIN_ROOT}/scripts/lib/check-alm-plan.js" --projectRoot "." ``` The helper returns JSON with `{ exists, deferred, stale, staleness: { reason, detail }, generatedAt, planStatus, ... }`. Pass `--envUrl`, `--token`, `--solutionId` once Phase 1 has acquired them if you also want a freshness check; otherwise the helper does an existence-only check, which is sufficient for the gate decision below. @@ -80,7 +80,7 @@ The helper returns JSON with `{ exists, deferred, stale, staleness: { reason, de |---|---|---| | 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 with the pre-classified `siteSettings` already passed via `docs/alm/alm-plan-context.json`. +- **Yes (Recommended)** → invoke `/power-pages:plan-alm`. It builds the plan and returns — `plan-alm` is a planner and does not deploy. This skill then re-runs the Phase 0 check (now `exists:true`) and proceeds to Phase 1, picking up the pre-classified `siteSettings` from `docs/alm/alm-plan-context.json`. - **Continue without a plan** → set `BYPASSED_PLAN_GATE = true` and proceed to Phase 1. - **Cancel** → exit cleanly. @@ -116,7 +116,7 @@ ls .powerpages-site/site-settings/ # list all site setting YAML files **1.2 Acquire token and verify prerequisites:** ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" \ +node "${PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" \ --envUrl "{devEnvUrl}" \ --require-manifest ``` @@ -170,9 +170,9 @@ Ask via `AskUserQuestion`: > N. I'll type my own setting names" For each selected setting, ask for: -1. **Env var schema name** — generate via `${CLAUDE_PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.js` (single source of truth shared with `setup-solution`): +1. **Env var schema name** — generate via `${PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.js` (single source of truth shared with `setup-solution`): ```bash - node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.js" \ + node "${PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.js" \ --publisherPrefix "{publisherPrefix}" --settingName "{settingName}" ``` Output: `{ schemaName, sanitized }`. The canonical rule is `{prefix}_{settingName.replace(/[^A-Za-z0-9]+/g,'_').toLowerCase()}` — e.g. `Authentication/Registration/LocalLoginEnabled` becomes `ids_authentication_registration_localloginenabled`. Do NOT inline a custom rule here: setup-solution emits schema names from this helper, and configure-env-variables MUST match what setup-solution already created (otherwise the link to the existing site setting fails). The user can override the suggestion if they have a reason, but the default must come from the helper. @@ -204,7 +204,7 @@ Definition-only flow — per-stage values come later from `deployment-settings.j **3.A.1 Check and create if needed** using `create-env-var-definition.js` (the script checks for an existing definition by `schemaName` before posting): ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/create-env-var-definition.js" \ +node "${PLUGIN_ROOT}/scripts/lib/create-env-var-definition.js" \ --envUrl "{devEnvUrl}" \ --token "{TOKEN}" \ --schemaName "{schemaName}" \ @@ -263,7 +263,7 @@ Dataverse / the Power Platform Pipelines handler accept exactly three formats fo When the user has already stored the secret in Azure Key Vault and has a Secret Identifier URI in canonical form, use the atomic deep-insert path — the same flow `add-server-logic` Phase 7.2a uses: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "{devEnvUrl}" \ +node "${PLUGIN_ROOT}/scripts/create-environment-variable.js" "{devEnvUrl}" \ --schemaName "{schemaName}" \ --displayName "{displayName}" \ --type secret \ @@ -272,14 +272,14 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "{devEnvUrl} This script POSTs a single deep-insert that creates the `environmentvariabledefinition` (type 100000005) AND the `environmentvariablevalues` row with the Key Vault URI in `value` — Dataverse resolves the secret at runtime by dereferencing the URI. The script is ALM-aware and adds the new definition to the target solution via `AddSolutionComponent` (same `resolve-target-solution.js` resolution order as the rest of the family). -> **Cross-reference: see `${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/SKILL.md` Phase 7.2a** for the full Key Vault end-to-end: vault selection (`list-azure-keyvaults.js` / `create-azure-keyvault.js`), secret storage (`store-keyvault-secret.js` with stdin to keep the secret out of the conversation), and the URI handoff back to env-var creation. The implementation is identical; we re-use the same helper scripts. +> **Cross-reference: see `${PLUGIN_ROOT}/skills/add-server-logic/SKILL.md` Phase 7.2a** for the full Key Vault end-to-end: vault selection (`list-azure-keyvaults.js` / `create-azure-keyvault.js`), secret storage (`store-keyvault-secret.js` with stdin to keep the secret out of the conversation), and the URI handoff back to env-var creation. The implementation is identical; we re-use the same helper scripts. ### 3.C — Secret env vars without a Key Vault URI (legacy / deferred) When the user has not chosen Key Vault yet or hasn't stored the secret, create the definition with an empty value placeholder and instruct the user to wire the value via Power Platform Admin Center after import: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/create-env-var-definition.js" \ +node "${PLUGIN_ROOT}/scripts/lib/create-env-var-definition.js" \ --envUrl "{devEnvUrl}" \ --token "{TOKEN}" \ --schemaName "{schemaName}" \ @@ -302,7 +302,7 @@ Track created env var IDs: `{ schemaName, envVarDefId, siteSettingName, devValue For each site setting to link, run `link-site-setting-to-env-var.js` (HAR-confirmed PATCH via v9.0 API — no UI step required): ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/link-site-setting-to-env-var.js" \ +node "${PLUGIN_ROOT}/scripts/lib/link-site-setting-to-env-var.js" \ --envUrl "{devEnvUrl}" \ --token "{TOKEN}" \ --siteSettingId "{siteSettingId}" \ @@ -376,7 +376,7 @@ Before persisting `deployment-settings.json` to disk, validate every entry again Catching invalid values **at write time** is the difference between a sub-second hard stop and a hours-long blind alley. ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/validate-deployment-settings.js" \ +node "${PLUGIN_ROOT}/scripts/lib/validate-deployment-settings.js" \ --settingsFile "./deployment-settings.json" \ --envUrl "{devEnvUrl}" \ --token "{token}" @@ -414,7 +414,7 @@ GET {devEnvUrl}/api/data/v9.2/solutioncomponents?$filter=_solutionid_value eq {s **7.2b Verify values landed on dev env.** After creating the definitions + values, query the dev env to confirm each `environmentvariablevalues` row exists. The shared helper `scripts/lib/verify-env-var-values.js` does this read-only check: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/verify-env-var-values.js" \ +node "${PLUGIN_ROOT}/scripts/lib/verify-env-var-values.js" \ --envUrl "{devEnvUrl}" \ --schemaNames "{comma-separated schema names just created}" ``` @@ -424,7 +424,7 @@ Capture stdout as JSON. If `summary.landed === summary.total`, the local definit **7.2b.bump Bump source solution version + manifest sync.** Creating new env var definitions and adding them to the solution via `AddSolutionComponent` modifies `solutions.modifiedon`. Bump the patch segment so downstream skills see a strictly-increasing version label AND the local `.solution-manifest.json` tracks the change: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/bump-solution-version.js" \ +node "${PLUGIN_ROOT}/scripts/lib/bump-solution-version.js" \ --envUrl "{devEnvUrl}" \ --token "{token}" \ --uniqueName "{solutionUniqueName}" \ @@ -436,7 +436,7 @@ The helper returns `{ previous, next, bumped: true, manifestUpdated, manifestUpd **7.2c Refresh the post-config env var snapshot.** Re-run the discovery helper to write `docs/alm/last-env-vars.json` with the freshly-created definitions. Without this, the rendered ALM plan's Env Variables tab stays at whatever setup-solution last wrote — newly-created definitions don't appear until plan-alm runs again. The refresh helper invoked at the end of this phase ingests this sidecar into `planData.envVars[]` AND mirrors it over to `docs/alm/alm-env-vars.json` so both snapshots stay current: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/discover-env-var-definitions.js" \ +node "${PLUGIN_ROOT}/scripts/lib/discover-env-var-definitions.js" \ --envUrl "{devEnvUrl}" \ --publisherPrefix "{publisherPrefix}" \ --websiteRecordId "{websiteRecordId}" \ @@ -478,10 +478,23 @@ Next steps: **7.5 Record skill usage:** -> Reference: `${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md` +> Reference: `${PLUGIN_ROOT}/references/skill-tracking-reference.md` Follow the skill tracking instructions in the reference to record this skill's usage. Use `--skillName "ConfigureEnvVariables"`. +**7.5b Refresh the ALM plan (if one exists):** + +```bash +node "${PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \ + --projectRoot "." \ + --phase configure-env-variables \ + --render +``` + +The helper re-reads `docs/alm/last-env-vars.json` so newly-created definitions appear in `planData.envVars[]`, backfills per-stage values from `deployment-settings.json` into the "Values by Environment" matrix, zeroes `plannedEnvVarCount`, stamps `LAST_SYNC_AT`, and re-renders `docs/alm-plan.html`. When `docs/.alm-plan-data.json` is absent (standalone invocation, not part of an ALM plan), the helper returns `ok:false` as a soft no-op — safe to run unconditionally. + +**Point the user at the next step (user-driven sequencing).** The helper's stdout JSON includes `nextStep: { name, skill: string | null } | null`. When non-null, branch on `skill`: when `skill` is non-null, tell the user *"Plan updated. Next in your plan: **{nextStep.name}** → run `{nextStep.skill}` when you're ready."*; when `skill` is `null` (an internal step such as Finalize, no user command), name the step only — *"Plan updated. Next in your plan: **{nextStep.name}**."* — and never print `run null`. When `null` or the helper returned `ok:false`, say nothing about a next step. **Never auto-invoke the next skill** — the user drives execution. + ## Key Decision Points (Wait for User) | Phase | Decision | Options | diff --git a/plugins/power-pages/skills/create-site/SKILL.md b/plugins/power-pages/skills/create-site/SKILL.md index 6ec8fd9b9..799c49531 100644 --- a/plugins/power-pages/skills/create-site/SKILL.md +++ b/plugins/power-pages/skills/create-site/SKILL.md @@ -11,7 +11,7 @@ allowed-tools: Read, Write, Edit, Grep, Glob, Bash, WebSearch, AskUserQuestion, model: opus --- -> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. +> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. # Create Power Pages Code Site @@ -35,6 +35,8 @@ Guide the user through creating a complete, production-quality Power Pages code ## Live Preview Status Protocol + + While the scaffold loading screen is visible (from Phase 2.6 until the Home page itself is replaced in Phase 5), the loader polls `GET /scaffold-status.json` every 1.5 seconds. The `message` you write into `/public/scaffold-status.json` appears as the label under the progress bar, and `awaitingInput` controls the "waiting for your input" banner. The decorative spinner above the progress bar continues its built-in phrase cycle; keep the progress-bar label current so the loader still reflects what is actually happening. **Why this matters**: When the browser with the loader takes over the user's screen, a prompt in the terminal can sit unanswered for a long time because the user doesn't realize anything is waiting. The banner makes it obvious. @@ -70,6 +72,14 @@ Write the file with the `Write` tool (atomic overwrite). You do not need to read **Actions**: + + +> 🚦 **Gate (plan · create-site:1.purpose):** Multi-question prompt collecting site name, framework, purpose, audience, and target directory. Determines what gets scaffolded. Fires only on the "site purpose unclear" branch (step 3 below). +> +> **Trigger:** Phase 1 when site purpose was not provided in `$ARGUMENTS`. +> **Why we ask:** Wrong framework picked → wrong template copied into the wrong directory; cleanup is annoying. +> **Cancel leaves:** Nothing — no scaffolding has started yet. + 1. Create todo list with all 8 phases (see [Progress Tracking](#progress-tracking) table) 2. If site purpose is clear from arguments: - Summarize understanding @@ -114,28 +124,28 @@ Write the file with the `Write` tool (atomic overwrite). You do not need to read > **The scaffold is a temporary branded loading screen** — it shows a Power Pages animated "Building your site" experience with orbiting elements, status messages, and feature cards. Its only purpose is to get the dev server running quickly so the user has something to look at while you plan and build. **During Phase 5 (Implementation), the entire scaffold — including theme.css, Layout, Home page, and all placeholder components — is completely replaced** with the user's actual site: their chosen typography, color palette, pages, components, and navigation. Do NOT try to build on top of the loading screen; replace it entirely. -> See `${CLAUDE_PLUGIN_ROOT}/references/framework-conventions.md` for the full framework → build tool → router → output path mapping. +> See `${PLUGIN_ROOT}/references/framework-conventions.md` for the full framework → build tool → router → output path mapping. **Actions**: ### 2.1 Copy Template -> `${CLAUDE_PLUGIN_ROOT}` is already resolved to the plugin's absolute path at runtime. Use it directly in Glob/Read paths — do NOT search for the plugin directory. +> `${PLUGIN_ROOT}` is already resolved to the plugin's absolute path at runtime. Use it directly in Glob/Read paths — do NOT search for the plugin directory. Read and copy all files from the matching asset template to the project directory: | Framework | Asset Directory | |-----------|----------------| -| React | `${CLAUDE_PLUGIN_ROOT}/skills/create-site/assets/react/` | -| Vue | `${CLAUDE_PLUGIN_ROOT}/skills/create-site/assets/vue/` | -| Angular | `${CLAUDE_PLUGIN_ROOT}/skills/create-site/assets/angular/` | -| Astro | `${CLAUDE_PLUGIN_ROOT}/skills/create-site/assets/astro/` | +| React | `${PLUGIN_ROOT}/skills/create-site/assets/react/` | +| Vue | `${PLUGIN_ROOT}/skills/create-site/assets/vue/` | +| Angular | `${PLUGIN_ROOT}/skills/create-site/assets/angular/` | +| Astro | `${PLUGIN_ROOT}/skills/create-site/assets/astro/` | Use `Glob` to discover all files in the asset directory, `Read` each file, then `Write` to the project directory preserving the relative path structure. **Also copy the shared loader icon** that the scaffold references from its CSS (`url('/power-pages-icon.png')`): -`Read` the binary file `${CLAUDE_PLUGIN_ROOT}/skills/create-site/assets/shared/power-pages-icon.png` and `Write` it to `/public/power-pages-icon.png`. (All four supported frameworks serve `public/` at the web root, so the same `/power-pages-icon.png` URL works for every framework.) +`Read` the binary file `${PLUGIN_ROOT}/skills/create-site/assets/shared/power-pages-icon.png` and `Write` it to `/public/power-pages-icon.png`. (All four supported frameworks serve `public/` at the web root, so the same `/power-pages-icon.png` URL works for every framework.) **Seed the live status file** so the loader shows a real message the moment it mounts. `Write` `/public/scaffold-status.json`: @@ -217,6 +227,14 @@ Immediately after the dev server starts, verify the scaffold is working: **Goal**: Determine what pages, components, and design elements the site needs — while the user previews the running scaffold + + +> 🚦 **Gate (plan · create-site:3.requirements):** Three sub-prompts (features multi-select, aesthetic, mood) — shape the Phase 4 plan and the Phase 5 implementation. Fires at step 2 of the action list below. +> +> **Trigger:** Phase 3 entry; scaffold loader is up. +> **Why we ask:** Wrong feature set / aesthetic gets baked into the rendered plan — the Phase 4.7 gate would still catch most errors, but it's wasteful to defer the catch. +> **Cancel leaves:** Nothing — scaffold loader files are throwaway artifacts replaced wholesale in Phase 5. + **Actions**: 1. **Raise the "awaiting input" banner** so the user notices the terminal prompt even while the browser loader is full-screen. `Write` `/public/scaffold-status.json`: @@ -244,9 +262,42 @@ Immediately after the dev server starts, verify the scaffold is working: > > **If you include an Authentication feature option**, describe it generically as "Login/signup for tracking application status" or similar. Do NOT mention a specific identity provider (e.g., "Entra ID", "SAML", "Google") in the feature description — the `/power-pages:setup-auth` skill will ask the user which provider they want. -3. Read the design aesthetics reference: `${CLAUDE_PLUGIN_ROOT}/skills/create-site/references/design-aesthetics.md` -4. **Map aesthetic + mood to design choices** using the Aesthetic x Mood Mapping table from the design reference. Record the chosen font direction, color direction, and motion direction. -5. Analyze requirements and determine needed components. Present component plan to user as a table: +3. **AI Component Planning** — Based on Phase 1 answers (site name, purpose, audience) and the feature selection above, propose which of the Power Pages generative-AI summarization APIs the site might use. The site itself does not depend on them — the page ships with reserved slots and runs without AI; `/add-ai-webapi` populates the slots later when the user is ready. Use `AskUserQuestion` with multi-select to let the user opt in: + + | Question | Header | Options | + |----------|--------|---------| + | Which AI summarization features should the site have? (multi-select — each can be added later with `/add-ai-webapi`) | AI Summaries | *(generate 2-4 context-aware options plus "None for now")* | + + > **Options are NOT hardcoded.** Infer relevant AI summary features from Phase 1 and the features picked above. Examples: + > - "HR Dashboard" + Leave Requests feature → "Data summarization for leave requests", "Search summary on the knowledge base" + > - "Contoso Portal" + Knowledge Base → "Search summary on site-wide search", "Data summarization for articles" + > - "Customer Self-Service" + Support Cases / Incidents → "Data summarization for support cases (Microsoft-shipped recipe)", "Data summarization for attached knowledge articles" + > + > Treat the standard `incident` table like any other Dataverse table — propose Data + > Summarization for it when the site handles support cases, but don't force the Microsoft-shipped + > recipe (`$select=description,title` + the portal-comments expand) unless that genuinely fits + > the user's UX. A custom case-like table or a different facet of the standard incident is a + > regular Data Summarization pick. Always include **None for now** so the user can defer. Do + > NOT integrate the APIs in this skill — only record the user's picks so Phase 4's plan can + > mention them and Phase 8 can suggest `/add-ai-webapi` as a recommended next step. + > + > Capture the selection in memory as `AI_SUMMARY_PICKS` — a list of one or more of: `search-summary`, `data-summarization`. + +4. **Map picks to target pages.** For each entry in `AI_SUMMARY_PICKS`, decide which page will carry the AI surface and store the mapping as `AI_SUMMARY_PLACEMENTS`. This is what Phase 4 shows the user and what Phase 5 reserves slots for. Use the feature selection from step 2 — and treat this mapping as an *input* to the page list Claude proposes in step 7: if a pick has no natural target page, add one to the plan so the summary has a home: + + | Pick | Default target page | If no matching page is planned | + |------|--------------------|-------------------------------| + | `search-summary` | A search / search-results page (e.g., `SearchResults`, `Search`) | Add a search page to the plan so the summary has a home | + | `data-summarization` | The detail page of the table the user called out (e.g., `ProductDetail` for products, `CaseDetail` for support cases) — ask the user if ambiguous | Propose adding a detail page; if rejected, fall back to a list/dashboard page | + + `AI_SUMMARY_PLACEMENTS` shape: one record per placement, e.g. + `[{ pick: "data-summarization", targetPage: "CaseDetail", marker: "POWERPAGES:AI-SLOT kind=data-summarization" }]`. + + The `marker` string is the comment tag Phase 5 emits into the page source as a reserved anchor that `/add-ai-webapi` later finds. Keep the shape uniform — one marker per placement, always the same tag, so the follow-up skill's explore step can grep for them deterministically. + +5. Read the design aesthetics reference: `${PLUGIN_ROOT}/skills/create-site/references/design-aesthetics.md` +6. **Map aesthetic + mood to design choices** using the Aesthetic x Mood Mapping table from the design reference. Record the chosen font direction, color direction, and motion direction. +7. Analyze requirements and determine needed components. If `AI_SUMMARY_PLACEMENTS` from step 4 implies a page that wasn't already in the plan (e.g., a `CaseDetail` page for a data-summarization pick on the support-case table), add it to the page list now. Present the component plan to the user as a table: ``` | Component Type | Count | Details | @@ -257,7 +308,7 @@ Immediately after the dev server starts, verify the scaffold is working: | Routes | 4 | /, /about, /services, /contact | ``` -6. Use best judgement to determine the final color palette based on the chosen aesthetic + mood. These will be written fresh into a new `theme.css` during Implementation (Phase 5) when the scaffold loading screen is completely replaced: +8. Use best judgement to determine the final color palette based on the chosen aesthetic + mood. These will be written fresh into a new `theme.css` during Implementation (Phase 5) when the scaffold loading screen is completely replaced: | CSS Variable | Description | Value | |-------------|-------------|-------| @@ -280,7 +331,9 @@ Immediately after the dev server starts, verify the scaffold is working: ### 4.1 Read the Design Reference -Read the design aesthetics reference: `${CLAUDE_PLUGIN_ROOT}/skills/create-site/references/design-aesthetics.md`. Every field you populate below should be justified by the chosen aesthetic + mood from Phase 3. +Read the design aesthetics reference: `${PLUGIN_ROOT}/skills/create-site/references/design-aesthetics.md`. Every field you populate below should be justified by the chosen aesthetic + mood from Phase 3. + +> **AI Readiness in the plan.** If `AI_SUMMARY_PLACEMENTS` from Phase 3 is non-empty, reflect each placement in the matching `PAGES_DATA` entry's `description` or `content` — e.g., *"Reserved slot for an AI summary card; populated later by `/add-ai-webapi`. The page ships without AI."* This keeps the user's expectation honest: the site does not depend on generative-AI features being enabled on the tenant, and there is no "Run /add-ai-webapi" placeholder visible to end-users. If `AI_SUMMARY_PLACEMENTS` is empty, omit any AI references from the plan. ### 4.2 Build the Plan Data @@ -311,7 +364,7 @@ Assemble a single JSON object with the following keys. The plan template rejects Pick an output path under `/docs/`. Default is `create-site-plan.html`; if that file already exists, pick a descriptive variant like `create-site-plan-v2.html` (the render script refuses to overwrite existing files). ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/render-createsite-plan.js" --output "/docs/create-site-plan.html" --data-inline '' +node "${PLUGIN_ROOT}/scripts/render-createsite-plan.js" --output "/docs/create-site-plan.html" --data-inline '' ``` Use `--data-inline` so no temp JSON file is written. If the JSON is too large for a single shell argument, write it to a temp file and use `--data ` instead, then delete the temp file after the render succeeds. @@ -344,6 +397,14 @@ Immediately after the user answers, `Write` the same file again with `"awaitingI ### 4.7 Ask for Approval + + +> 🚦 **Gate (plan · create-site:4.7.plan-approval):** Final sign-off on the rendered HTML plan before Phase 5 starts replacing the scaffold with real pages, components, and design tokens. +> +> **Trigger:** Phase 4.3 rendered `docs/create-site-plan.html`; Phase 4.4 opened it in the browser. +> **Why we ask:** Phase 5 rewrites the entire scaffold (theme.css, Layout, Home page, components, routes) — undoing that touches every commit in the implementation phase. +> **Cancel leaves:** Nothing destructive — the scaffold itself can be deleted with the project directory; no Dataverse / deploy fired. + Use `AskUserQuestion`: | Question | Header | Options | @@ -363,7 +424,7 @@ Use `AskUserQuestion`: > **Prerequisite:** The dev server MUST already be running and verified via Playwright (completed in Phase 2). If it is not, go back and complete Phase 2. > -> **Design reference:** Read `${CLAUDE_PLUGIN_ROOT}/skills/create-site/references/design-aesthetics.md` and apply its principles throughout this phase. All pages and components should be built with the chosen typography, color palette, motion, and backgrounds from the start — do NOT build with neutral styling first and redesign later. +> **Design reference:** Read `${PLUGIN_ROOT}/skills/create-site/references/design-aesthetics.md` and apply its principles throughout this phase. All pages and components should be built with the chosen typography, color palette, motion, and backgrounds from the start — do NOT build with neutral styling first and redesign later. **Actions**: @@ -392,6 +453,24 @@ The scaffold is a temporary loading screen — it must be **completely replaced* 5. **Router** — Register all new routes (the scaffold only has `/` and `/about` — add all requested routes) 6. **Navigation** — Add links to the new Layout/Header component 7. **Entry HTML** — Update `index.html` (or `Layout.astro` for Astro) to load the chosen Google Fonts instead of the scaffold's DM Sans + Outfit +8. **Reserve AI summary slots** — only if `AI_SUMMARY_PLACEMENTS` from Phase 3 is non-empty. For each placement, insert a single comment marker in the target page source at the intended insertion point. No visible placeholder UI, no stub components, no extra routes — just a grep-able anchor that `/add-ai-webapi` will later find and replace. Syntax depends on the framework: + + | Framework | Marker syntax | + |-----------|--------------| + | React (JSX) | `{/* POWERPAGES:AI-SLOT kind= */}` inside the component's return JSX | + | Vue (SFC template) | `` inside `