Skip to content
Turning Maintainability Guidelines Into an Agent Skill
Albert-Jan Schot
Albert-Jan Schot

· 12 min read

Post

Turning Maintainability Guidelines Into an Agent Skill

A few weeks ago, together with Daniel Laskewitz, I gave a session at the European BizzApps Summit called Beyond Shipping: How to Keep Your Low-Code Solutions Healthy. I wrote about it in more detail in this blog, where we walked through 10 simple guidelines translated from the book Building Maintainable Software into the Power Platform world.

The talk landed well, and the conversations afterwards were great, but while prepping it we kept a single demo for last. We wanted to show how you could use AI to check your solutions against the guidelines, resulting in a Skill that you could run on any solution to get a health check report.

The Gap Between Knowing and Doing

Here is the thing. Guidelines are useful, but only if you apply them. And applying them consistently, across multiple solutions, multiple makers, and multiple environments, is hard. Especially in low-code, where the speed of building is part of the appeal.

You can write a beautiful governance document, share it with your team, present it at a brown bag session. And three months later you open a flow and still find a step called DO NOT DELETE – NO IDEA WHAT THIS DOES. So the question is not do we know the rules? It is how do we make checking them part of the workflow, without slowing everyone down?.

And this is where I think AI genuinely helps. Not as a magic bullet, but as a junior colleague that does the boring checking work for you, so you can focus on the decisions that actually need a human.

Building on the AGENTS.md Idea

In an earlier blog I experimented with using AGENTS.md and the Power Platform MCP server to generate architecture documentation. The idea there was simple: give the agent a clear contract, point it at the solution metadata, and let it draft TOGAF-aligned documentation while flagging the things it can’t infer.

That worked surprisingly well. And it got me thinking, if we can use the same approach to generate documentation, why not use it to validate solutions against the 10 guidelines from the talk?

The mechanics are the same:

  • Export the solution as the single source of truth
  • Extract the metadata
  • Run it through a structured set of checks
  • Flag findings, assumptions, and missing information

The difference is the lens, the set of instructions we pass on. Documentation looks at what is there. The health check looks at how healthy what is there actually is.

Introducing the Skill

So I built a skill for it. It runs as a GitHub Copilot skill, but the same instructions can be used as a Claude skill, the format is portable enough that it depends mostly on where you want to run it.

The skill does roughly the following:

  1. Picks up the solution metadata (either from an export or via the MCP server)
  2. Walks through the 10 guidelines from the talk, one by one
  3. Produces a structured report with findings per guideline

Things it checks include long Power Fx formulas, deeply nested conditions in Power Automate, signs of copy-paste duplication, oversized interfaces on child flows, hard-coded values that should be environment variables, and naming hygiene like Button3 versus btnSubmitRequest. The usual suspects, basically.

What I like about this approach is that it is proportional. You don’t need to refactor everything on day one. The report gives you a list, you pick the ones that matter most for your context, and you fix those first. The next publish is a little healthier than the last one. That is the whole idea.

The Skill

---
name: analyze-power-platform-solution
description: Use when analyzing an exported Power Platform solution folder for maintainability issues mapped to the 10 SIG guidelines from the "Beyond Shipping" conference session.
---

# Analyze Power Platform Solution

## Overview

Walks through an unpacked Power Platform solution folder and checks it against the 10 SIG maintainability guidelines translated to Power Platform. Produces a structured per-guideline findings report with ratings and actionable recommendations.

---

## Export and Unpack Steps

The agent MUST:
- Export the Power Platform solution as the single source of truth
- Use the `solution_export` tool from the `power-platform` MCP server
- Use `Expand-Archive` in PowerShell to extract the solution zip file to `/extracted-[solution-name]/`
- Analyze extracted files Power Platform solution by reading the extract XML, JSON and YAML files.
- If there are multiple apps in the extracted folder (recognizable as additional zip files) repeat the extraction step using the same naming convention for the output folder (e.g. `/[app-name]/`) and analyze each app separately.

---

## Expected Folder Structure

