Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 2026-07-22

### dataverse-xml-lsp 2.2.0

- Validate pac's `<savedqueries>` wrapper root (exported `SavedQueries/*.xml`) by extracting and
checking each inner `<savedquery>` against `Fetch.xsd` — indicative, like forms. Previously every
exported saved-query file failed as an unknown root.
- Validate the model-driven-app `<AppModuleSiteMap>` root by extracting its inner `<SiteMap>` and
checking it against `SiteMap.xsd` — authoritative.
- Both new roots are covered by the PostToolUse hook. Removed the stale live
`**/SavedQueries/**/*.xml → Fetch.xsd` association, which mis-fired on the `<savedqueries>` root.

### cloud-flow-json-lsp 2.1.0

- The clientdata schema now validates actions nested inside `Scope`/`If`/`Foreach`/`Switch`
recursively, so a bogus `runAfter` status or a missing `type` on a nested action is caught instead
of passing silently. Top-level behaviour is unchanged.

## 2.1.0 — 2026-07-21

### dataverse-xml-lsp
Expand Down
85 changes: 69 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,86 @@
# dataverse-agent-plugins

Agent plugins for Microsoft Dataverse / Power Platform development. A
[Claude Code plugin marketplace](https://docs.anthropic.com/en/docs/claude-code) — each
plugin also works standalone (plain PowerShell scripts) for other agents, VS Code, and CI.
Catch broken Dataverse / Power Platform customization files **while you edit them** — before
`pac solution import` fails at the end of a long round-trip.

This is a [Claude Code plugin marketplace](https://docs.anthropic.com/en/docs/claude-code) with
two language-server (LSP) plugins. Each one validates a kind of hand-edited solution file against a
real schema and shows the errors inline as you type. Every plugin also runs **standalone** — plain
PowerShell/Node with no agent — so the same checks work from other agents, VS Code, or CI.

## Plugins

| Plugin | Validates | How |
|--------|-----------|-----|
| [dataverse-xml-lsp](plugins/dataverse-xml-lsp/) | Dataverse customization XML — ribbon (`RibbonDiff.xml`), sitemap, app sitemap, forms, saved queries, FetchXML, charts, ISV config, `Customizations.xml` | Live [lemminx](https://github.com/eclipse/lemminx) LSP diagnostics against the official Microsoft XSDs, plus a standalone PowerShell validator (`Validate-DataverseXml.ps1`) for wrapper files and CI |
| [cloud-flow-json-lsp](plugins/cloud-flow-json-lsp/) | Unpacked Power Automate cloud-flow clientdata (`Workflows/*.json`) | Live `vscode-json-language-server` diagnostics against a bundled clientdata/WDL wrapper schema; headless checks via built-in `Test-Json` |

## When would I use this?

- **You edit solution files as code.** You unpack a solution (`pac solution unpack`), tweak a ribbon
button, a form, a sitemap, or a cloud flow by hand, then pack and import. These plugins turn the
errors that would otherwise only appear at import time into red squiggles while you edit.
- **An agent is editing them for you.** In Claude Code (or Codex), the validator runs after edits so
the agent sees its own mistakes and fixes them, instead of confidently producing a file that won't
import.
- **You want a CI gate.** Run the standalone validator over changed files in a pipeline so a bad edit
fails the build, not the deployment.

Neither plugin talks to Dataverse or needs auth — they validate files on disk. A successful
`pac solution import` / `pac solution check` remains the final authority; these catch the large
class of mistakes you don't need a live environment to find.

## Install (Claude Code)

Add the marketplace once:

/plugin marketplace add Cordedmink2/dataverse-agent-plugins

Then install whichever plugin you need and run its one-time, per-machine setup (this fetches the
Microsoft XSDs / lemminx / JSON language server — nothing bulky is committed to the repo):

/plugin install dataverse-xml-lsp@dataverse-agent-plugins
/dataverse-xml-lsp:dataverse-xml-lsp-setup

Update later with `/plugin marketplace update dataverse-agent-plugins` then
`/plugin update dataverse-xml-lsp@dataverse-agent-plugins` (re-run setup after updates).
/plugin install cloud-flow-json-lsp@dataverse-agent-plugins
/cloud-flow-json-lsp:cloud-flow-json-lsp-setup

## Plugins
Run `/reload-plugins` afterwards. Update later with
`/plugin marketplace update dataverse-agent-plugins`, then
`/plugin update <plugin>@dataverse-agent-plugins`, and re-run that plugin's setup.

## Use it without Claude Code

| Plugin | What it does |
|--------|--------------|
| [dataverse-xml-lsp](plugins/dataverse-xml-lsp/) | LSP + CLI validator for Dataverse customization XML (ribbon, sitemap, forms, FetchXML, charts, ISV config and more) against the official Microsoft XSDs. Live lemminx LSP diagnostics + a standalone PowerShell validator. Also usable from [Codex](plugins/dataverse-xml-lsp/docs/codex.md), [VS Code with no agent](plugins/dataverse-xml-lsp/docs/vscode.md), and CI. |
| [cloud-flow-json-lsp](plugins/cloud-flow-json-lsp/) | JSON LSP for unpacked Power Automate cloud-flow clientdata (`Workflows/*.json`), validated against a bundled clientdata/WDL wrapper schema. Live `vscode-json-language-server` LSP diagnostics; headless structure checks via built-in `Test-Json`. Also usable from [Codex](plugins/cloud-flow-json-lsp/docs/codex.md), [VS Code with no agent](plugins/cloud-flow-json-lsp/docs/vscode.md), and CI. |
Each plugin works from other tools — see its docs:

- **Other agents (Codex, etc.):** `plugins/<plugin>/docs/codex.md`
- **VS Code, no agent:** `plugins/<plugin>/docs/vscode.md`
- **CI / headless:** the standalone validator (`Validate-DataverseXml.ps1`) or `Test-Json`; see the
same docs.

## Requirements

PowerShell 7+ (`pwsh`) on Windows, macOS or Linux. The `cloud-flow-json-lsp` plugin also
needs Node.js (for its `npm`-installed JSON language server). Plugins fetch what else they need
at setup (Microsoft XSDs, lemminx binary, the JSON language server) — nothing bulky or
third-party is committed here.
PowerShell 7+ (`pwsh`) on Windows, macOS, or Linux. `cloud-flow-json-lsp` also needs Node.js (for its
`npm`-installed JSON language server). Setup fetches the rest.

## Roadmap

Planned, not yet built (design detail in
[the validator-gaps spec](docs/superpowers/specs/2026-07-22-validator-gaps-design.md)):

- **Authoritative saved-query validation.** Saved queries currently validate *indicatively* — the
bundled Microsoft `Fetch.xsd` lags real exports (`layoutxml`, `LocalizedNames`), so a few
expected "not declared" errors are noise, the same as whole-form `FormXml`. A setup-time patch
step would extend the schema so saved queries validate cleanly and the patch survives an XSD
re-download.
- **Semantic cloud-flow linting.** A layer above the JSON shape check that catches things a schema
can't express: `runAfter` naming a non-sibling action or a cycle, a `connectionName` that resolves
to no declared connection reference (the top cause of a flow importing turned Off), child-invoker
connection mistakes, hard-coded environment GUIDs / host / site URLs, and condition rows that are
empty or always-true (`{"equals":["",""]}`). Bundled into `cloud-flow-json-lsp` as its "semantics"
layer.

## License

MIT. The Microsoft XSD schemas are downloaded from Microsoft at setup time and remain
subject to Microsoft's terms — they are not redistributed in this repo.
MIT. The Microsoft XSD schemas are downloaded from Microsoft at setup time and remain subject to
Microsoft's terms — they are not redistributed in this repo.
148 changes: 148 additions & 0 deletions docs/superpowers/specs/2026-07-22-validator-gaps-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Close three validator gaps (dataverse-xml-lsp + cloud-flow-json-lsp)

Date: 2026-07-22
Status: Approved for planning

## Background

Testing both plugins against a real client solution export (712 customization XML files
and 12 unpacked cloud flows) surfaced three gaps where a validator silently fails to
cover the most common real-export shapes. Each finding below was reproduced against the
installed plugins (cache paths, freshly downloaded schemas, LSP servers reloaded), so the
gaps are in the mapping/schema logic, not in setup.

| Finding | Plugin | Real-data evidence |
|---------|--------|--------------------|
| `<savedqueries>` wrapper root rejected | dataverse-xml-lsp | 295 / 712 files fail root detection |
| `<AppModuleSiteMap>` root rejected | dataverse-xml-lsp | modern per-app sitemaps uncovered |
| Nested-action errors not caught | cloud-flow-json-lsp | bogus `runAfter` in a nested action passes silently |

Two design facts established during investigation drive the approach:

- `AppModuleSiteMap` contains an inner `<SiteMap>` element carrying the Area/Group/SubArea
tree. Extracting and validating that inner element against `SiteMap.xsd` produces **0
errors** on real data — so this fix is authoritative, not indicative.
- A real `<savedquery>` fragment carries `layoutxml` / `LocalizedNames` (absent from the
bundled `Fetch.xsd`) and omits elements the schema marks required. Validation is
therefore **indicative** — the same situation as FormXml today.

## Goals

- Real exported saved queries and per-app sitemaps validate instead of being rejected at
root detection.
- Cloud-flow structural validation reaches actions nested inside Scope / If / Foreach /
Switch, not just top-level actions.
- Every fix ships with a regression test derived from the real-export shapes.

## Non-goals

- Making saved-query validation authoritative (patching the Microsoft XSD) — deferred to
the roadmap.
- Semantic flow linting (condition rows, connection-reference resolution, etc.) — deferred
to the roadmap.
- The minor "directory input is shallow / flags Entity.xml" polish — out of scope.

## Design

All three changes are small; two reuse existing machinery.

### Fix A — `dataverse-xml-lsp`: `<savedqueries>` wrapper root

Root cause: `Validate-DataverseXml.ps1` maps the singular inner element `savedquery`, but
pac exports the file with a `<savedqueries>` wrapper, so root detection reports
`Unknown root element <savedqueries>`.

Changes:

- `scripts/Validate-DataverseXml.ps1`: add `innerElementByRoot['savedqueries'] =
'savedquery'`. This reuses the existing per-fragment extraction loop (identical code path
to `forms` -> `systemform/form`); no new logic. Each `<savedquery>` child is validated
against `Fetch.xsd`.
- `hooks/validate-wrapper.mjs`: add `savedqueries` to `OWNED_ROOTS` so the PostToolUse hook
runs the CLI validator after an edit.
- `scripts/lsp-launch.mjs`: remove the stale live association `**/SavedQueries/**/*.xml ->
Fetch.xsd`. It mis-fires: lemminx validates the whole document, whose root
`<savedqueries>` is not declared in `Fetch.xsd`, producing a misleading root-level error.
Coverage moves to the hook, consistent with the other wrapper/lag-prone roots.

Behaviour: **indicative**. The known OOB errors (`layoutxml` not declared; required
`name` / `returnedtypecode` reported missing) are expected noise, read the same way as
FormXml — confirm your own edit is not named in the output.

### Fix B — `dataverse-xml-lsp`: `<AppModuleSiteMap>` wrapper root

Root cause: model-driven-app sitemaps export with root `<AppModuleSiteMap>`, which is not
in the root map; the `**/SiteMap*.xml` glob does not match the filename either.

Changes:

- `scripts/Validate-DataverseXml.ps1`: add `rootToSchema['AppModuleSiteMap'] =
'SiteMap.xsd'` and `innerElementByRoot['AppModuleSiteMap'] = 'SiteMap'`. The inner
`<SiteMap>` subtree is extracted and validated.
- `hooks/validate-wrapper.mjs`: add `AppModuleSiteMap` to `OWNED_ROOTS`.

Behaviour: **authoritative** (0 errors on real data). No live LSP association — lemminx
cannot do per-fragment extraction, so this follows the wrapper-root-via-hook pattern.

### Fix C — `cloud-flow-json-lsp`: recursive nested-action validation

Root cause: `schemas/cloud-flow-clientdata.schema.json` constrains only the top-level
`actions` map. Actions nested inside a container (`Scope` / `If` / `Foreach` / `Switch`)
fall under `additionalProperties: true` and are unchecked, so a bogus nested `runAfter`
status or a missing nested `type` passes silently.

Changes:

- Hoist the action object shape into `definitions/action` (draft-07 keyword `definitions`,
not `$defs`, so the bundled and VS Code validators both resolve it).
- `$ref` `#/definitions/action` from `definition.actions.additionalProperties` and,
recursively, from the container keys inside an action: `actions`, `else.actions`,
`cases.*.actions`, `default.actions`.
- Keep `inputs` untyped (the intentional looseness that avoids OpenApiConnection false
positives). Only `type` presence and the `runAfter` status enum are enforced, now at any
depth.

## Testing

Each plugin gains fixtures derived from the real-export shapes, kept minimal:

- dataverse-xml-lsp: a valid `<savedqueries>` file (passes with only the documented OOB
noise), a valid `<AppModuleSiteMap>` file (passes clean), and an invalid variant of each.
- cloud-flow-json-lsp: a valid nested-action flow and an invalid one with a bogus
`runAfter` status on a nested action — which must now be caught.

Assertions extend the existing suites:

- `dataverse-xml-lsp/tests/Validate-DataverseXml.Tests.ps1`: new roots resolve to the right
schema and inner element; valid/invalid fixtures behave as expected.
- `dataverse-xml-lsp/tests/Hook.Tests.ps1`: the hook now claims `savedqueries` and
`AppModuleSiteMap`.
- `dataverse-xml-lsp` LSP association test: the `SavedQueries` association is gone.
- `cloud-flow-json-lsp`: the setup self-check / smoke test gains the nested-bad-runAfter
case and confirms a diagnostic fires.

Committed fixtures stay minimal and synthetic (derived from the real shapes, not client
data). Two real client exports — `Claude/QEII` and `Claude/NZLS` — are available locally as
additional corpora for a broad pre-release sweep of each fix; they are not committed.

## Docs and versioning

- `dataverse-xml-lsp/docs/guide.md`: add both new roots to the root->schema table; extend
the "indicative, not authoritative" note to name `savedqueries`.
- `CHANGELOG.md`: one entry per plugin.
- Version bumps: `dataverse-xml-lsp` 2.1.0 -> 2.2.0; `cloud-flow-json-lsp` 2.0.0 -> 2.1.0.

## Roadmap (documented, not built here)

1. **Authoritative saved queries.** Add a post-download patch step in
`dataverse-xml-lsp/scripts/Get-Schemas.ps1` that extends the stock `Fetch.xsd` (declare
`layoutxml` / `LocalizedNames`, relax the required attributes) so saved queries validate
cleanly rather than indicatively, and the patch survives XSD re-download.
2. **Flow semantic-validate skill.** Port the checks from the personal
`power-automate-flow-dev` skill (`flow-lint.ps1` + `flow-validate-conditions.ps1`) into
`cloud-flow-json-lsp` as a bundled semantic layer above the shape schema: `runAfter`
integrity and cycles, `connectionName` -> `connectionReferences` resolution,
child-invoker connection detection, hardcoded env GUID/host/site-URL warnings, and
condition-row checks (empty and/or groups, empty operands, the always-true
`{"equals":["",""]}` row, stringified literals). This is the "Semantics" layer the guide
already references as living in an external skill.
10 changes: 7 additions & 3 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

- [Repo README](README.md): marketplace overview, install, requirements
- [Plugin README](plugins/dataverse-xml-lsp/README.md): full setup for every consumer type
- [SKILL.md](plugins/dataverse-xml-lsp/SKILL.md): the agent skill — validation workflow, root-element→schema table, ribbon-button recipe, gotchas
- [validate skill](plugins/dataverse-xml-lsp/skills/dataverse-xml-validate/SKILL.md): when and how to run the validator
- [setup skill](plugins/dataverse-xml-lsp/skills/dataverse-xml-lsp-setup/SKILL.md): one-time per-machine install
- [guide.md](plugins/dataverse-xml-lsp/docs/guide.md): root-element→schema table (incl. savedqueries and AppModuleSiteMap wrapper roots), ribbon-button recipe, gotchas
- [Codex guide](plugins/dataverse-xml-lsp/docs/codex.md): using the validator loop from non-Claude agents
- [VS Code guide](plugins/dataverse-xml-lsp/docs/vscode.md): editor-only setup, CI usage
- [Debugging](plugins/dataverse-xml-lsp/docs/debugging.md): setup failures, missing diagnostics, false positives
Expand All @@ -22,13 +24,15 @@
### cloud-flow-json-lsp

- [Plugin README](plugins/cloud-flow-json-lsp/README.md): install + setup for every consumer type
- [SKILL.md](plugins/cloud-flow-json-lsp/SKILL.md): the shape-vs-semantics split, what attaches, gotchas
- [SKILL.md](plugins/cloud-flow-json-lsp/SKILL.md): one-time per-machine install
- [guide.md](plugins/cloud-flow-json-lsp/docs/guide.md): the shape-vs-semantics split, what attaches, recursive nested-action validation, gotchas
- [Codex guide](plugins/cloud-flow-json-lsp/docs/codex.md): the `Test-Json` validation loop from non-Claude agents
- [VS Code guide](plugins/cloud-flow-json-lsp/docs/vscode.md): editor-only setup, CI usage
- [Debugging](plugins/cloud-flow-json-lsp/docs/debugging.md): setup failures, missing diagnostics
- [Schema source](plugins/cloud-flow-json-lsp/schemas/SOURCE.md): what the wrapper schema validates and why

## Optional

- [Design spec](docs/superpowers/specs/2026-07-15-dataverse-agent-plugins-repo-design.md): why the repo is shaped this way
- [Repo design spec](docs/superpowers/specs/2026-07-15-dataverse-agent-plugins-repo-design.md): why the repo is shaped this way
- [Validator-gaps spec](docs/superpowers/specs/2026-07-22-validator-gaps-design.md): savedqueries + AppModuleSiteMap + nested-flow fixes and the roadmap behind them
- [Changelog](CHANGELOG.md)
2 changes: 1 addition & 1 deletion plugins/cloud-flow-json-lsp/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
"name": "cloud-flow-json-lsp",
"version": "2.0.0",
"version": "2.1.0",
"description": "JSON LSP for unpacked Power Automate cloud-flow clientdata: live vscode-json-language-server diagnostics on solution Workflows/*.json against a bundled clientdata/WDL wrapper schema.",
"author": {
"name": "Connor Parsons"
Expand Down
2 changes: 1 addition & 1 deletion plugins/cloud-flow-json-lsp/docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ It is one layer of a two-layer story — do not confuse them:

| Layer | What it checks | Where |
|-------|----------------|-------|
| **Shape (this plugin)** | JSON well-formedness + the clientdata/WDL wrapper structure: `properties.definition` present, `definition` has `$schema`/`triggers`/`actions`, `runAfter` statuses are the WDL enum. Live, in-editor. | `schemas/cloud-flow-clientdata.schema.json` via `vscode-json-language-server` (`.lsp.json`) |
| **Shape (this plugin)** | JSON well-formedness + the clientdata/WDL wrapper structure: `properties.definition` present, `definition` has `$schema`/`triggers`/`actions`, `runAfter` statuses are the WDL enum, validated recursively through Scope/If/Foreach/Switch. Live, in-editor. | `schemas/cloud-flow-clientdata.schema.json` via `vscode-json-language-server` (`.lsp.json`) |
| **Semantics (the `power-automate-flow-dev` skill)** | Cross-node rules JSON Schema *cannot* express: `runAfter` naming a real sibling, `connectionName` resolving to a declared connection reference, child-invoker connections, hard-coded environment values, condition rows. Plus the export → unpack → edit → pack → import → verify round-trip. | that skill's `flow-lint.ps1` |

**Rule of thumb:** the LSP catches "this isn't a well-formed flow file." `flow-lint.ps1` catches "this
Expand Down
Loading
Loading