<SolutionRoot>/
  solution.xml                          — component manifest
  CanvasApps/
    <AppName>/src/
      App.fx.yaml                       — named formulas, app-level settings
      <ScreenName>.fx.yaml              — per-screen controls and formulas
  Workflows/
    <FlowName>-<GUID>.json              — cloud flow definitions
  Entities/
    <TableName>/
      Entity.xml
      BusinessRules/                    — Dataverse business rule XML
  connectionreferences/                 — connector references
  environmentvariabledefinitions/       — env var definitions
  environmentvariablevalues/

---

## Analysis: 10 Guidelines

### G1 — Keep Formulas Short

Files: `CanvasApps/*/src/*.fx.yaml`

What to check:
- Read every control property value (OnSelect, OnChange, OnVisible, Items, etc.)
- Flag formulas exceeding 15 lines or 800 characters
- Count semicolons in OnSelect — flag if > 5 (multiple concerns)
- Grep `App.fx.yaml` for `App.Formulas:` block — presence = positive signal
- Grep for `Function(` or UDF definitions — presence = positive signal

Red flags: OnSelect with 5+ semicolon-separated statements, no named formulas in a large app, single property formula > 30 lines.

---

### G2 — Flatten Logic

Files: `CanvasApps/*/src/*.fx.yaml`, `Workflows/*.json`

What to check:
- Canvas: grep for `If(` and count nesting (If inside If) — flag depth > 2
- Canvas: grep for `Switch(` — presence = positive signal
- Flows: parse `actions` JSON structure, look for `"type": "If"` inside another `"type": "If"` — flag depth > 2
- Flows: look for `"type": "Switch"` — positive signal
- Flows: look for `"type": "Scope"` — positive signal (try/catch pattern)

Red flags: nested If depth > 2 in canvas or flows, zero Switch actions in flows with multi-path logic, no Scope actions for error handling.

---

### G3 — Write Code Once (DRY)

Files: `solution.xml`, `Workflows/*.json`, `CanvasApps/*/src/*.fx.yaml`, `environmentvariabledefinitions/`

What to check:
- `solution.xml`: grep for `ComponentType="ComponentLibrary"` — positive signal
- `solution.xml`: grep for `ComponentType="CanvasApp"` and count
- Flows: grep for child flow calls — look for flows calling another flow's HTTP trigger with a solution-aware connection — positive signal
- `environmentvariabledefinitions/`: count files — positive signal if > 0
- Canvas .fx.yaml: grep for `https://` or `http://` hardcoded strings — flag these
- Canvas .fx.yaml: look for identical formula fragments repeated > 3 times across screens

Red flags: no component library, no child flows, no environment variables, hardcoded URLs/GUIDs in flows or canvas, repeated formula blocks across screens.

---

### G4 — Keep Interfaces Small

Files: `CanvasApps/*/src/*.fx.yaml` (component files), `Workflows/*.json`

What to check:
- Canvas component files: grep for `CustomProperties:` and count entries per component — flag > 5
- Flows: find flows with `"type": "Request"` (manual trigger), count `properties.schema.properties` input fields — flag > 5
- Flows: find `"Respond_to_a_PowerApp_or_flow"` action, count output fields — flag > 5

Red flags: component with > 5 custom properties, flow trigger with > 5 input fields, flow response with > 5 output fields.

---

### G5 — Separate Concerns

Files: `solution.xml`, `CanvasApps/*/src/*.fx.yaml`, `Entities/*/BusinessRules/`

What to check:
- `solution.xml`: count `ComponentType="CanvasApp"` vs `ComponentType="AppModule"` (model-driven)
- Per canvas app: count `.fx.yaml` files in `src/` (= screen count) — flag if > 15
- Canvas .fx.yaml: grep for `Launch(` — positive signal (multi-app architecture)
- Canvas .fx.yaml: grep for `Param(` — positive signal (receives context from another app)
- `Entities/*/BusinessRules/`: count XML files — positive signal
- Canvas .fx.yaml: grep for `Patch(` combined with `If(IsBlank(` in same OnSelect — flag (validation in UI layer)

Red flags: single canvas app > 15 screens, no model-driven apps for data-heavy solution, no Launch/Param usage, no Dataverse business rules, validation logic embedded in canvas Patch calls.

---

### G6 — Loose Coupling

Files: `Workflows/*.json`, `CanvasApps/*/src/*.fx.yaml`, `solution.xml`

What to check:
- Flows: categorise trigger types from JSON:
  - `"OpenApiConnectionWebhook"` with Dataverse = event-driven (positive)
  - `"Request"` HTTP trigger = potential tight coupling
  - `"Recurrence"` = scheduled (neutral)
  - `"ApiConnectionNotification"` = connector-based event (positive)
- Canvas .fx.yaml: grep for `http` inside `Office365Outlook`, direct HTTP connector calls, or hardcoded flow trigger URLs
- `solution.xml`: grep for `ComponentType="CustomConnector"` — positive signal

Red flags: all flows are HTTP-triggered (no event-driven), canvas app contains hardcoded flow URL calls, no Dataverse-triggered flows in a Dataverse solution.

---

### G7 — Balanced Architecture

Files: `solution.xml`, `CanvasApps/*/src/`, `Workflows/*.json`

What to check:
- Canvas: count `.fx.yaml` screen files per app, compute min/max/average — flag if max > 3× average
- Flows: parse each flow JSON, count total actions — flag if one flow has 5× more actions than average
- `solution.xml`: list component types and counts — flag extreme skew
- Check if all components are in a single solution file (no segmentation)

Red flags: one app with 3× more screens than others, one flow with 5× more actions than others, all components in one monolithic solution.

---

### G8 — Keep Codebase Small

Files: `solution.xml`, `CanvasApps/*/src/*.fx.yaml`, `Entities/*/BusinessRules/`, `connectionreferences/`

What to check:
- `solution.xml`: count `AppModule` (model-driven app) components — absence in data-heavy solution = flag
- `Entities/*/BusinessRules/`: count XML files — positive signal
- Canvas .fx.yaml: count `Patch(` occurrences — high count in absence of model-driven apps = flag
- `connectionreferences/`: identify custom vs. prebuilt connectors
- Canvas .fx.yaml: total line count across all screen files — proxy for custom code volume

Red flags: zero model-driven apps for a data management solution, many Patch() screens that could be model-driven views/forms, custom connectors where prebuilt equivalents exist.

---

### G9 — Automate Tests

Files: solution root (recursive search), `Workflows/*.json`

What to check:
- Search solution root for `*.testplan.yaml`, `testplan.yaml`, or any YAML with `testSuite:` key — positive signal
- Search for `azure-pipelines.yml` or `.github/workflows/` — positive signal
- Flows: count flows with at least one `"type": "Scope"` action (error handling)
- Flows: look for monitoring/alerting flows (triggered on `Failed` run of another flow)
- Look for solution checker result files (`*.sarif`, `*-checker-results.*`)

Red flags: no test plan YAML files, no CI/CD pipeline config, flows with zero Scope/error-handling actions, no solution checker results.

---

### G10 — Clean Code

Files: `CanvasApps/*/src/*.fx.yaml`, `Workflows/*.json`

What to check:
- Canvas: grep all control names for default patterns: `Button\d+`, `TextInput\d+`, `Label\d+`, `Gallery\d+`, `Icon\d+`, `Image\d+`
  - Calculate %: (default-named controls / total controls) — flag if > 20%
- Canvas: check for prefix convention: `btn`, `txt`, `lbl`, `gal`, `ico`, `img`, `drp`, `sldr` — positive signal
- Flows: in JSON, check each action's `metadata.operationMetadataId` vs. its display name property — flag unchanged/default names like `"Send_an_email_\(V2\)"`
- Flows: check `"status": "Stopped"` at flow level — each = dead code flag
- Flows: count actions with non-empty `description` or `note` fields — positive signal

Red flags: > 20% controls with default names, flow actions using default connector names, disabled (Stopped) flows in the solution, no action descriptions anywhere.

---

## Output Format

# Power Platform Maintainability Report
**Solution:** [display name from solution.xml]
**Analyzed:** [date]

## Summary

| # | Guideline | Rating | Key Finding |
|---|-----------|--------|-------------|
| G1 | Keep Formulas Short | 🟢/🟡/🔴 | [one-liner] |
| G2 | Flatten Logic | ... | ... |
| G3 | Write Code Once | ... | ... |
| G4 | Keep Interfaces Small | ... | ... |
| G5 | Separate Concerns | ... | ... |
| G6 | Loose Coupling | ... | ... |
| G7 | Balanced Architecture | ... | ... |
| G8 | Keep Codebase Small | ... | ... |
| G9 | Automate Tests | ... | ... |
| G10 | Clean Code | ... | ... |

---

## G1 — Keep Formulas Short
**Rating:** 🟢/🟡/🔴

**Findings:**
- [specific finding: file path, control name, line count]

**Positive signals:**
- [what the solution does right]

**Recommendation:**
- [concrete next step referencing Power Platform feature]

[... repeat for G2–G10 ...]

---

## Overall Assessment
[2–3 sentences on the solution's maintainability posture, referencing the three SIG principles:
simple guidelines, start now, be proportional.]

## Rating Heuristics

| Rating | Meaning |
|--------|---------|
| 🟢 | Follows the guideline; at most isolated minor issues |
| 🟡 | Pattern of issues; some attention needed |
| 🔴 | Systematic violations; refactoring recommended |

Apply proportionality: a few long formulas = 🟡, majority of OnSelects are bloated = 🔴, one unnamed control in an otherwise clean app = 🟢.

Drop it into your skills folder, point it at a solution, and let it run, or look at our session materials at Beyond Shipping for a ready-to-run version.

A Few Honest Caveats

Before anyone expects miracles, a few things to keep in mind.

The skill is good at the things metadata can tell you. Formula length, naming patterns, structural complexity, environment variable usage, those are all visible. But intent is harder. The skill cannot tell you whether splitting one big flow into three smaller ones actually maps to your business process. That still needs YOU!.

It is also not a replacement for the Solution Checker, the Power Platform Pipelines, or proper code review. Think of it as an extra pair of eyes that is happy to read through 47 actions and tell you which ones look suspicious. Finally only you decide what to do with the findings.

And as always: it depends. A small canvas app for a team of five does not need the same level of scrutiny as a solution that runs a core business process. Use the report as input, not as gospel.

Where This Fits in a Bigger Picture

This skill is one piece of a small but growing toolbox. The documentation skill from the earlier blog is another. And my friend Daniel Laskewitz has been working on a skill that generates an inventory report for the Power Platform, which gives you a different angle again, more about what do we have running where, rather than how healthy is this one solution.

Together, these skills start to form something interesting. You get documentation, inventory, and health checks, all generated from the same source of truth: the metadata you already have. None of them are silver bullets, and you still need to think and decide. But the boring gathering and structuring work, the part that always gets postponed, becomes a lot less painful.

If you take one thing from this post, let it be this: start small. Run the skill on one solution. See what it flags. Fix the two or three things that matter most. Then do it again on the next publish. Maintainability is not a big refactoring project, it is a habit. And the nice thing about habits is that tools can help you keep them.

AI is here to stay. Use it for the parts where it actually helps, and keep the interesting decisions for yourself.

Albert-Jan Schot

Albert-Jan Schot

CTO, Microsoft MVP & FastTrack Recognized Solution Architect

I am Albert-Jan Schot, CTO at Blis Digital, Microsoft MVP, and FastTrack Recognized Solution Architect focused on Microsoft 365, Azure, and AI agents. I help teams turn complex Microsoft Cloud challenges into practical architecture decisions and shipped outcomes.

Copilot Studio Microsoft 365 Agent Flows

Zuid Holland, Netherlands

Related Posts